1 | var Promise = require('bluebird');
|
2 | var asp = require('bluebird').promisify;
|
3 | var fs = require('fs');
|
4 | var path = require('path');
|
5 | var url = require('url');
|
6 | var createHash = require('crypto').createHash;
|
7 | var template = require('es6-template-strings');
|
8 | var getAlias = require('./utils').getAlias;
|
9 | var extend = require('./utils').extend;
|
10 | var traverseTree = require('./arithmetic').traverseTree;
|
11 | var verifyTree = require('./utils').verifyTree;
|
12 | var getFormatHint = require('./utils').getFormatHint;
|
13 |
|
14 | var compilerMap = {
|
15 | 'amd': '../compilers/amd',
|
16 | 'cjs': '../compilers/cjs',
|
17 | 'esm': '../compilers/esm',
|
18 | 'global': '../compilers/global',
|
19 | 'system': '../compilers/register',
|
20 | 'json': '../compilers/json'
|
21 | };
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | function getCompileHash(load, compileOpts) {
|
27 | return createHash('md5')
|
28 | .update(JSON.stringify({
|
29 | source: load.source,
|
30 | metadata: load.metadata,
|
31 | path: compileOpts.sourceMaps && load.path,
|
32 |
|
33 | normalize: compileOpts.normalize,
|
34 | anonymous: compileOpts.anonymous,
|
35 | systemGlobal: compileOpts.systemGlobal,
|
36 | static: compileOpts.static,
|
37 | encodeNames: compileOpts.encodeNames,
|
38 | sourceMaps: compileOpts.sourceMaps,
|
39 | lowResSourceMaps: compileOpts.lowResSourceMaps
|
40 | }))
|
41 | .digest('hex');
|
42 | }
|
43 |
|
44 | function getEncoding(canonical, encodings, loader) {
|
45 |
|
46 | if (canonical[0] == '@' && canonical != '@dummy-entry-point' && loader.has(canonical))
|
47 | return canonical;
|
48 |
|
49 |
|
50 | if (encodings[canonical])
|
51 | return encodings[canonical];
|
52 |
|
53 |
|
54 | var highestEncoding = 9;
|
55 | Object.keys(encodings).forEach(function(canonical) {
|
56 | var encoding = encodings[canonical];
|
57 | highestEncoding = Math.max(parseInt(encoding, '16'), highestEncoding);
|
58 | });
|
59 |
|
60 | highestEncoding++;
|
61 |
|
62 | return encodings[canonical] = highestEncoding.toString(16);
|
63 | }
|
64 | function getName(encoding, encodings) {
|
65 | var match
|
66 | Object.keys(encodings).some(function(e) {
|
67 | if (encodings[e] == encoding) {
|
68 | match = e;
|
69 | return true;
|
70 | }
|
71 | });
|
72 | return match;
|
73 | }
|
74 |
|
75 |
|
76 | var hashBangRegEx = /^\#\!.*/;
|
77 |
|
78 | exports.compileLoad = compileLoad;
|
79 | function compileLoad(loader, load, compileOpts, cache) {
|
80 |
|
81 | var cached = cache.loads[load.name];
|
82 | if (cached && cached.hash == getCompileHash(load, compileOpts))
|
83 | return Promise.resolve(cached.output);
|
84 |
|
85 |
|
86 | function remapLoadRecord(load, mapFunction) {
|
87 | load = extend({}, load);
|
88 | load.name = mapFunction(load.name, load.name);
|
89 | var depMap = {};
|
90 | Object.keys(load.depMap).forEach(function(dep) {
|
91 | depMap[dep] = mapFunction(load.depMap[dep], dep);
|
92 | });
|
93 | load.depMap = depMap;
|
94 | return load;
|
95 | }
|
96 | var mappedLoad = remapLoadRecord(load, function(name, original) {
|
97 |
|
98 | if (compileOpts.encodeNames)
|
99 | return getEncoding(name, cache.encodings, loader);
|
100 |
|
101 | if (compileOpts.normalize && name.indexOf('#:') != -1)
|
102 | throw new Error('Unable to build dependency ' + name + '. normalize must be disabled for bundles containing conditionals.');
|
103 |
|
104 | return name;
|
105 | });
|
106 |
|
107 | var format = load.metadata.format;
|
108 |
|
109 | if (format == 'defined')
|
110 | return Promise.resolve({ source: compileOpts.systemGlobal + '.register("' + mappedLoad.name + '", [], function() { return { setters: [], execute: function() {} } });\n' });
|
111 |
|
112 | if (format in compilerMap) {
|
113 | if (format == 'cjs')
|
114 | mappedLoad.source = mappedLoad.source.replace(hashBangRegEx, '');
|
115 | return Promise.resolve()
|
116 | .then(function() {
|
117 | return require(compilerMap[format]).compile(mappedLoad, compileOpts, loader);
|
118 | })
|
119 | .then(function(output) {
|
120 |
|
121 | cache.loads[load.name] = {
|
122 | hash: getCompileHash(load, compileOpts),
|
123 | output: output
|
124 | };
|
125 |
|
126 | return output;
|
127 | })
|
128 | .catch(function(err) {
|
129 |
|
130 | if (err instanceof Array)
|
131 | err = err[0];
|
132 | err.message = 'Error compiling ' + format + ' module "' + load.name + '" at ' + load.path + '\n\t' + err.message;
|
133 | throw err;
|
134 | });
|
135 | }
|
136 |
|
137 | return Promise.reject(new TypeError('Unknown module format ' + format));
|
138 | }
|
139 |
|
140 |
|
141 |
|
142 | exports.getTreeModulesPostOrder = getTreeModulesPostOrder;
|
143 | function getTreeModulesPostOrder(tree, traceOpts) {
|
144 | var entryPoints = [];
|
145 |
|
146 |
|
147 | var entryMap = {};
|
148 |
|
149 | var moduleList = Object.keys(tree).filter(function(module) {
|
150 | return tree[module] !== false;
|
151 | }).sort();
|
152 |
|
153 |
|
154 |
|
155 | moduleList.forEach(function(entryPoint) {
|
156 | traverseTree(tree, entryPoint, function(depName, parentName) {
|
157 |
|
158 | if (entryMap[depName])
|
159 | return false;
|
160 |
|
161 | if (parentName)
|
162 | entryMap[depName] = entryPoint;
|
163 | }, traceOpts);
|
164 | });
|
165 |
|
166 |
|
167 | moduleList.forEach(function(entryPoint) {
|
168 | if (!entryMap[entryPoint])
|
169 | entryPoints.push(entryPoint);
|
170 | });
|
171 |
|
172 |
|
173 |
|
174 | entryPoints = entryPoints.sort();
|
175 |
|
176 | var modules = [];
|
177 |
|
178 | entryPoints.reverse().forEach(function(moduleName) {
|
179 | traverseTree(tree, moduleName, function(depName, parentName) {
|
180 | if (modules.indexOf(depName) == -1)
|
181 | modules.push(depName);
|
182 | }, traceOpts, true);
|
183 | });
|
184 |
|
185 | return {
|
186 | entryPoints: entryPoints,
|
187 | modules: modules.reverse()
|
188 | };
|
189 | }
|
190 |
|
191 |
|
192 |
|
193 | exports.pluginBundleHook = pluginBundleHook;
|
194 | function pluginBundleHook(loader, loads, compileOpts, outputOpts) {
|
195 | var outputs = [];
|
196 |
|
197 | var assetList = [];
|
198 | var pluginLoads = {};
|
199 |
|
200 |
|
201 | loads.forEach(function(load) {
|
202 | if (load.metadata.loader) {
|
203 | var pluginLoad = extend({}, load);
|
204 | pluginLoad.address = loader.baseURL + load.path;
|
205 | (pluginLoads[load.metadata.loader] = pluginLoads[load.metadata.loader] || []).push(pluginLoad);
|
206 | }
|
207 | });
|
208 |
|
209 | return Promise.all(Object.keys(pluginLoads).map(function(pluginName) {
|
210 | var loads = pluginLoads[pluginName];
|
211 | var loaderModule = loads[0].metadata.loaderModule;
|
212 |
|
213 | if (loaderModule.listAssets)
|
214 | return Promise.resolve(loaderModule.listAssets.call(loader.pluginLoader, loads, compileOpts, outputOpts))
|
215 | .then(function(_assetList) {
|
216 | assetList = assetList.concat(_assetList.map(function(asset) {
|
217 | return {
|
218 | url: asset.url,
|
219 | type: asset.type,
|
220 | source: asset.source,
|
221 | sourceMap: asset.sourceMap
|
222 | };
|
223 | }));
|
224 | });
|
225 | }))
|
226 | .then(function() {
|
227 | return Promise.all(Object.keys(pluginLoads).map(function(pluginName) {
|
228 | var loads = pluginLoads[pluginName];
|
229 | var loaderModule = loads[0].metadata.loaderModule;
|
230 |
|
231 | if (compileOpts.inlinePlugins) {
|
232 | if (loaderModule.inline) {
|
233 | return Promise.resolve(loaderModule.inline.call(loader.pluginLoader, loads, compileOpts, outputOpts));
|
234 | }
|
235 |
|
236 | else if (loaderModule.bundle) {
|
237 |
|
238 | if (loaderModule.bundle.length < 3)
|
239 | return Promise.resolve(loaderModule.bundle.call(loader.pluginLoader, loads, extend(extend({}, compileOpts), outputOpts)));
|
240 | else
|
241 | return Promise.resolve(loaderModule.bundle.call(loader.pluginLoader, loads, compileOpts, outputOpts));
|
242 | }
|
243 | }
|
244 | }));
|
245 | })
|
246 | .then(function(compiled) {
|
247 | var outputs = [];
|
248 | compiled = compiled || [];
|
249 | compiled.forEach(function(output) {
|
250 | if (output instanceof Array)
|
251 | outputs = outputs.concat(output);
|
252 | else if (output)
|
253 | outputs.push(output);
|
254 | });
|
255 |
|
256 | return {
|
257 | outputs: outputs,
|
258 | assetList: assetList
|
259 | };
|
260 | });
|
261 | }
|
262 |
|
263 | exports.compileTree = compileTree;
|
264 | function compileTree(loader, tree, traceOpts, compileOpts, outputOpts, cache) {
|
265 |
|
266 |
|
267 | verifyTree(tree);
|
268 |
|
269 | var ordered = getTreeModulesPostOrder(tree, traceOpts);
|
270 |
|
271 | var inputEntryPoints;
|
272 |
|
273 |
|
274 | var entryPoints;
|
275 |
|
276 | var modules;
|
277 |
|
278 | var outputs = [];
|
279 |
|
280 | var compilers = {};
|
281 |
|
282 | return Promise.resolve()
|
283 | .then(function() {
|
284 |
|
285 | if (!compileOpts.entryPoints)
|
286 | return [];
|
287 |
|
288 | return Promise.all(compileOpts.entryPoints.map(function(entryPoint) {
|
289 | return loader.normalize(entryPoint)
|
290 | .then(function(normalized) {
|
291 | return loader.getCanonicalName(normalized);
|
292 | });
|
293 | }))
|
294 | .filter(function(inputEntryPoint) {
|
295 |
|
296 | return !inputEntryPoint.match(/\#\:|\#\?|\#{/) && tree[inputEntryPoint];
|
297 | })
|
298 | })
|
299 | .then(function(inputEntryPoints) {
|
300 | entryPoints = inputEntryPoints || [];
|
301 |
|
302 | ordered.entryPoints.forEach(function(entryPoint) {
|
303 | if (entryPoints.indexOf(entryPoint) == -1)
|
304 | entryPoints.push(entryPoint);
|
305 | });
|
306 |
|
307 | modules = ordered.modules.filter(function(moduleName) {
|
308 | var load = tree[moduleName];
|
309 | if (load.runtimePlugin && compileOpts.static)
|
310 | throw new TypeError('Plugin ' + load.plugin + ' does not support static builds, compiling ' + load.name + '.');
|
311 | return load && !load.conditional && !load.runtimePlugin;
|
312 | });
|
313 |
|
314 | if (compileOpts.encodeNames)
|
315 | entryPoints = entryPoints.map(function(name) {
|
316 | return getEncoding(name, cache.encodings, loader);
|
317 | });
|
318 | })
|
319 |
|
320 |
|
321 | .then(function() {
|
322 | return Promise.all(modules.map(function(name) {
|
323 | return Promise.resolve()
|
324 | .then(function() {
|
325 | var load = tree[name];
|
326 |
|
327 | if (load === true)
|
328 | throw new TypeError(name + ' was defined via a bundle, so can only be used for subtraction or union operations.');
|
329 |
|
330 | return compileLoad(loader, tree[name], compileOpts, cache);
|
331 | });
|
332 | }));
|
333 | })
|
334 | .then(function(compiled) {
|
335 | outputs = outputs.concat(compiled);
|
336 | })
|
337 |
|
338 |
|
339 | .then(function() {
|
340 | var pluginLoads = [];
|
341 |
|
342 | modules.forEach(function(name) {
|
343 | var load = tree[name];
|
344 |
|
345 | pluginLoads.push(load);
|
346 |
|
347 |
|
348 | if (load.compactedLoads)
|
349 | load.compactedLoads.forEach(function(load) {
|
350 | pluginLoads.push(load);
|
351 | });
|
352 | });
|
353 |
|
354 | return pluginBundleHook(loader, pluginLoads, compileOpts, outputOpts);
|
355 | })
|
356 | .then(function(pluginResults) {
|
357 | outputs = outputs.concat(pluginResults.outputs);
|
358 | var assetList = pluginResults.assetList;
|
359 |
|
360 | return Promise.resolve()
|
361 | .then(function() {
|
362 |
|
363 |
|
364 | if (modules.some(function(name) {
|
365 | return tree[name].metadata.format == 'amd';
|
366 | }) && !compileOpts.static)
|
367 | outputs.unshift('"bundle";');
|
368 |
|
369 |
|
370 | if (compileOpts.static)
|
371 | return wrapSFXOutputs(loader, tree, modules, outputs, entryPoints, compileOpts, cache);
|
372 |
|
373 | return outputs;
|
374 | })
|
375 | .then(function(outputs) {
|
376 |
|
377 | return {
|
378 | outputs: outputs,
|
379 | entryPoints: entryPoints,
|
380 | assetList: assetList,
|
381 | modules: modules.reverse()
|
382 | };
|
383 | });
|
384 | });
|
385 | }
|
386 |
|
387 | exports.wrapSFXOutputs = wrapSFXOutputs;
|
388 | function wrapSFXOutputs(loader, tree, modules, outputs, entryPoints, compileOpts, cache) {
|
389 | var compilers = {};
|
390 | var externalDeps = [];
|
391 |
|
392 | Object.keys(tree).forEach(function(module) {
|
393 | if (tree[module] === false && !loader.has(module))
|
394 | externalDeps.push(module);
|
395 | });
|
396 |
|
397 | externalDeps.sort();
|
398 |
|
399 |
|
400 | var legacyTranspiler = false;
|
401 | modules.forEach(function(name) {
|
402 | if (!legacyTranspiler && tree[name].metadata.originalSource)
|
403 | legacyTranspiler = true;
|
404 | compilers[tree[name].metadata.format] = true;
|
405 | });
|
406 |
|
407 |
|
408 | Object.keys(compilerMap).forEach(function(format) {
|
409 | if (!compilers[format])
|
410 | return;
|
411 | var compiler = require(compilerMap[format]);
|
412 | if (compiler.sfx)
|
413 | outputs.unshift(compiler.sfx(loader));
|
414 | });
|
415 |
|
416 |
|
417 | var globalDeps = [];
|
418 | modules.forEach(function(name) {
|
419 | var load = tree[name];
|
420 |
|
421 |
|
422 | load.deps.forEach(function(dep) {
|
423 | var key = load.depMap[dep];
|
424 | if (!(key in tree) && !loader.has(key)) {
|
425 | if (compileOpts.format == 'esm')
|
426 | throw new TypeError('ESM static builds with externals only work when all modules in the build are ESM.');
|
427 |
|
428 | if (externalDeps.indexOf(key) == -1)
|
429 | externalDeps.push(key);
|
430 | }
|
431 | });
|
432 | });
|
433 |
|
434 | var externalDepIds = externalDeps.map(function(dep) {
|
435 | if (compileOpts.format == 'global' ||
|
436 | compileOpts.format == 'umd' && (compileOpts.globalName || Object.keys(compileOpts.globalDeps).length > 0)) {
|
437 | var alias = getAlias(loader, dep);
|
438 | var globalDep = compileOpts.globalDeps[dep] || compileOpts.globalDeps[alias];
|
439 | if (!globalDep)
|
440 | throw new TypeError('Global SFX bundle dependency "' + alias +
|
441 | '" must be configured to an environment global via the globalDeps option.');
|
442 |
|
443 | globalDeps.push(globalDep);
|
444 | }
|
445 |
|
446 |
|
447 | var entryPointIndex = entryPoints.indexOf(dep);
|
448 | if (entryPointIndex != -1)
|
449 | entryPoints.splice(entryPointIndex, 1);
|
450 |
|
451 | if (compileOpts.encodeNames)
|
452 | return getEncoding(dep, cache.encodings, loader);
|
453 | else
|
454 | return dep;
|
455 | });
|
456 |
|
457 |
|
458 | return asp(fs.readFile)(path.resolve(__dirname, '../templates/sfx-core.min.js'))
|
459 | .then(function(sfxcore) {
|
460 |
|
461 | outputs.unshift("var require = this.require, exports = this.exports, module = this.module;");
|
462 |
|
463 |
|
464 | var exportDefault = compileOpts.exportDefault;
|
465 | var exportedLoad = tree[compileOpts.encodeNames && getName(entryPoints[0], cache.encodings) || entryPoints[0]];
|
466 | if (exportedLoad && exportedLoad.metadata.format != 'system' && exportedLoad.metadata.format != 'esm')
|
467 | exportDefault = true;
|
468 |
|
469 | outputs.unshift(sfxcore.toString(), "(" + JSON.stringify(entryPoints) + ", " + JSON.stringify(externalDepIds) + ", " +
|
470 | (exportDefault ? "true" : "false") + ", function(" + compileOpts.systemGlobal + ") {");
|
471 |
|
472 | outputs.push("})");
|
473 | return asp(fs.readFile)(path.resolve(__dirname, '../templates/sfx-' + compileOpts.format + '.js'));
|
474 | })
|
475 |
|
476 | .then(function(formatWrapper) {
|
477 | outputs.push(template(formatWrapper.toString(), {
|
478 | deps: externalDeps.map(function(dep) {
|
479 | if (dep.indexOf('#:') != -1)
|
480 | dep = dep.replace('#:/', '/');
|
481 | var name = getAlias(loader, dep);
|
482 | return name;
|
483 | }),
|
484 | globalDeps: globalDeps,
|
485 | globalName: compileOpts.globalName
|
486 | }));
|
487 | })
|
488 |
|
489 | .then(function() {
|
490 | if (!legacyTranspiler)
|
491 | return;
|
492 |
|
493 |
|
494 | var usesBabelHelpersGlobal = modules.some(function(name) {
|
495 | return tree[name].metadata.usesBabelHelpersGlobal;
|
496 | });
|
497 |
|
498 | if (!usesBabelHelpersGlobal)
|
499 | usesBabelHelpersGlobal = modules.some(function(name) {
|
500 | return tree[name].metadata.format == 'esm' && cache.loads[name].output.source.match(/regeneratorRuntime/);
|
501 | });
|
502 | if (compileOpts.runtime && usesBabelHelpersGlobal)
|
503 | return getModuleSource(loader, 'babel/external-helpers')
|
504 | .then(function(source) {
|
505 | outputs.unshift(source);
|
506 | });
|
507 | })
|
508 | .then(function() {
|
509 | if (!legacyTranspiler)
|
510 | return;
|
511 |
|
512 |
|
513 | var usesTraceurRuntimeGlobal = modules.some(function(name) {
|
514 | return tree[name].metadata.usesTraceurRuntimeGlobal;
|
515 | });
|
516 | if (compileOpts.runtime && usesTraceurRuntimeGlobal)
|
517 | return getModuleSource(loader, 'traceur-runtime')
|
518 | .then(function(source) {
|
519 |
|
520 | outputs.unshift("(function(){ var curSystem = typeof System != 'undefined' ? System : undefined;\n" + source + "\nSystem = curSystem; })();");
|
521 | });
|
522 | })
|
523 |
|
524 | .then(function() {
|
525 | if (compileOpts.formatHint)
|
526 | outputs.unshift(getFormatHint(compileOpts));
|
527 | })
|
528 | .then(function() {
|
529 | return outputs;
|
530 | });
|
531 | }
|
532 |
|
533 | exports.attachCompilers = function(loader) {
|
534 | Object.keys(compilerMap).forEach(function(compiler) {
|
535 | var attach = require(compilerMap[compiler]).attach;
|
536 | if (attach)
|
537 | attach(loader);
|
538 | });
|
539 | };
|
540 |
|
541 | function getModuleSource(loader, module) {
|
542 | return loader.normalize(module)
|
543 | .then(function(normalized) {
|
544 | return loader.locate({ name: normalized, metadata: {} });
|
545 | })
|
546 | .then(function(address) {
|
547 | return loader.fetch({ address: address, metadata: {} });
|
548 | })
|
549 | .then(function(fetched) {
|
550 |
|
551 | var redirection = fetched.toString().match(/^\s*module\.exports = require\(\"([^\"]+)\"\);\s*$/);
|
552 | if (redirection)
|
553 | return getModuleSource(loader, redirection[1]);
|
554 | return fetched;
|
555 | })
|
556 | .catch(function(err) {
|
557 | console.log('Unable to find helper module "' + module + '". Make sure it is configured in the builder.');
|
558 | throw err;
|
559 | });
|
560 | }
|