Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
handlebars.js/lib/precompiler.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
344 lines (306 sloc)
8.36 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-env node */ | |
/* eslint-disable no-console */ | |
import Async from 'neo-async'; | |
import fs from 'fs'; | |
import * as Handlebars from './handlebars'; | |
import { basename } from 'path'; | |
import { SourceMapConsumer, SourceNode } from 'source-map'; | |
module.exports.loadTemplates = function (opts, callback) { | |
loadStrings(opts, function (err, strings) { | |
if (err) { | |
callback(err); | |
} else { | |
loadFiles(opts, function (err, files) { | |
if (err) { | |
callback(err); | |
} else { | |
opts.templates = strings.concat(files); | |
callback(undefined, opts); | |
} | |
}); | |
} | |
}); | |
}; | |
function loadStrings(opts, callback) { | |
let strings = arrayCast(opts.string), | |
names = arrayCast(opts.name); | |
if (names.length !== strings.length && strings.length > 1) { | |
return callback( | |
new Handlebars.Exception( | |
'Number of names did not match the number of string inputs' | |
) | |
); | |
} | |
Async.map( | |
strings, | |
function (string, callback) { | |
if (string !== '-') { | |
callback(undefined, string); | |
} else { | |
// Load from stdin | |
let buffer = ''; | |
process.stdin.setEncoding('utf8'); | |
process.stdin.on('data', function (chunk) { | |
buffer += chunk; | |
}); | |
process.stdin.on('end', function () { | |
callback(undefined, buffer); | |
}); | |
} | |
}, | |
function (err, strings) { | |
strings = strings.map((string, index) => ({ | |
name: names[index], | |
path: names[index], | |
source: string, | |
})); | |
callback(err, strings); | |
} | |
); | |
} | |
function loadFiles(opts, callback) { | |
// Build file extension pattern | |
let extension = (opts.extension || 'handlebars').replace( | |
/[\\^$*+?.():=!|{}\-[\]]/g, | |
function (arg) { | |
return '\\' + arg; | |
} | |
); | |
extension = new RegExp('\\.' + extension + '$'); | |
let ret = [], | |
queue = (opts.files || []).map((template) => ({ | |
template, | |
root: opts.root, | |
})); | |
Async.whilst( | |
() => queue.length, | |
function (callback) { | |
let { template: path, root } = queue.shift(); | |
fs.stat(path, function (err, stat) { | |
if (err) { | |
return callback( | |
new Handlebars.Exception(`Unable to open template file "${path}"`) | |
); | |
} | |
if (stat.isDirectory()) { | |
opts.hasDirectory = true; | |
fs.readdir(path, function (err, children) { | |
/* istanbul ignore next : Race condition that being too lazy to test */ | |
if (err) { | |
return callback(err); | |
} | |
children.forEach(function (file) { | |
let childPath = path + '/' + file; | |
if ( | |
extension.test(childPath) || | |
fs.statSync(childPath).isDirectory() | |
) { | |
queue.push({ template: childPath, root: root || path }); | |
} | |
}); | |
callback(); | |
}); | |
} else { | |
fs.readFile(path, 'utf8', function (err, data) { | |
/* istanbul ignore next : Race condition that being too lazy to test */ | |
if (err) { | |
return callback(err); | |
} | |
if (opts.bom && data.indexOf('\uFEFF') === 0) { | |
data = data.substring(1); | |
} | |
// Clean the template name | |
let name = path; | |
if (!root) { | |
name = basename(name); | |
} else if (name.indexOf(root) === 0) { | |
name = name.substring(root.length + 1); | |
} | |
name = name.replace(extension, ''); | |
ret.push({ | |
path: path, | |
name: name, | |
source: data, | |
}); | |
callback(); | |
}); | |
} | |
}); | |
}, | |
function (err) { | |
if (err) { | |
callback(err); | |
} else { | |
callback(undefined, ret); | |
} | |
} | |
); | |
} | |
module.exports.cli = function (opts) { | |
if (opts.version) { | |
console.log(Handlebars.VERSION); | |
return; | |
} | |
if (!opts.templates.length && !opts.hasDirectory) { | |
throw new Handlebars.Exception( | |
'Must define at least one template or directory.' | |
); | |
} | |
if (opts.simple && opts.min) { | |
throw new Handlebars.Exception('Unable to minimize simple output'); | |
} | |
const multiple = opts.templates.length !== 1 || opts.hasDirectory; | |
if (opts.simple && multiple) { | |
throw new Handlebars.Exception( | |
'Unable to output multiple templates in simple mode' | |
); | |
} | |
// Force simple mode if we have only one template and it's unnamed. | |
if ( | |
!opts.amd && | |
!opts.commonjs && | |
opts.templates.length === 1 && | |
!opts.templates[0].name | |
) { | |
opts.simple = true; | |
} | |
// Convert the known list into a hash | |
let known = {}; | |
if (opts.known && !Array.isArray(opts.known)) { | |
opts.known = [opts.known]; | |
} | |
if (opts.known) { | |
for (let i = 0, len = opts.known.length; i < len; i++) { | |
known[opts.known[i]] = true; | |
} | |
} | |
const objectName = opts.partial ? 'Handlebars.partials' : 'templates'; | |
let output = new SourceNode(); | |
if (!opts.simple) { | |
if (opts.amd) { | |
output.add( | |
"define(['" + | |
opts.handlebarPath + | |
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];' | |
); | |
} else if (opts.commonjs) { | |
output.add('var Handlebars = require("' + opts.commonjs + '");'); | |
} else { | |
output.add('(function() {\n'); | |
} | |
output.add(' var template = Handlebars.template, templates = '); | |
if (opts.namespace) { | |
output.add(opts.namespace); | |
output.add(' = '); | |
output.add(opts.namespace); | |
output.add(' || '); | |
} | |
output.add('{};\n'); | |
} | |
opts.templates.forEach(function (template) { | |
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 (!template.name) { | |
throw new Handlebars.Exception('Name missing for template'); | |
} | |
if (opts.amd && !multiple) { | |
output.add('return '); | |
} | |
output.add([ | |
objectName, | |
"['", | |
template.name, | |
"'] = template(", | |
precompiled, | |
');\n', | |
]); | |
} | |
}); | |
// Output the content | |
if (!opts.simple) { | |
if (opts.amd) { | |
if (multiple) { | |
output.add(['return ', objectName, ';\n']); | |
} | |
output.add('});'); | |
} else if (!opts.commonjs) { | |
output.add('})();'); | |
} | |
} | |
if (opts.map) { | |
output.add('\n//# sourceMappingURL=' + opts.map + '\n'); | |
} | |
output = output.toStringWithSourceMap(); | |
output.map = output.map + ''; | |
if (opts.min) { | |
output = minify(output, opts.map); | |
} | |
if (opts.map) { | |
fs.writeFileSync(opts.map, output.map, 'utf8'); | |
} | |
output = output.code; | |
if (opts.output) { | |
fs.writeFileSync(opts.output, output, 'utf8'); | |
} else { | |
console.log(output); | |
} | |
}; | |
function arrayCast(value) { | |
value = value != null ? value : []; | |
if (!Array.isArray(value)) { | |
value = [value]; | |
} | |
return value; | |
} | |
/** | |
* Run uglify to minify the compiled template, if uglify exists in the dependencies. | |
* | |
* We are using `require` instead of `import` here, because es6-modules do not allow | |
* dynamic imports and uglify-js is an optional dependency. Since we are inside NodeJS here, this | |
* should not be a problem. | |
* | |
* @param {string} output the compiled template | |
* @param {string} sourceMapFile the file to write the source map to. | |
*/ | |
function minify(output, sourceMapFile) { | |
try { | |
// Try to resolve uglify-js in order to see if it does exist | |
require.resolve('uglify-js'); | |
} catch (e) { | |
if (e.code !== 'MODULE_NOT_FOUND') { | |
// Something else seems to be wrong | |
throw e; | |
} | |
// it does not exist! | |
console.error( | |
'Code minimization is disabled due to missing uglify-js dependency' | |
); | |
return output; | |
} | |
return require('uglify-js').minify(output.code, { | |
sourceMap: { | |
content: output.map, | |
url: sourceMapFile, | |
}, | |
}); | |
} |