299 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			299 lines
		
	
	
		
			7.9 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
 | |
|     }
 | |
|   });
 | |
| }
 |