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