1 | const Packager = require('./Packager');
|
2 | const path = require('path');
|
3 | const concat = require('../scope-hoisting/concat');
|
4 | const urlJoin = require('../utils/urlJoin');
|
5 | const getExisting = require('../utils/getExisting');
|
6 | const walk = require('babylon-walk');
|
7 | const babylon = require('@babel/parser');
|
8 | const t = require('@babel/types');
|
9 | const {getName, getIdentifier} = require('../scope-hoisting/utils');
|
10 |
|
11 | const prelude = getExisting(
|
12 | path.join(__dirname, '../builtins/prelude2.min.js'),
|
13 | path.join(__dirname, '../builtins/prelude2.js')
|
14 | );
|
15 |
|
16 | const helpers = getExisting(
|
17 | path.join(__dirname, '../builtins/helpers.min.js'),
|
18 | path.join(__dirname, '../builtins/helpers.js')
|
19 | );
|
20 |
|
21 | class JSConcatPackager extends Packager {
|
22 | async start() {
|
23 | this.addedAssets = new Set();
|
24 | this.assets = new Map();
|
25 | this.exposedModules = new Set();
|
26 | this.externalModules = new Set();
|
27 | this.size = 0;
|
28 | this.needsPrelude = false;
|
29 | this.statements = [];
|
30 | this.assetPostludes = new Map();
|
31 |
|
32 | for (let asset of this.bundle.assets) {
|
33 |
|
34 | let isExposed = !Array.from(asset.parentDeps).every(dep => {
|
35 | let depAsset = this.bundler.loadedAssets.get(dep.parent);
|
36 | return this.bundle.assets.has(depAsset) || depAsset.type !== 'js';
|
37 | });
|
38 |
|
39 | if (
|
40 | isExposed ||
|
41 | (this.bundle.entryAsset === asset &&
|
42 | this.bundle.parentBundle &&
|
43 | this.bundle.parentBundle.childBundles.size !== 1)
|
44 | ) {
|
45 | this.exposedModules.add(asset);
|
46 | this.needsPrelude = true;
|
47 | }
|
48 |
|
49 | this.assets.set(asset.id, asset);
|
50 |
|
51 | for (let mod of asset.depAssets.values()) {
|
52 | if (
|
53 | !this.bundle.assets.has(mod) &&
|
54 | this.options.bundleLoaders[asset.type]
|
55 | ) {
|
56 | this.needsPrelude = true;
|
57 | break;
|
58 | }
|
59 | }
|
60 | }
|
61 |
|
62 | if (this.bundle.entryAsset) {
|
63 | this.markUsedExports(this.bundle.entryAsset);
|
64 | }
|
65 |
|
66 | if (this.needsPrelude) {
|
67 | if (
|
68 | this.bundle.entryAsset &&
|
69 | this.options.bundleLoaders[this.bundle.entryAsset.type]
|
70 | ) {
|
71 | this.exposedModules.add(this.bundle.entryAsset);
|
72 | }
|
73 | }
|
74 |
|
75 | this.write(helpers.minified);
|
76 | }
|
77 |
|
78 | write(string) {
|
79 | this.statements.push(...this.parse(string));
|
80 | }
|
81 |
|
82 | getSize() {
|
83 | return this.size;
|
84 | }
|
85 |
|
86 | markUsedExports(asset) {
|
87 | if (asset.usedExports) {
|
88 | return;
|
89 | }
|
90 |
|
91 | asset.usedExports = new Set();
|
92 |
|
93 | for (let identifier in asset.cacheData.imports) {
|
94 | let [source, name] = asset.cacheData.imports[identifier];
|
95 | let dep = asset.depAssets.get(asset.dependencies.get(source));
|
96 |
|
97 | if (dep) {
|
98 | if (name === '*') {
|
99 | this.markUsedExports(dep);
|
100 | }
|
101 |
|
102 | this.markUsed(dep, name);
|
103 | }
|
104 | }
|
105 | }
|
106 |
|
107 | markUsed(mod, name) {
|
108 | let {id} = this.findExportModule(mod.id, name);
|
109 | mod = this.assets.get(id);
|
110 |
|
111 | if (!mod) {
|
112 | return;
|
113 | }
|
114 |
|
115 | let exp = mod.cacheData.exports[name];
|
116 | if (Array.isArray(exp)) {
|
117 | let depMod = mod.depAssets.get(mod.dependencies.get(exp[0]));
|
118 | return this.markUsed(depMod, exp[1]);
|
119 | }
|
120 |
|
121 | this.markUsedExports(mod);
|
122 | mod.usedExports.add(name);
|
123 | }
|
124 |
|
125 | getExportIdentifier(asset) {
|
126 | let id = getName(asset, 'exports');
|
127 | if (this.shouldWrap(asset)) {
|
128 | return `(${getName(asset, 'init')}(), ${id})`;
|
129 | }
|
130 |
|
131 | return id;
|
132 | }
|
133 |
|
134 | async addAsset(asset) {
|
135 | if (this.addedAssets.has(asset)) {
|
136 | return;
|
137 | }
|
138 | this.addedAssets.add(asset);
|
139 | let {js} = asset.generated;
|
140 |
|
141 |
|
142 |
|
143 | if (
|
144 | asset.cacheData.sideEffects === false &&
|
145 | (!asset.usedExports || asset.usedExports.size === 0)
|
146 | ) {
|
147 | return;
|
148 | }
|
149 |
|
150 | for (let [dep, mod] of asset.depAssets) {
|
151 | if (dep.dynamic) {
|
152 | for (let child of mod.parentBundle.siblingBundles) {
|
153 | if (!child.isEmpty) {
|
154 | await this.addBundleLoader(child.type, asset);
|
155 | }
|
156 | }
|
157 |
|
158 | await this.addBundleLoader(mod.type, asset, true);
|
159 | } else {
|
160 |
|
161 |
|
162 |
|
163 | if (
|
164 | !this.bundle.assets.has(mod) &&
|
165 | (!this.bundle.parentBundle ||
|
166 | this.bundle.parentBundle.type !== 'js') &&
|
167 | this.options.bundleLoaders[mod.type]
|
168 | ) {
|
169 | this.externalModules.add(mod);
|
170 | await this.addBundleLoader(mod.type, asset);
|
171 | }
|
172 | }
|
173 | }
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 | this.size += js.length;
|
185 | }
|
186 |
|
187 | shouldWrap(asset) {
|
188 | if (!asset) {
|
189 | return false;
|
190 | }
|
191 |
|
192 | if (asset.cacheData.shouldWrap != null) {
|
193 | return asset.cacheData.shouldWrap;
|
194 | }
|
195 |
|
196 |
|
197 | asset.cacheData.shouldWrap = false;
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | let shouldWrap = [...asset.parentDeps].some(
|
204 | dep =>
|
205 | dep.shouldWrap ||
|
206 | this.shouldWrap(this.bundler.loadedAssets.get(dep.parent))
|
207 | );
|
208 |
|
209 | asset.cacheData.shouldWrap = shouldWrap;
|
210 | return shouldWrap;
|
211 | }
|
212 |
|
213 | addDeps(asset, included) {
|
214 | if (!this.bundle.assets.has(asset) || included.has(asset)) {
|
215 | return [];
|
216 | }
|
217 |
|
218 | included.add(asset);
|
219 |
|
220 | let depAsts = new Map();
|
221 | for (let depAsset of asset.depAssets.values()) {
|
222 | if (!depAsts.has(depAsset)) {
|
223 | let depAst = this.addDeps(depAsset, included);
|
224 | depAsts.set(depAsset, depAst);
|
225 | }
|
226 | }
|
227 |
|
228 | let statements;
|
229 | if (
|
230 | asset.cacheData.sideEffects === false &&
|
231 | (!asset.usedExports || asset.usedExports.size === 0)
|
232 | ) {
|
233 | statements = [];
|
234 | } else {
|
235 | statements = this.parse(asset.generated.js, asset.name);
|
236 | }
|
237 |
|
238 | if (this.shouldWrap(asset)) {
|
239 | statements = this.wrapModule(asset, statements);
|
240 | }
|
241 |
|
242 | if (statements[0]) {
|
243 | if (!statements[0].leadingComments) {
|
244 | statements[0].leadingComments = [];
|
245 | }
|
246 | statements[0].leadingComments.push({
|
247 | type: 'CommentLine',
|
248 | value: ` ASSET: ${path.relative(this.options.rootDir, asset.name)}`
|
249 | });
|
250 | }
|
251 |
|
252 | let statementIndices = new Map();
|
253 | for (let i = 0; i < statements.length; i++) {
|
254 | let statement = statements[i];
|
255 | if (t.isExpressionStatement(statement)) {
|
256 | for (let depAsset of this.findRequires(asset, statement)) {
|
257 | if (!statementIndices.has(depAsset)) {
|
258 | statementIndices.set(depAsset, i);
|
259 | }
|
260 | }
|
261 | }
|
262 | }
|
263 |
|
264 | let reverseDeps = [...asset.depAssets.values()].reverse();
|
265 | for (let dep of reverseDeps) {
|
266 | let index = statementIndices.has(dep) ? statementIndices.get(dep) : 0;
|
267 | statements.splice(index, 0, ...depAsts.get(dep));
|
268 | }
|
269 |
|
270 | if (this.assetPostludes.has(asset)) {
|
271 | statements.push(...this.parse(this.assetPostludes.get(asset)));
|
272 | }
|
273 |
|
274 | return statements;
|
275 | }
|
276 |
|
277 | wrapModule(asset, statements) {
|
278 | let body = [];
|
279 | let decls = [];
|
280 | let fns = [];
|
281 | for (let node of statements) {
|
282 |
|
283 |
|
284 | if (t.isVariableDeclaration(node)) {
|
285 | for (let decl of node.declarations) {
|
286 | decls.push(t.variableDeclarator(decl.id));
|
287 | if (decl.init) {
|
288 | body.push(
|
289 | t.expressionStatement(
|
290 | t.assignmentExpression(
|
291 | '=',
|
292 | t.identifier(decl.id.name),
|
293 | decl.init
|
294 | )
|
295 | )
|
296 | );
|
297 | }
|
298 | }
|
299 | } else if (t.isFunctionDeclaration(node)) {
|
300 |
|
301 | fns.push(node);
|
302 | } else if (t.isClassDeclaration(node)) {
|
303 |
|
304 |
|
305 | decls.push(t.variableDeclarator(t.identifier(node.id.name)));
|
306 | body.push(
|
307 | t.expressionStatement(
|
308 | t.assignmentExpression(
|
309 | '=',
|
310 | t.identifier(node.id.name),
|
311 | t.toExpression(node)
|
312 | )
|
313 | )
|
314 | );
|
315 | } else {
|
316 | body.push(node);
|
317 | }
|
318 | }
|
319 |
|
320 | let executed = getName(asset, 'executed');
|
321 | decls.push(
|
322 | t.variableDeclarator(t.identifier(executed), t.booleanLiteral(false))
|
323 | );
|
324 |
|
325 | let init = t.functionDeclaration(
|
326 | getIdentifier(asset, 'init'),
|
327 | [],
|
328 | t.blockStatement([
|
329 | t.ifStatement(t.identifier(executed), t.returnStatement()),
|
330 | t.expressionStatement(
|
331 | t.assignmentExpression(
|
332 | '=',
|
333 | t.identifier(executed),
|
334 | t.booleanLiteral(true)
|
335 | )
|
336 | ),
|
337 | ...body
|
338 | ])
|
339 | );
|
340 |
|
341 | return [t.variableDeclaration('var', decls), ...fns, init];
|
342 | }
|
343 |
|
344 | parse(code, filename) {
|
345 | let ast = babylon.parse(code, {
|
346 | sourceFilename: filename,
|
347 | allowReturnOutsideFunction: true
|
348 | });
|
349 |
|
350 | return ast.program.body;
|
351 | }
|
352 |
|
353 | findRequires(asset, ast) {
|
354 | let result = [];
|
355 | walk.simple(ast, {
|
356 | CallExpression(node) {
|
357 | let {arguments: args, callee} = node;
|
358 |
|
359 | if (!t.isIdentifier(callee)) {
|
360 | return;
|
361 | }
|
362 |
|
363 | if (callee.name === '$parcel$require') {
|
364 | result.push(
|
365 | asset.depAssets.get(asset.dependencies.get(args[1].value))
|
366 | );
|
367 | }
|
368 | }
|
369 | });
|
370 |
|
371 | return result;
|
372 | }
|
373 |
|
374 | getBundleSpecifier(bundle) {
|
375 | let name = path.relative(path.dirname(this.bundle.name), bundle.name);
|
376 | if (bundle.entryAsset) {
|
377 | return [name, bundle.entryAsset.id];
|
378 | }
|
379 |
|
380 | return name;
|
381 | }
|
382 |
|
383 | async addAssetToBundle(asset) {
|
384 | if (this.bundle.assets.has(asset)) {
|
385 | return;
|
386 | }
|
387 | this.assets.set(asset.id, asset);
|
388 | this.bundle.addAsset(asset);
|
389 | if (!asset.parentBundle) {
|
390 | asset.parentBundle = this.bundle;
|
391 | }
|
392 |
|
393 |
|
394 | for (let child of asset.depAssets.values()) {
|
395 | await this.addAssetToBundle(child, this.bundle);
|
396 | }
|
397 |
|
398 | await this.addAsset(asset);
|
399 | }
|
400 |
|
401 | async addBundleLoader(bundleType, parentAsset, dynamic) {
|
402 | let loader = this.options.bundleLoaders[bundleType];
|
403 | if (!loader) {
|
404 | return;
|
405 | }
|
406 |
|
407 | let bundleLoader = this.bundler.loadedAssets.get(
|
408 | require.resolve('../builtins/bundle-loader')
|
409 | );
|
410 | if (!bundleLoader && !dynamic) {
|
411 | bundleLoader = await this.bundler.getAsset('_bundle_loader');
|
412 | }
|
413 |
|
414 | if (bundleLoader) {
|
415 |
|
416 | await this.addAssetToBundle(bundleLoader);
|
417 | } else {
|
418 | return;
|
419 | }
|
420 |
|
421 | let target = this.options.target === 'node' ? 'node' : 'browser';
|
422 | let asset = await this.bundler.getAsset(loader[target]);
|
423 | if (!this.bundle.assets.has(asset)) {
|
424 | let dep = {name: asset.name};
|
425 | asset.parentDeps.add(dep);
|
426 | parentAsset.dependencies.set(dep.name, dep);
|
427 | parentAsset.depAssets.set(dep, asset);
|
428 | this.assetPostludes.set(
|
429 | asset,
|
430 | `${this.getExportIdentifier(bundleLoader)}.register(${JSON.stringify(
|
431 | bundleType
|
432 | )},${this.getExportIdentifier(asset)});\n`
|
433 | );
|
434 |
|
435 | await this.addAssetToBundle(asset);
|
436 | }
|
437 | }
|
438 |
|
439 | async end() {
|
440 | let included = new Set();
|
441 | for (let asset of this.bundle.assets) {
|
442 | this.statements.push(...this.addDeps(asset, included));
|
443 | }
|
444 |
|
445 |
|
446 | if (this.externalModules.size > 0) {
|
447 | let bundleLoader = this.bundler.loadedAssets.get(
|
448 | require.resolve('../builtins/bundle-loader')
|
449 | );
|
450 |
|
451 | let preload = [];
|
452 | for (let mod of this.externalModules) {
|
453 |
|
454 | let bundle = Array.from(mod.bundles).find(b => b.entryAsset === mod);
|
455 | if (bundle) {
|
456 | preload.push([path.basename(bundle.name), mod.id]);
|
457 | }
|
458 | }
|
459 |
|
460 | let loads = `${this.getExportIdentifier(
|
461 | bundleLoader
|
462 | )}.load(${JSON.stringify(preload)})`;
|
463 | if (this.bundle.entryAsset) {
|
464 | loads += '.then($parcel$entry)';
|
465 | }
|
466 |
|
467 | loads += ';';
|
468 | this.write(loads);
|
469 | }
|
470 |
|
471 | let entryExports =
|
472 | this.bundle.entryAsset &&
|
473 | this.getExportIdentifier(this.bundle.entryAsset);
|
474 | if (
|
475 | entryExports &&
|
476 | this.bundle.entryAsset.generated.js.includes(entryExports)
|
477 | ) {
|
478 | this.write(`
|
479 | if (typeof exports === "object" && typeof module !== "undefined") {
|
480 | // CommonJS
|
481 | module.exports = ${entryExports};
|
482 | } else if (typeof define === "function" && define.amd) {
|
483 | // RequireJS
|
484 | define(function () {
|
485 | return ${entryExports};
|
486 | });
|
487 | } ${
|
488 | this.options.global
|
489 | ? `else {
|
490 | // <script>
|
491 | this[${JSON.stringify(this.options.global)}] = ${entryExports};
|
492 | }`
|
493 | : ''
|
494 | }
|
495 | `);
|
496 | }
|
497 |
|
498 | if (this.needsPrelude) {
|
499 | let exposed = [];
|
500 | let prepareModule = [];
|
501 | for (let m of this.exposedModules) {
|
502 | if (m.cacheData.isES6Module) {
|
503 | prepareModule.push(
|
504 | `${this.getExportIdentifier(m)}.__esModule = true;`
|
505 | );
|
506 | }
|
507 |
|
508 | exposed.push(`"${m.id}": ${this.getExportIdentifier(m)}`);
|
509 | }
|
510 |
|
511 | this.write(`
|
512 | ${prepareModule.join('\n')}
|
513 | return {${exposed.join(', ')}};
|
514 | `);
|
515 | }
|
516 |
|
517 | try {
|
518 | let ast = t.file(t.program(this.statements));
|
519 | let {code: output} = concat(this, ast);
|
520 |
|
521 | if (!this.options.minify) {
|
522 | output = '\n' + output + '\n';
|
523 | }
|
524 |
|
525 | let preludeCode = this.options.minify ? prelude.minified : prelude.source;
|
526 | if (this.needsPrelude) {
|
527 | output = preludeCode + '(function (require) {' + output + '});';
|
528 | } else {
|
529 | output = '(function () {' + output + '})();';
|
530 | }
|
531 |
|
532 | this.size = output.length;
|
533 |
|
534 | let {sourceMaps} = this.options;
|
535 | if (sourceMaps) {
|
536 |
|
537 | let mapBundle = this.bundle.siblingBundlesMap.get('map');
|
538 | if (mapBundle) {
|
539 | let mapUrl = urlJoin(
|
540 | this.options.publicURL,
|
541 | path.basename(mapBundle.name)
|
542 | );
|
543 | output += `\n//# sourceMappingURL=${mapUrl}`;
|
544 | }
|
545 | }
|
546 |
|
547 | await super.write(output);
|
548 | } catch (e) {
|
549 | throw e;
|
550 | } finally {
|
551 | await super.end();
|
552 | }
|
553 | }
|
554 |
|
555 | resolveModule(id, name) {
|
556 | let module = this.assets.get(id);
|
557 | return module.depAssets.get(module.dependencies.get(name));
|
558 | }
|
559 |
|
560 | findExportModule(id, name, replacements) {
|
561 | let asset = this.assets.get(id);
|
562 | let exp =
|
563 | asset &&
|
564 | Object.prototype.hasOwnProperty.call(asset.cacheData.exports, name)
|
565 | ? asset.cacheData.exports[name]
|
566 | : null;
|
567 |
|
568 |
|
569 | if (Array.isArray(exp)) {
|
570 | let mod = this.resolveModule(id, exp[0]);
|
571 | return this.findExportModule(mod.id, exp[1], replacements);
|
572 | }
|
573 |
|
574 |
|
575 |
|
576 | let wildcards = asset && asset.cacheData.wildcards;
|
577 | if (wildcards && name !== 'default' && name !== '*') {
|
578 | for (let source of wildcards) {
|
579 | let mod = this.resolveModule(id, source);
|
580 | let m = this.findExportModule(mod.id, name, replacements);
|
581 | if (m.identifier) {
|
582 | return m;
|
583 | }
|
584 | }
|
585 | }
|
586 |
|
587 |
|
588 | if (asset && name === '*') {
|
589 | exp = getName(asset, 'exports');
|
590 | }
|
591 |
|
592 | if (replacements && replacements.has(exp)) {
|
593 | exp = replacements.get(exp);
|
594 | }
|
595 |
|
596 | return {
|
597 | identifier: exp,
|
598 | name,
|
599 | id
|
600 | };
|
601 | }
|
602 | }
|
603 |
|
604 | module.exports = JSConcatPackager;
|