341 lines
8.3 KiB
JavaScript
341 lines
8.3 KiB
JavaScript
|
/* 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
|
||
|
}
|
||
|
});
|
||
|
}
|