UNPKG

5.57 kBJavaScriptView Raw
1
2/*!
3 * Stylus - Renderer
4 * Copyright (c) Automattic <developer.wordpress.com>
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
12var Parser = require('./parser')
13 , EventEmitter = require('events').EventEmitter
14 , Evaluator = require('./visitor/evaluator')
15 , Normalizer = require('./visitor/normalizer')
16 , events = new EventEmitter
17 , utils = require('./utils')
18 , nodes = require('./nodes')
19 , join = require('path').join;
20
21/**
22 * Expose `Renderer`.
23 */
24
25module.exports = Renderer;
26
27/**
28 * Initialize a new `Renderer` with the given `str` and `options`.
29 *
30 * @param {String} str
31 * @param {Object} options
32 * @api public
33 */
34
35function Renderer(str, options) {
36 options = options || {};
37 options.globals = options.globals || {};
38 options.functions = options.functions || {};
39 options.use = options.use || [];
40 options.use = Array.isArray(options.use) ? options.use : [options.use];
41 options.imports = [join(__dirname, 'functions/index.styl')].concat(options.imports || []);
42 options.paths = options.paths || [];
43 options.filename = options.filename || 'stylus';
44 options.Evaluator = options.Evaluator || Evaluator;
45 this.options = options;
46 this.str = str;
47 this.events = events;
48};
49
50/**
51 * Inherit from `EventEmitter.prototype`.
52 */
53
54Renderer.prototype.__proto__ = EventEmitter.prototype;
55
56/**
57 * Expose events explicitly.
58 */
59
60module.exports.events = events;
61
62/**
63 * Parse and evaluate AST, then callback `fn(err, css, js)`.
64 *
65 * @param {Function} fn
66 * @api public
67 */
68
69Renderer.prototype.render = function(fn){
70 var parser = this.parser = new Parser(this.str, this.options);
71
72 // use plugin(s)
73 for (var i = 0, len = this.options.use.length; i < len; i++) {
74 this.use(this.options.use[i]);
75 }
76
77 try {
78 nodes.filename = this.options.filename;
79 // parse
80 var ast = parser.parse();
81
82 // evaluate
83 this.evaluator = new this.options.Evaluator(ast, this.options);
84 this.nodes = nodes;
85 this.evaluator.renderer = this;
86 ast = this.evaluator.evaluate();
87
88 // normalize
89 var normalizer = new Normalizer(ast, this.options);
90 ast = normalizer.normalize();
91
92 // compile
93 var compiler = this.options.sourcemap
94 ? new (require('./visitor/sourcemapper'))(ast, this.options)
95 : new (require('./visitor/compiler'))(ast, this.options)
96 , css = compiler.compile();
97
98 // expose sourcemap
99 if (this.options.sourcemap) this.sourcemap = compiler.map.toJSON();
100 } catch (err) {
101 var options = {};
102 options.input = err.input || this.str;
103 options.filename = err.filename || this.options.filename;
104 options.lineno = err.lineno || parser.lexer.lineno;
105 options.column = err.column || parser.lexer.column;
106 if (!fn) throw utils.formatException(err, options);
107 return fn(utils.formatException(err, options));
108 }
109
110 // fire `end` event
111 var listeners = this.listeners('end');
112 if (fn) listeners.push(fn);
113 for (var i = 0, len = listeners.length; i < len; i++) {
114 var ret = listeners[i](null, css);
115 if (ret) css = ret;
116 }
117 if (!fn) return css;
118};
119
120/**
121 * Get dependencies of the compiled file.
122 *
123 * @param {String} [filename]
124 * @return {Array}
125 * @api public
126 */
127
128Renderer.prototype.deps = function(filename){
129 var opts = utils.merge({ cache: false }, this.options);
130 if (filename) opts.filename = filename;
131
132 var DepsResolver = require('./visitor/deps-resolver')
133 , parser = new Parser(this.str, opts);
134
135 try {
136 nodes.filename = opts.filename;
137 // parse
138 var ast = parser.parse()
139 , resolver = new DepsResolver(ast, opts);
140
141 // resolve dependencies
142 return resolver.resolve();
143 } catch (err) {
144 var options = {};
145 options.input = err.input || this.str;
146 options.filename = err.filename || opts.filename;
147 options.lineno = err.lineno || parser.lexer.lineno;
148 options.column = err.column || parser.lexer.column;
149 throw utils.formatException(err, options);
150 }
151};
152
153/**
154 * Set option `key` to `val`.
155 *
156 * @param {String} key
157 * @param {Mixed} val
158 * @return {Renderer} for chaining
159 * @api public
160 */
161
162Renderer.prototype.set = function(key, val){
163 this.options[key] = val;
164 return this;
165};
166
167/**
168 * Get option `key`.
169 *
170 * @param {String} key
171 * @return {Mixed} val
172 * @api public
173 */
174
175Renderer.prototype.get = function(key){
176 return this.options[key];
177};
178
179/**
180 * Include the given `path` to the lookup paths array.
181 *
182 * @param {String} path
183 * @return {Renderer} for chaining
184 * @api public
185 */
186
187Renderer.prototype.include = function(path){
188 this.options.paths.push(path);
189 return this;
190};
191
192/**
193 * Use the given `fn`.
194 *
195 * This allows for plugins to alter the renderer in
196 * any way they wish, exposing paths etc.
197 *
198 * @param {Function}
199 * @return {Renderer} for chaining
200 * @api public
201 */
202
203Renderer.prototype.use = function(fn){
204 fn.call(this, this);
205 return this;
206};
207
208/**
209 * Define function or global var with the given `name`. Optionally
210 * the function may accept full expressions, by setting `raw`
211 * to `true`.
212 *
213 * @param {String} name
214 * @param {Function|Node} fn
215 * @return {Renderer} for chaining
216 * @api public
217 */
218
219Renderer.prototype.define = function(name, fn, raw){
220 fn = utils.coerce(fn, raw);
221
222 if (fn.nodeName) {
223 this.options.globals[name] = fn;
224 return this;
225 }
226
227 // function
228 this.options.functions[name] = fn;
229 if (undefined != raw) fn.raw = raw;
230 return this;
231};
232
233/**
234 * Import the given `file`.
235 *
236 * @param {String} file
237 * @return {Renderer} for chaining
238 * @api public
239 */
240
241Renderer.prototype.import = function(file){
242 this.options.imports.push(file);
243 return this;
244};
245
246