UNPKG

12.3 kBJavaScriptView Raw
1/*!
2 * Stylus - Compiler
3 * Copyright (c) Automattic <developer.wordpress.com>
4 * MIT Licensed
5 */
6
7/**
8 * Module dependencies.
9 */
10
11var Visitor = require('./')
12 , utils = require('../utils')
13 , fs = require('fs');
14
15/**
16 * Initialize a new `Compiler` with the given `root` Node
17 * and the following `options`.
18 *
19 * Options:
20 *
21 * - `compress` Compress the CSS output (default: false)
22 *
23 * @param {Node} root
24 * @api public
25 */
26
27var Compiler = module.exports = function Compiler(root, options) {
28 options = options || {};
29 this.compress = options.compress;
30 this.firebug = options.firebug;
31 this.linenos = options.linenos;
32 this.spaces = options['indent spaces'] || 2;
33 this.indents = 1;
34 Visitor.call(this, root);
35 this.stack = [];
36};
37
38/**
39 * Inherit from `Visitor.prototype`.
40 */
41
42Compiler.prototype.__proto__ = Visitor.prototype;
43
44/**
45 * Compile to css, and return a string of CSS.
46 *
47 * @return {String}
48 * @api private
49 */
50
51Compiler.prototype.compile = function(){
52 return this.visit(this.root);
53};
54
55/**
56 * Output `str`
57 *
58 * @param {String} str
59 * @param {Node} node
60 * @return {String}
61 * @api private
62 */
63
64Compiler.prototype.out = function(str, node){
65 return str;
66};
67
68/**
69 * Return indentation string.
70 *
71 * @return {String}
72 * @api private
73 */
74
75Compiler.prototype.__defineGetter__('indent', function(){
76 if (this.compress) return '';
77 return new Array(this.indents).join(Array(this.spaces + 1).join(' '));
78});
79
80/**
81 * Check if given `node` needs brackets.
82 *
83 * @param {Node} node
84 * @return {Boolean}
85 * @api private
86 */
87
88Compiler.prototype.needBrackets = function(node){
89 return 1 == this.indents
90 || 'atrule' != node.nodeName
91 || node.hasOnlyProperties;
92};
93
94/**
95 * Visit Root.
96 */
97
98Compiler.prototype.visitRoot = function(block){
99 this.buf = '';
100 for (var i = 0, len = block.nodes.length; i < len; ++i) {
101 var node = block.nodes[i];
102 if (this.linenos || this.firebug) this.debugInfo(node);
103 var ret = this.visit(node);
104 if (ret) this.buf += this.out(ret + '\n', node);
105 }
106 return this.buf;
107};
108
109/**
110 * Visit Block.
111 */
112
113Compiler.prototype.visitBlock = function(block){
114 var node
115 , separator = this.compress ? '' : '\n'
116 , needBrackets;
117
118 if (block.hasProperties && !block.lacksRenderedSelectors) {
119 needBrackets = this.needBrackets(block.node);
120
121 if (needBrackets) {
122 this.buf += this.out(this.compress ? '{' : ' {\n');
123 ++this.indents;
124 }
125 for (var i = 0, len = block.nodes.length; i < len; ++i) {
126 this.last = len - 1 == i;
127 node = block.nodes[i];
128 switch (node.nodeName) {
129 case 'null':
130 case 'expression':
131 case 'function':
132 case 'group':
133 case 'block':
134 case 'unit':
135 case 'media':
136 case 'keyframes':
137 case 'atrule':
138 case 'supports':
139 continue;
140 // inline comments
141 case !this.compress && node.inline && 'comment':
142 this.buf = this.buf.slice(0, -1);
143 this.buf += this.out(' ' + this.visit(node) + '\n', node);
144 break;
145 case 'property':
146 var ret = this.visit(node) + separator;
147 this.buf += this.compress ? ret : this.out(ret, node);
148 break;
149 default:
150 this.buf += this.out(this.visit(node) + separator, node);
151 }
152 }
153 if (needBrackets) {
154 --this.indents;
155 this.buf += this.out(this.indent + '}' + separator);
156 }
157 }
158
159 // Nesting
160 for (var i = 0, len = block.nodes.length; i < len; ++i) {
161 node = block.nodes[i];
162 switch (node.nodeName) {
163 case 'group':
164 case 'block':
165 case 'keyframes':
166 if (this.linenos || this.firebug) this.debugInfo(node);
167 this.visit(node);
168 break;
169 case 'media':
170 case 'import':
171 case 'atrule':
172 case 'supports':
173 this.visit(node);
174 break;
175 case 'comment':
176 // only show unsuppressed comments
177 if (!node.suppress) {
178 this.buf += this.out(this.indent + this.visit(node) + '\n', node);
179 }
180 break;
181 case 'charset':
182 case 'literal':
183 case 'namespace':
184 this.buf += this.out(this.visit(node) + '\n', node);
185 break;
186 }
187 }
188};
189
190/**
191 * Visit Keyframes.
192 */
193
194Compiler.prototype.visitKeyframes = function(node){
195 if (!node.frames) return;
196
197 var prefix = 'official' == node.prefix
198 ? ''
199 : '-' + node.prefix + '-';
200
201 this.buf += this.out('@' + prefix + 'keyframes '
202 + this.visit(node.val)
203 + (this.compress ? '{' : ' {\n'), node);
204
205 this.keyframe = true;
206 ++this.indents;
207 this.visit(node.block);
208 --this.indents;
209 this.keyframe = false;
210
211 this.buf += this.out('}' + (this.compress ? '' : '\n'));
212};
213
214/**
215 * Visit Media.
216 */
217
218Compiler.prototype.visitMedia = function(media){
219 var val = media.val;
220 if (!media.hasOutput || !val.nodes.length) return;
221
222 this.buf += this.out('@media ', media);
223 this.visit(val);
224 this.buf += this.out(this.compress ? '{' : ' {\n');
225 ++this.indents;
226 this.visit(media.block);
227 --this.indents;
228 this.buf += this.out('}' + (this.compress ? '' : '\n'));
229};
230
231/**
232 * Visit QueryList.
233 */
234
235Compiler.prototype.visitQueryList = function(queries){
236 for (var i = 0, len = queries.nodes.length; i < len; ++i) {
237 this.visit(queries.nodes[i]);
238 if (len - 1 != i) this.buf += this.out(',' + (this.compress ? '' : ' '));
239 }
240};
241
242/**
243 * Visit Query.
244 */
245
246Compiler.prototype.visitQuery = function(node){
247 var len = node.nodes.length;
248 if (node.predicate) this.buf += this.out(node.predicate + ' ');
249 if (node.type) this.buf += this.out(node.type + (len ? ' and ' : ''));
250 for (var i = 0; i < len; ++i) {
251 this.buf += this.out(this.visit(node.nodes[i]));
252 if (len - 1 != i) this.buf += this.out(' and ');
253 }
254};
255
256/**
257 * Visit Feature.
258 */
259
260Compiler.prototype.visitFeature = function(node){
261 if (!node.expr) {
262 return node.name;
263 } else if (node.expr.isEmpty) {
264 return '(' + node.name + ')';
265 } else {
266 return '(' + node.name + ':' + (this.compress ? '' : ' ') + this.visit(node.expr) + ')';
267 }
268};
269
270/**
271 * Visit Import.
272 */
273
274Compiler.prototype.visitImport = function(imported){
275 this.buf += this.out('@import ' + this.visit(imported.path) + ';\n', imported);
276};
277
278/**
279 * Visit Atrule.
280 */
281
282Compiler.prototype.visitAtrule = function(atrule){
283 var newline = this.compress ? '' : '\n';
284
285 this.buf += this.out(this.indent + '@' + atrule.type, atrule);
286
287 if (atrule.val) this.buf += this.out(' ' + atrule.val.trim());
288
289 if (atrule.block) {
290 if (atrule.hasOnlyProperties) {
291 this.visit(atrule.block);
292 } else {
293 this.buf += this.out(this.compress ? '{' : ' {\n');
294 ++this.indents;
295 this.visit(atrule.block);
296 --this.indents;
297 this.buf += this.out(this.indent + '}' + newline);
298 }
299 } else {
300 this.buf += this.out(';' + newline);
301 }
302};
303
304/**
305 * Visit Supports.
306 */
307
308Compiler.prototype.visitSupports = function(node){
309 if (!node.hasOutput) return;
310
311 this.buf += this.out(this.indent + '@supports ', node);
312 this.isCondition = true;
313 this.buf += this.out(this.visit(node.condition));
314 this.isCondition = false;
315 this.buf += this.out(this.compress ? '{' : ' {\n');
316 ++this.indents;
317 this.visit(node.block);
318 --this.indents;
319 this.buf += this.out(this.indent + '}' + (this.compress ? '' : '\n'));
320},
321
322/**
323 * Visit Comment.
324 */
325
326Compiler.prototype.visitComment = function(comment){
327 return this.compress
328 ? comment.suppress
329 ? ''
330 : comment.str
331 : comment.str;
332};
333
334/**
335 * Visit Function.
336 */
337
338Compiler.prototype.visitFunction = function(fn){
339 return fn.name;
340};
341
342/**
343 * Visit Charset.
344 */
345
346Compiler.prototype.visitCharset = function(charset){
347 return '@charset ' + this.visit(charset.val) + ';';
348};
349
350/**
351 * Visit Namespace.
352 */
353
354Compiler.prototype.visitNamespace = function(namespace){
355 return '@namespace '
356 + (namespace.prefix ? this.visit(namespace.prefix) + ' ' : '')
357 + this.visit(namespace.val) + ';';
358};
359
360/**
361 * Visit Literal.
362 */
363
364Compiler.prototype.visitLiteral = function(lit){
365 var val = lit.val;
366 if (lit.css) val = val.replace(/^ /gm, '');
367 return val;
368};
369
370/**
371 * Visit Boolean.
372 */
373
374Compiler.prototype.visitBoolean = function(bool){
375 return bool.toString();
376};
377
378/**
379 * Visit RGBA.
380 */
381
382Compiler.prototype.visitRGBA = function(rgba){
383 return rgba.toString();
384};
385
386/**
387 * Visit HSLA.
388 */
389
390Compiler.prototype.visitHSLA = function(hsla){
391 return hsla.rgba.toString();
392};
393
394/**
395 * Visit Unit.
396 */
397
398Compiler.prototype.visitUnit = function(unit){
399 var type = unit.type || ''
400 , n = unit.val
401 , float = n != (n | 0);
402
403 // Compress
404 if (this.compress) {
405 // Always return '0' unless the unit is a percentage or time
406 if ('%' != type && 's' != type && 'ms' != type && 0 == n) return '0';
407 // Omit leading '0' on floats
408 if (float && n < 1 && n > -1) {
409 return n.toString().replace('0.', '.') + type;
410 }
411 }
412
413 return (float ? parseFloat(n.toFixed(15)) : n).toString() + type;
414};
415
416/**
417 * Visit Group.
418 */
419
420Compiler.prototype.visitGroup = function(group){
421 var stack = this.keyframe ? [] : this.stack
422 , comma = this.compress ? ',' : ',\n';
423
424 stack.push(group.nodes);
425
426 // selectors
427 if (group.block.hasProperties) {
428 var selectors = utils.compileSelectors.call(this, stack)
429 , len = selectors.length;
430
431 if (len) {
432 if (this.keyframe) comma = this.compress ? ',' : ', ';
433
434 for (var i = 0; i < len; ++i) {
435 var selector = selectors[i]
436 , last = (i == len - 1);
437
438 // keyframe blocks (10%, 20% { ... })
439 if (this.keyframe) selector = i ? selector.trim() : selector;
440
441 this.buf += this.out(selector + (last ? '' : comma), group.nodes[i]);
442 }
443 } else {
444 group.block.lacksRenderedSelectors = true;
445 }
446 }
447
448 // output block
449 this.visit(group.block);
450 stack.pop();
451};
452
453/**
454 * Visit Ident.
455 */
456
457Compiler.prototype.visitIdent = function(ident){
458 return ident.name;
459};
460
461/**
462 * Visit String.
463 */
464
465Compiler.prototype.visitString = function(string){
466 return this.isURL
467 ? string.val
468 : string.toString();
469};
470
471/**
472 * Visit Null.
473 */
474
475Compiler.prototype.visitNull = function(node){
476 return '';
477};
478
479/**
480 * Visit Call.
481 */
482
483Compiler.prototype.visitCall = function(call){
484 this.isURL = 'url' == call.name;
485 var args = call.args.nodes.map(function(arg){
486 return this.visit(arg);
487 }, this).join(this.compress ? ',' : ', ');
488 if (this.isURL) args = '"' + args + '"';
489 this.isURL = false;
490 return call.name + '(' + args + ')';
491};
492
493/**
494 * Visit Expression.
495 */
496
497Compiler.prototype.visitExpression = function(expr){
498 var buf = []
499 , self = this
500 , len = expr.nodes.length
501 , nodes = expr.nodes.map(function(node){ return self.visit(node); });
502
503 nodes.forEach(function(node, i){
504 var last = i == len - 1;
505 buf.push(node);
506 if ('/' == nodes[i + 1] || '/' == node) return;
507 if (last) return;
508
509 var space = self.isURL || (self.isCondition
510 && (')' == nodes[i + 1] || '(' == node))
511 ? '' : ' ';
512
513 buf.push(expr.isList
514 ? (self.compress ? ',' : ', ')
515 : space);
516 });
517
518 return buf.join('');
519};
520
521/**
522 * Visit Arguments.
523 */
524
525Compiler.prototype.visitArguments = Compiler.prototype.visitExpression;
526
527/**
528 * Visit Property.
529 */
530
531Compiler.prototype.visitProperty = function(prop){
532 var val = this.visit(prop.expr).trim()
533 , name = (prop.name || prop.segments.join(''))
534 , arr = [];
535 arr.push(
536 this.out(this.indent),
537 this.out(name + (this.compress ? ':' : ': '), prop),
538 this.out(val, prop.expr),
539 this.out(this.compress ? (this.last ? '' : ';') : ';')
540 );
541 return arr.join('');
542};
543
544/**
545 * Debug info.
546 */
547
548Compiler.prototype.debugInfo = function(node){
549
550 var path = node.filename == 'stdin' ? 'stdin' : fs.realpathSync(node.filename)
551 , line = (node.nodes && node.nodes.length ? node.nodes[0].lineno : node.lineno) || 1;
552
553 if (this.linenos){
554 this.buf += '\n/* ' + 'line ' + line + ' : ' + path + ' */\n';
555 }
556
557 if (this.firebug){
558 // debug info for firebug, the crazy formatting is needed
559 path = 'file\\\:\\\/\\\/' + path.replace(/([.:/\\])/g, function(m) {
560 return '\\' + (m === '\\' ? '\/' : m)
561 });
562 line = '\\00003' + line;
563 this.buf += '\n@media -stylus-debug-info'
564 + '{filename{font-family:' + path
565 + '}line{font-family:' + line + '}}\n';
566 }
567}