Refactor precompiler API into two phase
Load templates and then parse them in a distinct operation. This will allow us to use other input sources such as stdin and strings.
This commit is contained in:
+8
-6
@@ -26,7 +26,7 @@ var optimist = require('optimist')
|
||||
'type': 'string',
|
||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||
'alias': 'handlebarPath',
|
||||
'default': ''
|
||||
'default': ''
|
||||
},
|
||||
'k': {
|
||||
'type': 'string',
|
||||
@@ -59,12 +59,12 @@ var optimist = require('optimist')
|
||||
'description': 'Template root. Base value that will be stripped from template names.',
|
||||
'alias': 'root'
|
||||
},
|
||||
'p' : {
|
||||
'p': {
|
||||
'type': 'boolean',
|
||||
'description': 'Compiling a partial template',
|
||||
'alias': 'partial'
|
||||
},
|
||||
'd' : {
|
||||
'd': {
|
||||
'type': 'boolean',
|
||||
'description': 'Include data when compiling',
|
||||
'alias': 'data'
|
||||
@@ -103,9 +103,11 @@ var argv = optimist.argv;
|
||||
argv.templates = argv._;
|
||||
delete argv._;
|
||||
|
||||
var Precompiler = require('../dist/cjs/precompiler');
|
||||
Precompiler.loadTemplates(argv);
|
||||
|
||||
if (argv.help || (!argv.templates.length && !argv.version)) {
|
||||
optimist.showHelp();
|
||||
return;
|
||||
} else {
|
||||
Precompiler.cli(argv);
|
||||
}
|
||||
|
||||
return require('../dist/cjs/precompiler').cli(argv);
|
||||
|
||||
+89
-77
@@ -5,28 +5,73 @@ import {basename} from 'path';
|
||||
import {SourceMapConsumer, SourceNode} from 'source-map';
|
||||
import uglify from 'uglify-js';
|
||||
|
||||
module.exports.loadTemplates = function(opts) {
|
||||
// Build file extension pattern
|
||||
let extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
let ret = [];
|
||||
function processTemplate(template, root) {
|
||||
let path = template,
|
||||
stat;
|
||||
try {
|
||||
stat = fs.statSync(template);
|
||||
} catch (err) {
|
||||
throw new Handlebars.Exception(`Unable to open template file "${template}"`);
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
opts.hasDirectory = true;
|
||||
|
||||
fs.readdirSync(template).map(function(file) {
|
||||
let childPath = template + '/' + file;
|
||||
|
||||
if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
|
||||
processTemplate(childPath, root || template);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
if (!root) {
|
||||
template = basename(template);
|
||||
} else if (template.indexOf(root) === 0) {
|
||||
template = template.substring(root.length + 1);
|
||||
}
|
||||
template = template.replace(extension, '');
|
||||
|
||||
ret.push({
|
||||
path: path,
|
||||
name: template,
|
||||
source: data
|
||||
});
|
||||
}
|
||||
}
|
||||
opts.templates.forEach(function(template) {
|
||||
processTemplate(template, opts.root);
|
||||
});
|
||||
opts.templates = ret;
|
||||
};
|
||||
|
||||
module.exports.cli = function(opts) {
|
||||
if (opts.version) {
|
||||
console.log(Handlebars.VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opts.templates.length) {
|
||||
if (!opts.templates.length && !opts.hasDirectory) {
|
||||
throw new Handlebars.Exception('Must define at least one template or directory.');
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
try {
|
||||
fs.statSync(template);
|
||||
} catch (err) {
|
||||
throw new Handlebars.Exception(`Unable to open template file "${template}"`);
|
||||
}
|
||||
});
|
||||
|
||||
if (opts.simple && opts.min) {
|
||||
throw new Handlebars.Exception('Unable to minimize simple output');
|
||||
}
|
||||
if (opts.simple && (opts.templates.length !== 1 || fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
if (opts.simple && (opts.templates.length !== 1 || opts.hasDirectory)) {
|
||||
throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
|
||||
}
|
||||
|
||||
@@ -41,10 +86,6 @@ module.exports.cli = function(opts) {
|
||||
}
|
||||
}
|
||||
|
||||
// Build file extension pattern
|
||||
let extension = opts.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
let output = new SourceNode();
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
@@ -63,76 +104,47 @@ module.exports.cli = function(opts) {
|
||||
}
|
||||
output.add('{};\n');
|
||||
}
|
||||
function processTemplate(template, root) {
|
||||
let path = template,
|
||||
stat = fs.statSync(path);
|
||||
if (stat.isDirectory()) {
|
||||
fs.readdirSync(template).map(function(file) {
|
||||
let childPath = template + '/' + file;
|
||||
|
||||
if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
|
||||
processTemplate(childPath, root || template);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
let options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: opts.o
|
||||
};
|
||||
|
||||
if (opts.map) {
|
||||
options.srcName = path;
|
||||
}
|
||||
if (opts.data) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
if (!root) {
|
||||
template = basename(template);
|
||||
} else if (template.indexOf(root) === 0) {
|
||||
template = template.substring(root.length + 1);
|
||||
}
|
||||
template = template.replace(extension, '');
|
||||
|
||||
let precompiled = Handlebars.precompile(data, options);
|
||||
|
||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||
if (opts.map) {
|
||||
let consumer = new SourceMapConsumer(precompiled.map);
|
||||
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
|
||||
}
|
||||
|
||||
if (opts.simple) {
|
||||
output.add([precompiled, '\n']);
|
||||
} else if (opts.partial) {
|
||||
if (opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add(['Handlebars.partials[\'', template, '\'] = template(', precompiled, ');\n']);
|
||||
} else {
|
||||
if (opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add(['templates[\'', template, '\'] = template(', precompiled, ');\n']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
processTemplate(template, opts.root);
|
||||
let options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: opts.o
|
||||
};
|
||||
|
||||
if (opts.map) {
|
||||
options.srcName = template.path;
|
||||
}
|
||||
if (opts.data) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
let precompiled = Handlebars.precompile(template.source, options);
|
||||
|
||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||
if (opts.map) {
|
||||
let consumer = new SourceMapConsumer(precompiled.map);
|
||||
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
|
||||
}
|
||||
|
||||
if (opts.simple) {
|
||||
output.add([precompiled, '\n']);
|
||||
} else if (opts.partial) {
|
||||
if (opts.amd && (opts.templates.length == 1 && !opts.hasDirectory)) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add(['Handlebars.partials[\'', template.name, '\'] = template(', precompiled, ');\n']);
|
||||
} else {
|
||||
if (opts.amd && (opts.templates.length == 1 && !opts.hasDirectory)) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add(['templates[\'', template.name, '\'] = template(', precompiled, ');\n']);
|
||||
}
|
||||
});
|
||||
|
||||
// Output the content
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
if (opts.templates.length > 1 || (opts.templates.length == 1 && fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
if (opts.templates.length > 1 || (opts.templates.length == 1 && opts.hasDirectory)) {
|
||||
if (opts.partial) {
|
||||
output.add('return Handlebars.partials;\n');
|
||||
} else {
|
||||
|
||||
+58
-40
@@ -16,6 +16,12 @@ describe('precompiler', function() {
|
||||
precompile,
|
||||
minify,
|
||||
|
||||
emptyTemplate = {
|
||||
path: __dirname + '/artifacts/empty.handlebars',
|
||||
name: 'empty',
|
||||
source: ''
|
||||
},
|
||||
|
||||
file,
|
||||
content,
|
||||
writeFileSync;
|
||||
@@ -51,10 +57,9 @@ describe('precompiler', function() {
|
||||
Precompiler.cli({templates: []});
|
||||
}, Handlebars.Exception, 'Must define at least one template or directory.');
|
||||
});
|
||||
it('should throw on missing template', function() {
|
||||
shouldThrow(function() {
|
||||
Precompiler.cli({templates: ['foo']});
|
||||
}, Handlebars.Exception, 'Unable to open template file "foo"');
|
||||
it('should handle empty/filtered directories', function() {
|
||||
Precompiler.cli({hasDirectory: true, templates: []});
|
||||
// Success is not throwing
|
||||
});
|
||||
it('should throw when combining simple and minimized', function() {
|
||||
shouldThrow(function() {
|
||||
@@ -68,105 +73,118 @@ describe('precompiler', function() {
|
||||
});
|
||||
it('should throw when combining simple and directories', function() {
|
||||
shouldThrow(function() {
|
||||
Precompiler.cli({templates: [__dirname], simple: true});
|
||||
Precompiler.cli({hasDirectory: true, templates: [1], simple: true});
|
||||
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
|
||||
});
|
||||
it('should enumerate directories by extension', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'hbs'});
|
||||
equal(/'example_2'/.test(log), true);
|
||||
log = '';
|
||||
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'handlebars'});
|
||||
equal(/'empty'/.test(log), true);
|
||||
equal(/'example_1'/.test(log), true);
|
||||
});
|
||||
it('should protect from regexp patterns', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'hb(s'});
|
||||
// Success is not throwing
|
||||
});
|
||||
it('should output simple templates', function() {
|
||||
Handlebars.precompile = function() { return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], simple: true});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
it('should output amd templates', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], amd: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], amd: true});
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output multiple amd', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, extension: 'handlebars', namespace: 'foo'});
|
||||
Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, namespace: 'foo'});
|
||||
equal(/templates = foo = foo \|\|/.test(log), true);
|
||||
equal(/return templates/.test(log), true);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output amd partials', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], amd: true, partial: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], amd: true, partial: true});
|
||||
equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output multiple amd partials', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, partial: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, partial: true});
|
||||
equal(/return Handlebars\.partials\[/.test(log), false);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output commonjs templates', function() {
|
||||
Handlebars.precompile = function() { return 'commonjs'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], commonjs: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], commonjs: true});
|
||||
equal(/template\(commonjs\)/.test(log), true);
|
||||
});
|
||||
|
||||
it('should set data flag', function() {
|
||||
Handlebars.precompile = function(data, options) { equal(options.data, true); return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', data: true});
|
||||
Precompiler.cli({templates: [emptyTemplate], simple: true, data: true});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
|
||||
it('should set known helpers', function() {
|
||||
Handlebars.precompile = function(data, options) { equal(options.knownHelpers.foo, true); return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', known: 'foo'});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
|
||||
it('should handle different root', function() {
|
||||
Handlebars.precompile = function() { return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', root: 'foo/'});
|
||||
Precompiler.cli({templates: [emptyTemplate], simple: true, known: 'foo'});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
it('should output to file system', function() {
|
||||
Handlebars.precompile = function() { return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', output: 'file!'});
|
||||
Precompiler.cli({templates: [emptyTemplate], simple: true, output: 'file!'});
|
||||
equal(file, 'file!');
|
||||
equal(content, 'simple\n');
|
||||
equal(log, '');
|
||||
});
|
||||
it('should handle BOM', function() {
|
||||
Handlebars.precompile = function(template) { return template === 'a' ? 'simple' : 'fail'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/bom.handlebars'], simple: true, extension: 'handlebars', bom: true});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
|
||||
it('should output minimized templates', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
uglify.minify = function() { return {code: 'min'}; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], min: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], min: true});
|
||||
equal(log, 'min');
|
||||
});
|
||||
|
||||
it('should output map', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], map: 'foo.js.map', extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], map: 'foo.js.map'});
|
||||
|
||||
equal(file, 'foo.js.map');
|
||||
equal(/sourceMappingURL=/.test(log), true);
|
||||
});
|
||||
|
||||
it('should output map', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], min: true, map: 'foo.js.map', extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], min: true, map: 'foo.js.map'});
|
||||
|
||||
equal(file, 'foo.js.map');
|
||||
equal(/sourceMappingURL=/.test(log), true);
|
||||
});
|
||||
|
||||
describe('#loadTemplates', function() {
|
||||
it('should throw on missing template', function() {
|
||||
shouldThrow(function() {
|
||||
Precompiler.loadTemplates({templates: ['foo']});
|
||||
}, Handlebars.Exception, 'Unable to open template file "foo"');
|
||||
});
|
||||
it('should enumerate directories by extension', function() {
|
||||
var opts = {templates: [__dirname + '/artifacts'], extension: 'hbs'};
|
||||
Precompiler.loadTemplates(opts);
|
||||
equal(opts.templates.length, 1);
|
||||
equal(opts.templates[0].name, 'example_2');
|
||||
|
||||
opts = {templates: [__dirname + '/artifacts'], extension: 'handlebars'};
|
||||
Precompiler.loadTemplates(opts);
|
||||
equal(opts.templates.length, 3);
|
||||
equal(opts.templates[0].name, 'bom');
|
||||
equal(opts.templates[1].name, 'empty');
|
||||
equal(opts.templates[2].name, 'example_1');
|
||||
});
|
||||
it('should handle regular expression characters in extensions', function() {
|
||||
Precompiler.loadTemplates({templates: [__dirname + '/artifacts'], extension: 'hb(s'});
|
||||
// Success is not throwing
|
||||
});
|
||||
it('should handle BOM', function() {
|
||||
var opts = {templates: [__dirname + '/artifacts/bom.handlebars'], extension: 'handlebars', bom: true};
|
||||
Precompiler.loadTemplates(opts);
|
||||
equal(opts.templates[0].source, 'a');
|
||||
});
|
||||
|
||||
it('should handle different root', function() {
|
||||
var opts = {templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, root: 'foo/'};
|
||||
Precompiler.loadTemplates(opts);
|
||||
equal(opts.templates[0].name, __dirname + '/artifacts/empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user