UNPKG

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