UNPKG

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