You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
4.4 KiB
202 lines
4.4 KiB
/*! |
|
* Stylus - SourceMapper |
|
* Copyright (c) Automattic <developer.wordpress.com> |
|
* MIT Licensed |
|
*/ |
|
|
|
/** |
|
* Module dependencies. |
|
*/ |
|
|
|
var Compiler = require('./compiler') |
|
, SourceMapGenerator = require('source-map').SourceMapGenerator |
|
, basename = require('path').basename |
|
, extname = require('path').extname |
|
, dirname = require('path').dirname |
|
, join = require('path').join |
|
, relative = require('path').relative |
|
, sep = require('path').sep |
|
, fs = require('fs'); |
|
|
|
/** |
|
* Initialize a new `SourceMapper` generator with the given `root` Node |
|
* and the following `options`. |
|
* |
|
* @param {Node} root |
|
* @api public |
|
*/ |
|
|
|
var SourceMapper = module.exports = function SourceMapper(root, options){ |
|
options = options || {}; |
|
this.column = 1; |
|
this.lineno = 1; |
|
this.contents = {}; |
|
this.filename = options.filename; |
|
this.dest = options.dest; |
|
|
|
var sourcemap = options.sourcemap; |
|
this.basePath = sourcemap.basePath || '.'; |
|
this.inline = sourcemap.inline; |
|
this.comment = sourcemap.comment; |
|
if (extname(this.dest) === '.css') { |
|
this.basename = basename(this.dest); |
|
} else { |
|
this.basename = basename(this.filename, extname(this.filename)) + '.css'; |
|
} |
|
this.utf8 = false; |
|
|
|
this.map = new SourceMapGenerator({ |
|
file: this.basename, |
|
sourceRoot: sourcemap.sourceRoot || null |
|
}); |
|
Compiler.call(this, root, options); |
|
}; |
|
|
|
/** |
|
* Inherit from `Compiler.prototype`. |
|
*/ |
|
|
|
SourceMapper.prototype.__proto__ = Compiler.prototype; |
|
|
|
/** |
|
* Generate and write source map. |
|
* |
|
* @return {String} |
|
* @api private |
|
*/ |
|
|
|
var compile = Compiler.prototype.compile; |
|
SourceMapper.prototype.compile = function(){ |
|
var css = compile.call(this) |
|
, out = this.basename + '.map' |
|
, url = this.normalizePath(this.dest |
|
? join(this.dest, out) |
|
: join(dirname(this.filename), out)) |
|
, map; |
|
|
|
if (this.inline) { |
|
map = this.map.toString(); |
|
url = 'data:application/json;' |
|
+ (this.utf8 ? 'charset=utf-8;' : '') + 'base64,' |
|
+ new Buffer(map).toString('base64'); |
|
} |
|
if (this.inline || false !== this.comment) |
|
css += '/*# sourceMappingURL=' + url + ' */'; |
|
return css; |
|
}; |
|
|
|
/** |
|
* Add mapping information. |
|
* |
|
* @param {String} str |
|
* @param {Node} node |
|
* @return {String} |
|
* @api private |
|
*/ |
|
|
|
SourceMapper.prototype.out = function(str, node){ |
|
if (node && node.lineno) { |
|
var filename = this.normalizePath(node.filename); |
|
|
|
this.map.addMapping({ |
|
original: { |
|
line: node.lineno, |
|
column: node.column - 1 |
|
}, |
|
generated: { |
|
line: this.lineno, |
|
column: this.column - 1 |
|
}, |
|
source: filename |
|
}); |
|
|
|
if (this.inline && !this.contents[filename]) { |
|
this.map.setSourceContent(filename, fs.readFileSync(node.filename, 'utf-8')); |
|
this.contents[filename] = true; |
|
} |
|
} |
|
|
|
this.move(str); |
|
return str; |
|
}; |
|
|
|
/** |
|
* Move current line and column position. |
|
* |
|
* @param {String} str |
|
* @api private |
|
*/ |
|
|
|
SourceMapper.prototype.move = function(str){ |
|
var lines = str.match(/\n/g) |
|
, idx = str.lastIndexOf('\n'); |
|
|
|
if (lines) this.lineno += lines.length; |
|
this.column = ~idx |
|
? str.length - idx |
|
: this.column + str.length; |
|
}; |
|
|
|
/** |
|
* Normalize the given `path`. |
|
* |
|
* @param {String} path |
|
* @return {String} |
|
* @api private |
|
*/ |
|
|
|
SourceMapper.prototype.normalizePath = function(path){ |
|
path = relative(this.dest || this.basePath, path); |
|
if ('\\' == sep) { |
|
path = path.replace(/^[a-z]:\\/i, '/') |
|
.replace(/\\/g, '/'); |
|
} |
|
return path; |
|
}; |
|
|
|
/** |
|
* Visit Literal. |
|
*/ |
|
|
|
var literal = Compiler.prototype.visitLiteral; |
|
SourceMapper.prototype.visitLiteral = function(lit){ |
|
var val = literal.call(this, lit) |
|
, filename = this.normalizePath(lit.filename) |
|
, indentsRe = /^\s+/ |
|
, lines = val.split('\n'); |
|
|
|
// add mappings for multiline literals |
|
if (lines.length > 1) { |
|
lines.forEach(function(line, i) { |
|
var indents = line.match(indentsRe) |
|
, column = indents && indents[0] |
|
? indents[0].length |
|
: 0; |
|
|
|
if (lit.css) column += 2; |
|
|
|
this.map.addMapping({ |
|
original: { |
|
line: lit.lineno + i, |
|
column: column |
|
}, |
|
generated: { |
|
line: this.lineno + i, |
|
column: 0 |
|
}, |
|
source: filename |
|
}); |
|
}, this); |
|
} |
|
return val; |
|
}; |
|
|
|
/** |
|
* Visit Charset. |
|
*/ |
|
|
|
var charset = Compiler.prototype.visitCharset; |
|
SourceMapper.prototype.visitCharset = function(node){ |
|
this.utf8 = ('utf-8' == node.val.string.toLowerCase()); |
|
return charset.call(this, node); |
|
};
|
|
|