UNPKG

8.09 kBJavaScriptView Raw
1/* eslint-disable no-console */
2import Async from 'neo-async';
3import fs from 'fs';
4import * as Handlebars from './handlebars';
5import {basename} from 'path';
6import {SourceMapConsumer, SourceNode} from 'source-map';
7
8
9module.exports.loadTemplates = function(opts, callback) {
10 loadStrings(opts, function(err, strings) {
11 if (err) {
12 callback(err);
13 } else {
14 loadFiles(opts, function(err, files) {
15 if (err) {
16 callback(err);
17 } else {
18 opts.templates = strings.concat(files);
19 callback(undefined, opts);
20 }
21 });
22 }
23 });
24};
25
26function loadStrings(opts, callback) {
27 let strings = arrayCast(opts.string),
28 names = arrayCast(opts.name);
29
30 if (names.length !== strings.length
31 && strings.length > 1) {
32 return callback(new Handlebars.Exception('Number of names did not match the number of string inputs'));
33 }
34
35 Async.map(strings, function(string, callback) {
36 if (string !== '-') {
37 callback(undefined, string);
38 } else {
39 // Load from stdin
40 let buffer = '';
41 process.stdin.setEncoding('utf8');
42
43 process.stdin.on('data', function(chunk) {
44 buffer += chunk;
45 });
46 process.stdin.on('end', function() {
47 callback(undefined, buffer);
48 });
49 }
50 },
51 function(err, strings) {
52 strings = strings.map((string, index) => ({
53 name: names[index],
54 path: names[index],
55 source: string
56 }));
57 callback(err, strings);
58 });
59}
60
61function loadFiles(opts, callback) {
62 // Build file extension pattern
63 let extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-[\]]/g, function(arg) { return '\\' + arg; });
64 extension = new RegExp('\\.' + extension + '$');
65
66 let ret = [],
67 queue = (opts.files || []).map((template) => ({template, root: opts.root}));
68 Async.whilst(() => queue.length, function(callback) {
69 let {template: path, root} = queue.shift();
70
71 fs.stat(path, function(err, stat) {
72 if (err) {
73 return callback(new Handlebars.Exception(`Unable to open template file "${path}"`));
74 }
75
76 if (stat.isDirectory()) {
77 opts.hasDirectory = true;
78
79 fs.readdir(path, function(err, children) {
80 /* istanbul ignore next : Race condition that being too lazy to test */
81 if (err) {
82 return callback(err);
83 }
84 children.forEach(function(file) {
85 let childPath = path + '/' + file;
86
87 if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
88 queue.push({template: childPath, root: root || path});
89 }
90 });
91
92 callback();
93 });
94 } else {
95 fs.readFile(path, 'utf8', function(err, data) {
96 /* istanbul ignore next : Race condition that being too lazy to test */
97 if (err) {
98 return callback(err);
99 }
100
101 if (opts.bom && data.indexOf('\uFEFF') === 0) {
102 data = data.substring(1);
103 }
104
105 // Clean the template name
106 let name = path;
107 if (!root) {
108 name = basename(name);
109 } else if (name.indexOf(root) === 0) {
110 name = name.substring(root.length + 1);
111 }
112 name = name.replace(extension, '');
113
114 ret.push({
115 path: path,
116 name: name,
117 source: data
118 });
119
120 callback();
121 });
122 }
123 });
124 },
125 function(err) {
126 if (err) {
127 callback(err);
128 } else {
129 callback(undefined, ret);
130 }
131 });
132}
133
134module.exports.cli = function(opts) {
135 if (opts.version) {
136 console.log(Handlebars.VERSION);
137 return;
138 }
139
140 if (!opts.templates.length && !opts.hasDirectory) {
141 throw new Handlebars.Exception('Must define at least one template or directory.');
142 }
143
144 if (opts.simple && opts.min) {
145 throw new Handlebars.Exception('Unable to minimize simple output');
146 }
147
148 const multiple = opts.templates.length !== 1 || opts.hasDirectory;
149 if (opts.simple && multiple) {
150 throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
151 }
152
153 // Force simple mode if we have only one template and it's unnamed.
154 if (!opts.amd && !opts.commonjs && opts.templates.length === 1
155 && !opts.templates[0].name) {
156 opts.simple = true;
157 }
158
159 // Convert the known list into a hash
160 let known = {};
161 if (opts.known && !Array.isArray(opts.known)) {
162 opts.known = [opts.known];
163 }
164 if (opts.known) {
165 for (let i = 0, len = opts.known.length; i < len; i++) {
166 known[opts.known[i]] = true;
167 }
168 }
169
170 const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
171
172 let output = new SourceNode();
173 if (!opts.simple) {
174 if (opts.amd) {
175 output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
176 } else if (opts.commonjs) {
177 output.add('var Handlebars = require("' + opts.commonjs + '");');
178 } else {
179 output.add('(function() {\n');
180 }
181 output.add(' var template = Handlebars.template, templates = ');
182 if (opts.namespace) {
183 output.add(opts.namespace);
184 output.add(' = ');
185 output.add(opts.namespace);
186 output.add(' || ');
187 }
188 output.add('{};\n');
189 }
190
191 opts.templates.forEach(function(template) {
192 let options = {
193 knownHelpers: known,
194 knownHelpersOnly: opts.o
195 };
196
197 if (opts.map) {
198 options.srcName = template.path;
199 }
200 if (opts.data) {
201 options.data = true;
202 }
203
204 let precompiled = Handlebars.precompile(template.source, options);
205
206 // If we are generating a source map, we have to reconstruct the SourceNode object
207 if (opts.map) {
208 let consumer = new SourceMapConsumer(precompiled.map);
209 precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
210 }
211
212 if (opts.simple) {
213 output.add([precompiled, '\n']);
214 } else {
215 if (!template.name) {
216 throw new Handlebars.Exception('Name missing for template');
217 }
218
219 if (opts.amd && !multiple) {
220 output.add('return ');
221 }
222 output.add([objectName, '[\'', template.name, '\'] = template(', precompiled, ');\n']);
223 }
224 });
225
226 // Output the content
227 if (!opts.simple) {
228 if (opts.amd) {
229 if (multiple) {
230 output.add(['return ', objectName, ';\n']);
231 }
232 output.add('});');
233 } else if (!opts.commonjs) {
234 output.add('})();');
235 }
236 }
237
238 if (opts.map) {
239 output.add('\n//# sourceMappingURL=' + opts.map + '\n');
240 }
241
242 output = output.toStringWithSourceMap();
243 output.map = output.map + '';
244
245 if (opts.min) {
246 output = minify(output, opts.map);
247 }
248
249 if (opts.map) {
250 fs.writeFileSync(opts.map, output.map, 'utf8');
251 }
252 output = output.code;
253
254 if (opts.output) {
255 fs.writeFileSync(opts.output, output, 'utf8');
256 } else {
257 console.log(output);
258 }
259};
260
261function arrayCast(value) {
262 value = value != null ? value : [];
263 if (!Array.isArray(value)) {
264 value = [value];
265 }
266 return value;
267}
268
269/**
270 * Run uglify to minify the compiled template, if uglify exists in the dependencies.
271 *
272 * We are using `require` instead of `import` here, because es6-modules do not allow
273 * dynamic imports and uglify-js is an optional dependency. Since we are inside NodeJS here, this
274 * should not be a problem.
275 *
276 * @param {string} output the compiled template
277 * @param {string} sourceMapFile the file to write the source map to.
278 */
279function minify(output, sourceMapFile) {
280 try {
281 // Try to resolve uglify-js in order to see if it does exist
282 require.resolve('uglify-js');
283 } catch (e) {
284 if (e.code !== 'MODULE_NOT_FOUND') {
285 // Something else seems to be wrong
286 throw e;
287 }
288 // it does not exist!
289 console.error('Code minimization is disabled due to missing uglify-js dependency');
290 return output;
291 }
292 return require('uglify-js').minify(output.code, {
293 sourceMap: {
294 content: output.map,
295 url: sourceMapFile
296 }
297 });
298}