renderer.js 5.4 KB

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