sourcemapper.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /*!
  2. * Stylus - SourceMapper
  3. * Copyright (c) Automattic <developer.wordpress.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var Compiler = require('./compiler')
  10. , Buffer = require('safer-buffer').Buffer
  11. , SourceMapGenerator = require('source-map').SourceMapGenerator
  12. , basename = require('path').basename
  13. , extname = require('path').extname
  14. , dirname = require('path').dirname
  15. , join = require('path').join
  16. , relative = require('path').relative
  17. , sep = require('path').sep
  18. , fs = require('fs');
  19. /**
  20. * Initialize a new `SourceMapper` generator with the given `root` Node
  21. * and the following `options`.
  22. *
  23. * @param {Node} root
  24. * @api public
  25. */
  26. var SourceMapper = module.exports = function SourceMapper(root, options){
  27. options = options || {};
  28. this.column = 1;
  29. this.lineno = 1;
  30. this.contents = {};
  31. this.filename = options.filename;
  32. this.dest = options.dest;
  33. var sourcemap = options.sourcemap;
  34. this.basePath = sourcemap.basePath || '.';
  35. this.inline = sourcemap.inline;
  36. this.comment = sourcemap.comment;
  37. if (this.dest && extname(this.dest) === '.css') {
  38. this.basename = basename(this.dest);
  39. this.dest = dirname(this.dest);
  40. } else {
  41. this.basename = basename(this.filename, extname(this.filename)) + '.css';
  42. }
  43. this.utf8 = false;
  44. this.map = new SourceMapGenerator({
  45. file: this.basename,
  46. sourceRoot: sourcemap.sourceRoot || null
  47. });
  48. Compiler.call(this, root, options);
  49. };
  50. /**
  51. * Inherit from `Compiler.prototype`.
  52. */
  53. SourceMapper.prototype.__proto__ = Compiler.prototype;
  54. /**
  55. * Generate and write source map.
  56. *
  57. * @return {String}
  58. * @api private
  59. */
  60. var compile = Compiler.prototype.compile;
  61. SourceMapper.prototype.compile = function(){
  62. var css = compile.call(this)
  63. , out = this.basename + '.map'
  64. , url = this.normalizePath(this.dest
  65. ? join(this.dest, out)
  66. : join(dirname(this.filename), out))
  67. , map;
  68. if (this.inline) {
  69. map = this.map.toString();
  70. url = 'data:application/json;'
  71. + (this.utf8 ? 'charset=utf-8;' : '') + 'base64,'
  72. + Buffer.from(map).toString('base64');
  73. }
  74. if (this.inline || false !== this.comment)
  75. css += '/*# sourceMappingURL=' + url + ' */';
  76. return css;
  77. };
  78. /**
  79. * Add mapping information.
  80. *
  81. * @param {String} str
  82. * @param {Node} node
  83. * @return {String}
  84. * @api private
  85. */
  86. SourceMapper.prototype.out = function(str, node){
  87. if (node && node.lineno) {
  88. var filename = this.normalizePath(node.filename);
  89. this.map.addMapping({
  90. original: {
  91. line: node.lineno,
  92. column: node.column - 1
  93. },
  94. generated: {
  95. line: this.lineno,
  96. column: this.column - 1
  97. },
  98. source: filename
  99. });
  100. if (this.inline && !this.contents[filename]) {
  101. this.map.setSourceContent(filename, fs.readFileSync(node.filename, 'utf-8'));
  102. this.contents[filename] = true;
  103. }
  104. }
  105. this.move(str);
  106. return str;
  107. };
  108. /**
  109. * Move current line and column position.
  110. *
  111. * @param {String} str
  112. * @api private
  113. */
  114. SourceMapper.prototype.move = function(str){
  115. var lines = str.match(/\n/g)
  116. , idx = str.lastIndexOf('\n');
  117. if (lines) this.lineno += lines.length;
  118. this.column = ~idx
  119. ? str.length - idx
  120. : this.column + str.length;
  121. };
  122. /**
  123. * Normalize the given `path`.
  124. *
  125. * @param {String} path
  126. * @return {String}
  127. * @api private
  128. */
  129. SourceMapper.prototype.normalizePath = function(path){
  130. path = relative(this.dest || this.basePath, path);
  131. if ('\\' == sep) {
  132. path = path.replace(/^[a-z]:\\/i, '/')
  133. .replace(/\\/g, '/');
  134. }
  135. return path;
  136. };
  137. /**
  138. * Visit Literal.
  139. */
  140. var literal = Compiler.prototype.visitLiteral;
  141. SourceMapper.prototype.visitLiteral = function(lit){
  142. var val = literal.call(this, lit)
  143. , filename = this.normalizePath(lit.filename)
  144. , indentsRe = /^\s+/
  145. , lines = val.split('\n');
  146. // add mappings for multiline literals
  147. if (lines.length > 1) {
  148. lines.forEach(function(line, i) {
  149. var indents = line.match(indentsRe)
  150. , column = indents && indents[0]
  151. ? indents[0].length
  152. : 0;
  153. if (lit.css) column += 2;
  154. this.map.addMapping({
  155. original: {
  156. line: lit.lineno + i,
  157. column: column
  158. },
  159. generated: {
  160. line: this.lineno + i,
  161. column: 0
  162. },
  163. source: filename
  164. });
  165. }, this);
  166. }
  167. return val;
  168. };
  169. /**
  170. * Visit Charset.
  171. */
  172. var charset = Compiler.prototype.visitCharset;
  173. SourceMapper.prototype.visitCharset = function(node){
  174. this.utf8 = ('utf-8' == node.val.string.toLowerCase());
  175. return charset.call(this, node);
  176. };