Skip to content

Commit

Permalink
Merge pull request #613 from sethkinast/templates++
Browse files Browse the repository at this point in the history
Refactor template compilation

* `dust.render` and `dust.stream` now accept a compiled template function in addition to a template name.
* `dust.compile` no longer requires a template name, and will compile an anonymous template without one (so `--name` is no longer required for dustc either)
* `dust.load` is removed from the public API
* `dust.renderSource` is moved to the compiler, so it's only included in dust-full now (Fixes #412)
* `dust.compileFn` is moved to the compiler, so it's only included in dust-full now
* add `dust.isTemplateFn`
* add `dust.config.cache = true`, set to `false` to disable caching and load templates again every time (Fixes #451)
* add `dust.config.cjs = false`, set to `true` to compile templates as CommonJS modules
* add `--cjs` flag to `dustc`
* Move a bunch of exposed compiler stuff under `dust.compiler` (but leave it exposed until 2.8)
  • Loading branch information
prashn64 committed Apr 17, 2015
2 parents 82dee90 + 815cfc9 commit 7dd81ee
Show file tree
Hide file tree
Showing 11 changed files with 302 additions and 88 deletions.
15 changes: 13 additions & 2 deletions Gruntfile.js
Expand Up @@ -181,6 +181,17 @@ module.exports = function(grunt) {
}
},
jasmine_nodejs: {
cjs: {
specs: ['test/jasmine-test/spec/cjsSpec.js'],
options: {
reporters: {
console: {
colors: false,
verbose: false
}
}
}
},
dustc: {
specs: ['test/jasmine-test/spec/cli/*'],
options: {
Expand Down Expand Up @@ -286,11 +297,11 @@ module.exports = function(grunt) {
grunt.registerTask('build', ['clean:build', 'shell:buildParser', 'buildLib', 'uglify']);

//test tasks
grunt.registerTask('testNode', ['shell:oldTests']);
grunt.registerTask('testNode', ['jasmine_nodejs:cjs', 'shell:oldTests']);
grunt.registerTask('testRhino', ['build', 'shell:testRhino']);
grunt.registerTask('testPhantom', ['build', 'jasmine:testProd']);
grunt.registerTask('testCli', ['build', 'jasmine_nodejs:dustc']);
grunt.registerTask('test', ['build', 'jasmine:testProd', 'jasmine_nodejs:dustc', 'shell:oldTests', 'shell:testRhino', 'jasmine:coverage']);
grunt.registerTask('test', ['build', 'jasmine:testProd', 'jasmine_nodejs:dustc', 'jasmine_nodejs:cjs', 'shell:oldTests', 'shell:testRhino', 'jasmine:coverage']);

//task for debugging in browser
grunt.registerTask('dev', ['build', 'jasmine:testDev:build', 'connect:testServer','log:testClient', 'watch:lib']);
Expand Down
12 changes: 11 additions & 1 deletion bin/dustc
Expand Up @@ -22,6 +22,7 @@ cli.parse({
pwd: [false, 'generate template names starting from this directory', 'string'],
whitespace: ['w', 'preserve whitespace in templates', ],
amd: ['a', 'register templates as AMD modules' ],
cjs: [false, 'register templates as CommonJS modules (automatically turns on --split)' ],
watch: [false, 'watch files for changes and recompile' ]
});

Expand All @@ -30,7 +31,7 @@ cli.parse({
*/
if (!cli.argc) {
if (!cli.options.name) {
cli.getUsage();
cli.info('No template name provided. The compiled template will be anonymous.');
}
cli.info('No files to compile. Write template code and use ^D to exit');
cli.withStdin(function(data) {
Expand Down Expand Up @@ -59,6 +60,14 @@ function handle() {
path.basename(inputFile, path.extname(inputFile))),
compiledData;

// If CommonJS is turned on, you have to compile one per file.
if (cli.options.cjs && filesToProcess.length > 1) {
if(!cli.options.split) {
cli.info('CommonJS modules are limited to one per file. Enabling --split');
}
cli.options.split = true;
}

// Use the template's path as the output path if split-files is turned on
if (cli.options.split) {
outputFile = templateName + '.js';
Expand Down Expand Up @@ -131,6 +140,7 @@ function compile(data, name) {

dust.config.whitespace = (cli.options.whitespace === true);
dust.config.amd = (cli.options.amd === true);
dust.config.cjs = (cli.options.cjs === true);

try {
compiled = dust.compile(data, name);
Expand Down
4 changes: 2 additions & 2 deletions index.js
Expand Up @@ -6,8 +6,8 @@ var vm = require('vm'),

// use Node equivalents for some Dust methods
var context = vm.createContext({dust: dust});
dust.loadSource = function(source, path) {
return vm.runInContext(source, context, path);
dust.loadSource = function(source) {
return vm.runInContext(source, context);
};

dust.nextTick = process.nextTick;
Expand Down
63 changes: 47 additions & 16 deletions lib/compiler.js
Expand Up @@ -22,9 +22,6 @@
// for templates that are compiled as a callable (compileFn)
//
// for the common case (using compile and render) a name is required so that templates will be cached by name and rendered later, by name.
if (!name && name !== null) {
throw new Error('Template name parameter cannot be undefined when calling dust.compile');
}

try {
var ast = filterAST(parse(source));
Expand Down Expand Up @@ -77,7 +74,7 @@
};

compiler.pragmas = {
esc: function(compiler, context, bodies, params) {
esc: function(compiler, context, bodies) {
var old = compiler.auto,
out;
if (!context) {
Expand Down Expand Up @@ -164,18 +161,30 @@
auto: 'h'
},
escapedName = dust.escapeJs(name),
body_0 = 'function(dust){dust.register(' +
(name ? '"' + escapedName + '"' : 'null') + ',' +
compiler.compileNode(context, ast) +
');' +
compileBlocks(context) +
compileBodies(context) +
'return body_0;}';
AMDName = name? '"' + escapedName + '",' : '',
compiled = 'function(dust){',
entry = compiler.compileNode(context, ast),
iife;

if(name) {
compiled += 'dust.register("' + escapedName + '",' + entry + ');';
}

compiled += compileBlocks(context) +
compileBodies(context) +
'return ' + entry + '}';

iife = '(' + compiled + '(dust));';

if(dust.config.amd) {
return 'define("' + escapedName + '",["dust.core"],' + body_0 + ');';
return 'define(' + AMDName + '["dust.core"],' + compiled + ');';
} else if(dust.config.cjs) {
return 'module.exports=function(dust){' +
'var tmpl=' + iife +
'var f=' + loaderFor().toString() + ';' +
'f.template=tmpl;return f}';
} else {
return '(' + body_0 + ')(dust);';
return iife;
}
}

Expand Down Expand Up @@ -270,13 +279,13 @@

'+': function(context, node) {
if (typeof(node[1].text) === 'undefined' && typeof(node[4]) === 'undefined'){
return '.block(ctx.getBlock(' +
return '.b(ctx.getBlock(' +
compiler.compileNode(context, node[1]) +
',chk, ctx),' + compiler.compileNode(context, node[2]) + ', {},' +
compiler.compileNode(context, node[3]) +
')';
} else {
return '.block(ctx.getBlock(' +
return '.b(ctx.getBlock(' +
escape(node[1].text) +
'),' + compiler.compileNode(context, node[2]) + ',' +
compiler.compileNode(context, node[4]) + ',' +
Expand Down Expand Up @@ -427,8 +436,30 @@
function(str) { return '"' + escapeToJsSafeString(str) + '"';} :
JSON.stringify;

function renderSource(source, context, callback) {
var tmpl = dust.loadSource(dust.compile(source));
return loaderFor(tmpl)(context, callback);
}

function compileFn(source, name) {
var tmpl = dust.loadSource(dust.compile(source, name));
return loaderFor(tmpl);
}

function loaderFor(tmpl) {
return function load(ctx, cb) {
var fn = cb ? 'render' : 'stream';
return dust[fn](tmpl, ctx, cb);
};
}

// expose compiler methods
dust.compile = compiler.compile;
dust.compiler = compiler;
dust.compile = dust.compiler.compile;
dust.renderSource = renderSource;
dust.compileFn = compileFn;

// DEPRECATED legacy names. Removed in 2.8.0
dust.filterNode = compiler.filterNode;
dust.optimizers = compiler.optimizers;
dust.pragmas = compiler.pragmas;
Expand Down
90 changes: 45 additions & 45 deletions lib/dust.js
Expand Up @@ -16,7 +16,9 @@

dust.config = {
whitespace: false,
amd: false
amd: false,
cjs: false,
cache: true
};

// Directive aliases to minify code
Expand Down Expand Up @@ -84,79 +86,72 @@
if (!name) {
return;
}
tmpl.templateName = name;
dust.cache[name] = tmpl;
};

dust.render = function(name, context, callback) {
dust.render = function(nameOrTemplate, context, callback) {
var chunk = new Stub(callback).head;
try {
dust.load(name, chunk, Context.wrap(context, name)).end();
load(nameOrTemplate, chunk, context).end();
} catch (err) {
chunk.setError(err);
}
};

dust.stream = function(name, context) {
dust.stream = function(nameOrTemplate, context) {
var stream = new Stream(),
chunk = stream.head;
dust.nextTick(function() {
try {
dust.load(name, stream.head, Context.wrap(context, name)).end();
load(nameOrTemplate, chunk, context).end();
} catch (err) {
chunk.setError(err);
}
});
return stream;
};

dust.renderSource = function(source, context, callback) {
return dust.compileFn(source)(context, callback);
};
function load(nameOrTemplate, chunk, context) {
if(!nameOrTemplate) {
return chunk.setError(new Error('No template or template name provided to render'));
}

/**
* Compile a template to an invokable function.
* If `name` is provided, also registers the template under `name`.
* @param source {String} template source
* @param [name] {String} template name
* @return {Function} has the signature `fn(context, cb)`
*/
dust.compileFn = function(source, name) {
name = name || null;
var tmpl = dust.loadSource(dust.compile(source, name));
return function(context, callback) {
var master = callback ? new Stub(callback) : new Stream();
dust.nextTick(function() {
if(typeof tmpl === 'function') {
tmpl(master.head, Context.wrap(context, name)).end();
} else {
dust.log(new Error('Template `' + name + '` could not be loaded'), ERROR);
}
});
return master;
};
};
if(!dust.config.cache) {
dust.cache = {};
}

var tmpl;
if(typeof nameOrTemplate === 'function' && nameOrTemplate.template) {
// Sugar away CommonJS module templates
tmpl = nameOrTemplate.template;
} else if(dust.isTemplateFn(nameOrTemplate)) {
// Template functions passed directly
tmpl = nameOrTemplate;
} else {
// Load a template with this name from cache
tmpl = dust.cache[nameOrTemplate];
}

dust.load = function(name, chunk, context) {
var tmpl = dust.cache[name];
if (tmpl) {
return tmpl(chunk, context);
return tmpl(chunk, Context.wrap(context, tmpl.templateName));
} else {
if (dust.onLoad) {
return chunk.map(function(chunk) {
dust.onLoad(name, function(err, src) {
dust.onLoad(nameOrTemplate, function(err, src) {
if (err) {
return chunk.setError(err);
}
if (!dust.cache[name]) {
dust.loadSource(dust.compile(src, name));
if (!dust.cache[nameOrTemplate]) {
dust.loadSource(dust.compile(src, nameOrTemplate));
}
dust.cache[name](chunk, context).end();
dust.cache[nameOrTemplate](chunk, Context.wrap(context, nameOrTemplate)).end();
});
});
}
return chunk.setError(new Error('Template Not Found: ' + name));
return chunk.setError(new Error('Template Not Found: ' + nameOrTemplate));
}
};
}

dust.loadSource = function(source) {
/*jshint evil:true*/
Expand All @@ -173,9 +168,9 @@

dust.nextTick = (function() {
return function(callback) {
setTimeout(callback,0);
setTimeout(callback, 0);
};
} )();
})();

/**
* Dust has its own rules for what is "empty"-- which is not the same as falsy.
Expand Down Expand Up @@ -210,6 +205,11 @@
return true;
};

dust.isTemplateFn = function(elem) {
return typeof elem === 'function' &&
elem.__dustBody;
};

/**
* Decide somewhat-naively if something is a Thenable.
* @param elem {*} object to inspect
Expand Down Expand Up @@ -722,7 +722,7 @@
chunk = this,
i, len;

if (typeof elem === 'function' && !elem.__dustBody) {
if (typeof elem === 'function' && !dust.isTemplateFn(elem)) {
try {
elem = elem.apply(context.current(), [this, context, bodies, params]);
} catch(err) {
Expand Down Expand Up @@ -843,16 +843,16 @@
.push(head);
}

if (elem.__dustBody) {
if (dust.isTemplateFn(elem)) {
// The eventual result of evaluating `elem` is a partial name
// Load the partial after getting its name and end the async chunk
return this.capture(elem, context, function(name, chunk) {
context.templateName = name;
dust.load(name, chunk, context).end();
load(name, chunk, context).end();
});
} else {
context.templateName = elem;
return dust.load(elem, this, context);
return load(elem, this, context);
}
};

Expand Down

0 comments on commit 7dd81ee

Please sign in to comment.