index.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. 'use strict';
  2. /*!
  3. * Jade
  4. * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
  5. * MIT Licensed
  6. */
  7. /**
  8. * Module dependencies.
  9. */
  10. var Parser = require('./parser')
  11. , Lexer = require('./lexer')
  12. , Compiler = require('./compiler')
  13. , runtime = require('./runtime')
  14. , addWith = require('with')
  15. , fs = require('fs')
  16. , utils = require('./utils');
  17. /**
  18. * Expose self closing tags.
  19. */
  20. // FIXME: either stop exporting selfClosing in v2 or export the new object
  21. // form
  22. exports.selfClosing = Object.keys(require('void-elements'));
  23. /**
  24. * Default supported doctypes.
  25. */
  26. exports.doctypes = require('./doctypes');
  27. /**
  28. * Text filters.
  29. */
  30. exports.filters = require('./filters');
  31. /**
  32. * Utilities.
  33. */
  34. exports.utils = utils;
  35. /**
  36. * Expose `Compiler`.
  37. */
  38. exports.Compiler = Compiler;
  39. /**
  40. * Expose `Parser`.
  41. */
  42. exports.Parser = Parser;
  43. /**
  44. * Expose `Lexer`.
  45. */
  46. exports.Lexer = Lexer;
  47. /**
  48. * Nodes.
  49. */
  50. exports.nodes = require('./nodes');
  51. /**
  52. * Jade runtime helpers.
  53. */
  54. exports.runtime = runtime;
  55. /**
  56. * Template function cache.
  57. */
  58. exports.cache = {};
  59. /**
  60. * Parse the given `str` of jade and return a function body.
  61. *
  62. * @param {String} str
  63. * @param {Object} options
  64. * @return {Object}
  65. * @api private
  66. */
  67. function parse(str, options){
  68. if (options.lexer) {
  69. console.warn('Using `lexer` as a local in render() is deprecated and '
  70. + 'will be interpreted as an option in Jade 2.0.0');
  71. }
  72. // Parse
  73. var parser = new (options.parser || Parser)(str, options.filename, options);
  74. var tokens;
  75. try {
  76. // Parse
  77. tokens = parser.parse();
  78. } catch (err) {
  79. parser = parser.context();
  80. runtime.rethrow(err, parser.filename, parser.lexer.lineno, parser.input);
  81. }
  82. // Compile
  83. var compiler = new (options.compiler || Compiler)(tokens, options);
  84. var js;
  85. try {
  86. js = compiler.compile();
  87. } catch (err) {
  88. if (err.line && (err.filename || !options.filename)) {
  89. runtime.rethrow(err, err.filename, err.line, parser.input);
  90. } else {
  91. if (err instanceof Error) {
  92. err.message += '\n\nPlease report this entire error and stack trace to https://github.com/jadejs/jade/issues';
  93. }
  94. throw err;
  95. }
  96. }
  97. // Debug compiler
  98. if (options.debug) {
  99. console.error('\nCompiled Function:\n\n\u001b[90m%s\u001b[0m', js.replace(/^/gm, ' '));
  100. }
  101. var globals = [];
  102. if (options.globals) {
  103. globals = options.globals.slice();
  104. }
  105. globals.push('jade');
  106. globals.push('jade_mixins');
  107. globals.push('jade_interp');
  108. globals.push('jade_debug');
  109. globals.push('buf');
  110. var body = ''
  111. + 'var buf = [];\n'
  112. + 'var jade_mixins = {};\n'
  113. + 'var jade_interp;\n'
  114. + (options.self
  115. ? 'var self = locals || {};\n' + js
  116. : addWith('locals || {}', '\n' + js, globals)) + ';'
  117. + 'return buf.join("");';
  118. return {body: body, dependencies: parser.dependencies};
  119. }
  120. /**
  121. * Get the template from a string or a file, either compiled on-the-fly or
  122. * read from cache (if enabled), and cache the template if needed.
  123. *
  124. * If `str` is not set, the file specified in `options.filename` will be read.
  125. *
  126. * If `options.cache` is true, this function reads the file from
  127. * `options.filename` so it must be set prior to calling this function.
  128. *
  129. * @param {Object} options
  130. * @param {String=} str
  131. * @return {Function}
  132. * @api private
  133. */
  134. function handleTemplateCache (options, str) {
  135. var key = options.filename;
  136. if (options.cache && exports.cache[key]) {
  137. return exports.cache[key];
  138. } else {
  139. if (str === undefined) str = fs.readFileSync(options.filename, 'utf8');
  140. var templ = exports.compile(str, options);
  141. if (options.cache) exports.cache[key] = templ;
  142. return templ;
  143. }
  144. }
  145. /**
  146. * Compile a `Function` representation of the given jade `str`.
  147. *
  148. * Options:
  149. *
  150. * - `compileDebug` when `false` debugging code is stripped from the compiled
  151. template, when it is explicitly `true`, the source code is included in
  152. the compiled template for better accuracy.
  153. * - `filename` used to improve errors when `compileDebug` is not `false` and to resolve imports/extends
  154. *
  155. * @param {String} str
  156. * @param {Options} options
  157. * @return {Function}
  158. * @api public
  159. */
  160. exports.compile = function(str, options){
  161. var options = options || {}
  162. , filename = options.filename
  163. ? utils.stringify(options.filename)
  164. : 'undefined'
  165. , fn;
  166. str = String(str);
  167. var parsed = parse(str, options);
  168. if (options.compileDebug !== false) {
  169. fn = [
  170. 'var jade_debug = [ new jade.DebugItem( 1, ' + filename + ' ) ];'
  171. , 'try {'
  172. , parsed.body
  173. , '} catch (err) {'
  174. , ' jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno' + (options.compileDebug === true ? ',' + utils.stringify(str) : '') + ');'
  175. , '}'
  176. ].join('\n');
  177. } else {
  178. fn = parsed.body;
  179. }
  180. fn = new Function('locals, jade', fn)
  181. var res = function(locals){ return fn(locals, Object.create(runtime)) };
  182. if (options.client) {
  183. res.toString = function () {
  184. var err = new Error('The `client` option is deprecated, use the `jade.compileClient` method instead');
  185. err.name = 'Warning';
  186. console.error(err.stack || /* istanbul ignore next */ err.message);
  187. return exports.compileClient(str, options);
  188. };
  189. }
  190. res.dependencies = parsed.dependencies;
  191. return res;
  192. };
  193. /**
  194. * Compile a JavaScript source representation of the given jade `str`.
  195. *
  196. * Options:
  197. *
  198. * - `compileDebug` When it is `true`, the source code is included in
  199. * the compiled template for better error messages.
  200. * - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends
  201. * - `name` the name of the resulting function (defaults to "template")
  202. *
  203. * @param {String} str
  204. * @param {Options} options
  205. * @return {Object}
  206. * @api public
  207. */
  208. exports.compileClientWithDependenciesTracked = function(str, options){
  209. var options = options || {};
  210. var name = options.name || 'template';
  211. var filename = options.filename ? utils.stringify(options.filename) : 'undefined';
  212. var fn;
  213. str = String(str);
  214. options.compileDebug = options.compileDebug ? true : false;
  215. var parsed = parse(str, options);
  216. if (options.compileDebug) {
  217. fn = [
  218. 'var jade_debug = [ new jade.DebugItem( 1, ' + filename + ' ) ];'
  219. , 'try {'
  220. , parsed.body
  221. , '} catch (err) {'
  222. , ' jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno, ' + utils.stringify(str) + ');'
  223. , '}'
  224. ].join('\n');
  225. } else {
  226. fn = parsed.body;
  227. }
  228. return {body: 'function ' + name + '(locals) {\n' + fn + '\n}', dependencies: parsed.dependencies};
  229. };
  230. /**
  231. * Compile a JavaScript source representation of the given jade `str`.
  232. *
  233. * Options:
  234. *
  235. * - `compileDebug` When it is `true`, the source code is included in
  236. * the compiled template for better error messages.
  237. * - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends
  238. * - `name` the name of the resulting function (defaults to "template")
  239. *
  240. * @param {String} str
  241. * @param {Options} options
  242. * @return {String}
  243. * @api public
  244. */
  245. exports.compileClient = function (str, options) {
  246. return exports.compileClientWithDependenciesTracked(str, options).body;
  247. };
  248. /**
  249. * Compile a `Function` representation of the given jade file.
  250. *
  251. * Options:
  252. *
  253. * - `compileDebug` when `false` debugging code is stripped from the compiled
  254. template, when it is explicitly `true`, the source code is included in
  255. the compiled template for better accuracy.
  256. *
  257. * @param {String} path
  258. * @param {Options} options
  259. * @return {Function}
  260. * @api public
  261. */
  262. exports.compileFile = function (path, options) {
  263. options = options || {};
  264. options.filename = path;
  265. return handleTemplateCache(options);
  266. };
  267. /**
  268. * Render the given `str` of jade.
  269. *
  270. * Options:
  271. *
  272. * - `cache` enable template caching
  273. * - `filename` filename required for `include` / `extends` and caching
  274. *
  275. * @param {String} str
  276. * @param {Object|Function} options or fn
  277. * @param {Function|undefined} fn
  278. * @returns {String}
  279. * @api public
  280. */
  281. exports.render = function(str, options, fn){
  282. // support callback API
  283. if ('function' == typeof options) {
  284. fn = options, options = undefined;
  285. }
  286. if (typeof fn === 'function') {
  287. var res
  288. try {
  289. res = exports.render(str, options);
  290. } catch (ex) {
  291. return fn(ex);
  292. }
  293. return fn(null, res);
  294. }
  295. options = options || {};
  296. // cache requires .filename
  297. if (options.cache && !options.filename) {
  298. throw new Error('the "filename" option is required for caching');
  299. }
  300. return handleTemplateCache(options, str)(options);
  301. };
  302. /**
  303. * Render a Jade file at the given `path`.
  304. *
  305. * @param {String} path
  306. * @param {Object|Function} options or callback
  307. * @param {Function|undefined} fn
  308. * @returns {String}
  309. * @api public
  310. */
  311. exports.renderFile = function(path, options, fn){
  312. // support callback API
  313. if ('function' == typeof options) {
  314. fn = options, options = undefined;
  315. }
  316. if (typeof fn === 'function') {
  317. var res
  318. try {
  319. res = exports.renderFile(path, options);
  320. } catch (ex) {
  321. return fn(ex);
  322. }
  323. return fn(null, res);
  324. }
  325. options = options || {};
  326. options.filename = path;
  327. return handleTemplateCache(options)(options);
  328. };
  329. /**
  330. * Compile a Jade file at the given `path` for use on the client.
  331. *
  332. * @param {String} path
  333. * @param {Object} options
  334. * @returns {String}
  335. * @api public
  336. */
  337. exports.compileFileClient = function(path, options){
  338. var key = path + ':client';
  339. options = options || {};
  340. options.filename = path;
  341. if (options.cache && exports.cache[key]) {
  342. return exports.cache[key];
  343. }
  344. var str = fs.readFileSync(options.filename, 'utf8');
  345. var out = exports.compileClient(str, options);
  346. if (options.cache) exports.cache[key] = out;
  347. return out;
  348. };
  349. /**
  350. * Express support.
  351. */
  352. exports.__express = function(path, options, fn) {
  353. if(options.compileDebug == undefined && process.env.NODE_ENV === 'production') {
  354. options.compileDebug = false;
  355. }
  356. exports.renderFile(path, options, fn);
  357. }