| 1 |
/* eslint-disable no-console */ |
| 2 |
import Async from 'async'; |
| 3 |
import fs from 'fs'; |
| 4 |
import * as Handlebars from './handlebars'; |
| 5 |
import {basename} from 'path'; |
| 6 |
import {SourceMapConsumer, SourceNode} from 'source-map'; |
| 7 |
|
| 8 |
|
| 9 |
module.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 |
|
| 26 |
function 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 |
|
| 61 |
function 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 |
|
| 134 |
module.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 |
|
| 261 |
function 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 |
*/ |
| 279 |
function 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 |
} |
| 299 |
|