You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
419 lines
9.9 KiB
JavaScript
419 lines
9.9 KiB
JavaScript
6 years ago
|
'use strict';
|
||
|
|
||
|
/*!
|
||
|
* Jade
|
||
|
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
||
|
* MIT Licensed
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Module dependencies.
|
||
|
*/
|
||
|
|
||
|
var Parser = require('./parser')
|
||
|
, Lexer = require('./lexer')
|
||
|
, Compiler = require('./compiler')
|
||
|
, runtime = require('./runtime')
|
||
|
, addWith = require('with')
|
||
|
, fs = require('fs')
|
||
|
, utils = require('./utils');
|
||
|
|
||
|
/**
|
||
|
* Expose self closing tags.
|
||
|
*/
|
||
|
|
||
|
// FIXME: either stop exporting selfClosing in v2 or export the new object
|
||
|
// form
|
||
|
exports.selfClosing = Object.keys(require('void-elements'));
|
||
|
|
||
|
/**
|
||
|
* Default supported doctypes.
|
||
|
*/
|
||
|
|
||
|
exports.doctypes = require('./doctypes');
|
||
|
|
||
|
/**
|
||
|
* Text filters.
|
||
|
*/
|
||
|
|
||
|
exports.filters = require('./filters');
|
||
|
|
||
|
/**
|
||
|
* Utilities.
|
||
|
*/
|
||
|
|
||
|
exports.utils = utils;
|
||
|
|
||
|
/**
|
||
|
* Expose `Compiler`.
|
||
|
*/
|
||
|
|
||
|
exports.Compiler = Compiler;
|
||
|
|
||
|
/**
|
||
|
* Expose `Parser`.
|
||
|
*/
|
||
|
|
||
|
exports.Parser = Parser;
|
||
|
|
||
|
/**
|
||
|
* Expose `Lexer`.
|
||
|
*/
|
||
|
|
||
|
exports.Lexer = Lexer;
|
||
|
|
||
|
/**
|
||
|
* Nodes.
|
||
|
*/
|
||
|
|
||
|
exports.nodes = require('./nodes');
|
||
|
|
||
|
/**
|
||
|
* Jade runtime helpers.
|
||
|
*/
|
||
|
|
||
|
exports.runtime = runtime;
|
||
|
|
||
|
/**
|
||
|
* Template function cache.
|
||
|
*/
|
||
|
|
||
|
exports.cache = {};
|
||
|
|
||
|
/**
|
||
|
* Parse the given `str` of jade and return a function body.
|
||
|
*
|
||
|
* @param {String} str
|
||
|
* @param {Object} options
|
||
|
* @return {Object}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function parse(str, options){
|
||
|
|
||
|
if (options.lexer) {
|
||
|
console.warn('Using `lexer` as a local in render() is deprecated and '
|
||
|
+ 'will be interpreted as an option in Jade 2.0.0');
|
||
|
}
|
||
|
|
||
|
// Parse
|
||
|
var parser = new (options.parser || Parser)(str, options.filename, options);
|
||
|
var tokens;
|
||
|
try {
|
||
|
// Parse
|
||
|
tokens = parser.parse();
|
||
|
} catch (err) {
|
||
|
parser = parser.context();
|
||
|
runtime.rethrow(err, parser.filename, parser.lexer.lineno, parser.input);
|
||
|
}
|
||
|
|
||
|
// Compile
|
||
|
var compiler = new (options.compiler || Compiler)(tokens, options);
|
||
|
var js;
|
||
|
try {
|
||
|
js = compiler.compile();
|
||
|
} catch (err) {
|
||
|
if (err.line && (err.filename || !options.filename)) {
|
||
|
runtime.rethrow(err, err.filename, err.line, parser.input);
|
||
|
} else {
|
||
|
if (err instanceof Error) {
|
||
|
err.message += '\n\nPlease report this entire error and stack trace to https://github.com/jadejs/jade/issues';
|
||
|
}
|
||
|
throw err;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Debug compiler
|
||
|
if (options.debug) {
|
||
|
console.error('\nCompiled Function:\n\n\u001b[90m%s\u001b[0m', js.replace(/^/gm, ' '));
|
||
|
}
|
||
|
|
||
|
var globals = [];
|
||
|
|
||
|
if (options.globals) {
|
||
|
globals = options.globals.slice();
|
||
|
}
|
||
|
|
||
|
globals.push('jade');
|
||
|
globals.push('jade_mixins');
|
||
|
globals.push('jade_interp');
|
||
|
globals.push('jade_debug');
|
||
|
globals.push('buf');
|
||
|
|
||
|
var body = ''
|
||
|
+ 'var buf = [];\n'
|
||
|
+ 'var jade_mixins = {};\n'
|
||
|
+ 'var jade_interp;\n'
|
||
|
+ (options.self
|
||
|
? 'var self = locals || {};\n' + js
|
||
|
: addWith('locals || {}', '\n' + js, globals)) + ';'
|
||
|
+ 'return buf.join("");';
|
||
|
return {body: body, dependencies: parser.dependencies};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the template from a string or a file, either compiled on-the-fly or
|
||
|
* read from cache (if enabled), and cache the template if needed.
|
||
|
*
|
||
|
* If `str` is not set, the file specified in `options.filename` will be read.
|
||
|
*
|
||
|
* If `options.cache` is true, this function reads the file from
|
||
|
* `options.filename` so it must be set prior to calling this function.
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @param {String=} str
|
||
|
* @return {Function}
|
||
|
* @api private
|
||
|
*/
|
||
|
function handleTemplateCache (options, str) {
|
||
|
var key = options.filename;
|
||
|
if (options.cache && exports.cache[key]) {
|
||
|
return exports.cache[key];
|
||
|
} else {
|
||
|
if (str === undefined) str = fs.readFileSync(options.filename, 'utf8');
|
||
|
var templ = exports.compile(str, options);
|
||
|
if (options.cache) exports.cache[key] = templ;
|
||
|
return templ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Compile a `Function` representation of the given jade `str`.
|
||
|
*
|
||
|
* Options:
|
||
|
*
|
||
|
* - `compileDebug` when `false` debugging code is stripped from the compiled
|
||
|
template, when it is explicitly `true`, the source code is included in
|
||
|
the compiled template for better accuracy.
|
||
|
* - `filename` used to improve errors when `compileDebug` is not `false` and to resolve imports/extends
|
||
|
*
|
||
|
* @param {String} str
|
||
|
* @param {Options} options
|
||
|
* @return {Function}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
exports.compile = function(str, options){
|
||
|
var options = options || {}
|
||
|
, filename = options.filename
|
||
|
? utils.stringify(options.filename)
|
||
|
: 'undefined'
|
||
|
, fn;
|
||
|
|
||
|
str = String(str);
|
||
|
|
||
|
var parsed = parse(str, options);
|
||
|
if (options.compileDebug !== false) {
|
||
|
fn = [
|
||
|
'var jade_debug = [ new jade.DebugItem( 1, ' + filename + ' ) ];'
|
||
|
, 'try {'
|
||
|
, parsed.body
|
||
|
, '} catch (err) {'
|
||
|
, ' jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno' + (options.compileDebug === true ? ',' + utils.stringify(str) : '') + ');'
|
||
|
, '}'
|
||
|
].join('\n');
|
||
|
} else {
|
||
|
fn = parsed.body;
|
||
|
}
|
||
|
fn = new Function('locals, jade', fn)
|
||
|
var res = function(locals){ return fn(locals, Object.create(runtime)) };
|
||
|
if (options.client) {
|
||
|
res.toString = function () {
|
||
|
var err = new Error('The `client` option is deprecated, use the `jade.compileClient` method instead');
|
||
|
err.name = 'Warning';
|
||
|
console.error(err.stack || /* istanbul ignore next */ err.message);
|
||
|
return exports.compileClient(str, options);
|
||
|
};
|
||
|
}
|
||
|
res.dependencies = parsed.dependencies;
|
||
|
return res;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Compile a JavaScript source representation of the given jade `str`.
|
||
|
*
|
||
|
* Options:
|
||
|
*
|
||
|
* - `compileDebug` When it is `true`, the source code is included in
|
||
|
* the compiled template for better error messages.
|
||
|
* - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends
|
||
|
* - `name` the name of the resulting function (defaults to "template")
|
||
|
*
|
||
|
* @param {String} str
|
||
|
* @param {Options} options
|
||
|
* @return {Object}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
exports.compileClientWithDependenciesTracked = function(str, options){
|
||
|
var options = options || {};
|
||
|
var name = options.name || 'template';
|
||
|
var filename = options.filename ? utils.stringify(options.filename) : 'undefined';
|
||
|
var fn;
|
||
|
|
||
|
str = String(str);
|
||
|
options.compileDebug = options.compileDebug ? true : false;
|
||
|
var parsed = parse(str, options);
|
||
|
if (options.compileDebug) {
|
||
|
fn = [
|
||
|
'var jade_debug = [ new jade.DebugItem( 1, ' + filename + ' ) ];'
|
||
|
, 'try {'
|
||
|
, parsed.body
|
||
|
, '} catch (err) {'
|
||
|
, ' jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno, ' + utils.stringify(str) + ');'
|
||
|
, '}'
|
||
|
].join('\n');
|
||
|
} else {
|
||
|
fn = parsed.body;
|
||
|
}
|
||
|
|
||
|
return {body: 'function ' + name + '(locals) {\n' + fn + '\n}', dependencies: parsed.dependencies};
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Compile a JavaScript source representation of the given jade `str`.
|
||
|
*
|
||
|
* Options:
|
||
|
*
|
||
|
* - `compileDebug` When it is `true`, the source code is included in
|
||
|
* the compiled template for better error messages.
|
||
|
* - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends
|
||
|
* - `name` the name of the resulting function (defaults to "template")
|
||
|
*
|
||
|
* @param {String} str
|
||
|
* @param {Options} options
|
||
|
* @return {String}
|
||
|
* @api public
|
||
|
*/
|
||
|
exports.compileClient = function (str, options) {
|
||
|
return exports.compileClientWithDependenciesTracked(str, options).body;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Compile a `Function` representation of the given jade file.
|
||
|
*
|
||
|
* Options:
|
||
|
*
|
||
|
* - `compileDebug` when `false` debugging code is stripped from the compiled
|
||
|
template, when it is explicitly `true`, the source code is included in
|
||
|
the compiled template for better accuracy.
|
||
|
*
|
||
|
* @param {String} path
|
||
|
* @param {Options} options
|
||
|
* @return {Function}
|
||
|
* @api public
|
||
|
*/
|
||
|
exports.compileFile = function (path, options) {
|
||
|
options = options || {};
|
||
|
options.filename = path;
|
||
|
return handleTemplateCache(options);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Render the given `str` of jade.
|
||
|
*
|
||
|
* Options:
|
||
|
*
|
||
|
* - `cache` enable template caching
|
||
|
* - `filename` filename required for `include` / `extends` and caching
|
||
|
*
|
||
|
* @param {String} str
|
||
|
* @param {Object|Function} options or fn
|
||
|
* @param {Function|undefined} fn
|
||
|
* @returns {String}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
exports.render = function(str, options, fn){
|
||
|
// support callback API
|
||
|
if ('function' == typeof options) {
|
||
|
fn = options, options = undefined;
|
||
|
}
|
||
|
if (typeof fn === 'function') {
|
||
|
var res
|
||
|
try {
|
||
|
res = exports.render(str, options);
|
||
|
} catch (ex) {
|
||
|
return fn(ex);
|
||
|
}
|
||
|
return fn(null, res);
|
||
|
}
|
||
|
|
||
|
options = options || {};
|
||
|
|
||
|
// cache requires .filename
|
||
|
if (options.cache && !options.filename) {
|
||
|
throw new Error('the "filename" option is required for caching');
|
||
|
}
|
||
|
|
||
|
return handleTemplateCache(options, str)(options);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Render a Jade file at the given `path`.
|
||
|
*
|
||
|
* @param {String} path
|
||
|
* @param {Object|Function} options or callback
|
||
|
* @param {Function|undefined} fn
|
||
|
* @returns {String}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
exports.renderFile = function(path, options, fn){
|
||
|
// support callback API
|
||
|
if ('function' == typeof options) {
|
||
|
fn = options, options = undefined;
|
||
|
}
|
||
|
if (typeof fn === 'function') {
|
||
|
var res
|
||
|
try {
|
||
|
res = exports.renderFile(path, options);
|
||
|
} catch (ex) {
|
||
|
return fn(ex);
|
||
|
}
|
||
|
return fn(null, res);
|
||
|
}
|
||
|
|
||
|
options = options || {};
|
||
|
|
||
|
options.filename = path;
|
||
|
return handleTemplateCache(options)(options);
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Compile a Jade file at the given `path` for use on the client.
|
||
|
*
|
||
|
* @param {String} path
|
||
|
* @param {Object} options
|
||
|
* @returns {String}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
exports.compileFileClient = function(path, options){
|
||
|
var key = path + ':client';
|
||
|
options = options || {};
|
||
|
|
||
|
options.filename = path;
|
||
|
|
||
|
if (options.cache && exports.cache[key]) {
|
||
|
return exports.cache[key];
|
||
|
}
|
||
|
|
||
|
var str = fs.readFileSync(options.filename, 'utf8');
|
||
|
var out = exports.compileClient(str, options);
|
||
|
if (options.cache) exports.cache[key] = out;
|
||
|
return out;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Express support.
|
||
|
*/
|
||
|
|
||
|
exports.__express = function(path, options, fn) {
|
||
|
if(options.compileDebug == undefined && process.env.NODE_ENV === 'production') {
|
||
|
options.compileDebug = false;
|
||
|
}
|
||
|
exports.renderFile(path, options, fn);
|
||
|
}
|