UNPKG

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