@parcel/packager-js 2.0.0-nightly.97 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1172 @@
1
+ // @flow
2
+
3
+ import type {
4
+ Asset,
5
+ BundleGraph,
6
+ Dependency,
7
+ PluginOptions,
8
+ NamedBundle,
9
+ } from '@parcel/types';
10
+
11
+ import {PromiseQueue, relativeBundlePath, countLines} from '@parcel/utils';
12
+ import SourceMap from '@parcel/source-map';
13
+ import nullthrows from 'nullthrows';
14
+ import invariant from 'assert';
15
+ import ThrowableDiagnostic from '@parcel/diagnostic';
16
+ import globals from 'globals';
17
+
18
+ import {ESMOutputFormat} from './ESMOutputFormat';
19
+ import {CJSOutputFormat} from './CJSOutputFormat';
20
+ import {GlobalOutputFormat} from './GlobalOutputFormat';
21
+ import {prelude, helpers} from './helpers';
22
+ import {replaceScriptDependencies, getSpecifier} from './utils';
23
+
24
+ // https://262.ecma-international.org/6.0/#sec-names-and-keywords
25
+ const IDENTIFIER_RE = /^[$_\p{ID_Start}][$_\u200C\u200D\p{ID_Continue}]*$/u;
26
+ const ID_START_RE = /^[$_\p{ID_Start}]/u;
27
+ const NON_ID_CONTINUE_RE = /[^$_\u200C\u200D\p{ID_Continue}]/gu;
28
+
29
+ // General regex used to replace imports with the resolved code, references with resolutions,
30
+ // and count the number of newlines in the file for source maps.
31
+ const REPLACEMENT_RE =
32
+ /\n|import\s+"([0-9a-f]{16}:.+?)";|(?:\$[0-9a-f]{16}\$exports)|(?:\$[0-9a-f]{16}\$(?:import|importAsync|require)\$[0-9a-f]+(?:\$[0-9a-f]+)?)/g;
33
+
34
+ const BUILTINS = Object.keys(globals.builtin);
35
+ const GLOBALS_BY_CONTEXT = {
36
+ browser: new Set([...BUILTINS, ...Object.keys(globals.browser)]),
37
+ 'web-worker': new Set([...BUILTINS, ...Object.keys(globals.worker)]),
38
+ 'service-worker': new Set([
39
+ ...BUILTINS,
40
+ ...Object.keys(globals.serviceworker),
41
+ ]),
42
+ worklet: new Set([...BUILTINS]),
43
+ node: new Set([...BUILTINS, ...Object.keys(globals.node)]),
44
+ 'electron-main': new Set([...BUILTINS, ...Object.keys(globals.node)]),
45
+ 'electron-renderer': new Set([
46
+ ...BUILTINS,
47
+ ...Object.keys(globals.node),
48
+ ...Object.keys(globals.browser),
49
+ ]),
50
+ };
51
+
52
+ const OUTPUT_FORMATS = {
53
+ esmodule: ESMOutputFormat,
54
+ commonjs: CJSOutputFormat,
55
+ global: GlobalOutputFormat,
56
+ };
57
+
58
+ export interface OutputFormat {
59
+ buildBundlePrelude(): [string, number];
60
+ buildBundlePostlude(): [string, number];
61
+ }
62
+
63
+ export class ScopeHoistingPackager {
64
+ options: PluginOptions;
65
+ bundleGraph: BundleGraph<NamedBundle>;
66
+ bundle: NamedBundle;
67
+ parcelRequireName: string;
68
+ outputFormat: OutputFormat;
69
+ isAsyncBundle: boolean;
70
+ globalNames: $ReadOnlySet<string>;
71
+ assetOutputs: Map<string, {|code: string, map: ?Buffer|}>;
72
+ exportedSymbols: Map<
73
+ string,
74
+ {|
75
+ asset: Asset,
76
+ exportSymbol: string,
77
+ local: string,
78
+ exportAs: Array<string>,
79
+ |},
80
+ > = new Map();
81
+ externals: Map<string, Map<string, string>> = new Map();
82
+ topLevelNames: Map<string, number> = new Map();
83
+ seenAssets: Set<string> = new Set();
84
+ wrappedAssets: Set<string> = new Set();
85
+ hoistedRequires: Map<string, Map<string, string>> = new Map();
86
+ needsPrelude: boolean = false;
87
+ usedHelpers: Set<string> = new Set();
88
+
89
+ constructor(
90
+ options: PluginOptions,
91
+ bundleGraph: BundleGraph<NamedBundle>,
92
+ bundle: NamedBundle,
93
+ parcelRequireName: string,
94
+ ) {
95
+ this.options = options;
96
+ this.bundleGraph = bundleGraph;
97
+ this.bundle = bundle;
98
+ this.parcelRequireName = parcelRequireName;
99
+
100
+ let OutputFormat = OUTPUT_FORMATS[this.bundle.env.outputFormat];
101
+ this.outputFormat = new OutputFormat(this);
102
+
103
+ this.isAsyncBundle =
104
+ this.bundleGraph.hasParentBundleOfType(this.bundle, 'js') &&
105
+ !this.bundle.env.isIsolated() &&
106
+ this.bundle.bundleBehavior !== 'isolated';
107
+
108
+ this.globalNames = GLOBALS_BY_CONTEXT[bundle.env.context];
109
+ }
110
+
111
+ async package(): Promise<{|contents: string, map: ?SourceMap|}> {
112
+ let wrappedAssets = await this.loadAssets();
113
+ this.buildExportedSymbols();
114
+
115
+ // If building a library, the target is actually another bundler rather
116
+ // than the final output that could be loaded in a browser. So, loader
117
+ // runtimes are excluded, and instead we add imports into the entry bundle
118
+ // of each bundle group pointing at the sibling bundles. These can be
119
+ // picked up by another bundler later at which point runtimes will be added.
120
+ if (
121
+ this.bundle.env.isLibrary ||
122
+ this.bundle.env.outputFormat === 'commonjs'
123
+ ) {
124
+ let bundles = this.bundleGraph.getReferencedBundles(this.bundle);
125
+ for (let b of bundles) {
126
+ this.externals.set(relativeBundlePath(this.bundle, b), new Map());
127
+ }
128
+ }
129
+
130
+ let res = '';
131
+ let lineCount = 0;
132
+ let sourceMap = null;
133
+ let processAsset = asset => {
134
+ let [content, map, lines] = this.visitAsset(asset);
135
+ if (sourceMap && map) {
136
+ sourceMap.addSourceMap(map, lineCount);
137
+ } else if (this.bundle.env.sourceMap) {
138
+ sourceMap = map;
139
+ }
140
+
141
+ res += content + '\n';
142
+ lineCount += lines + 1;
143
+ };
144
+
145
+ // Hoist wrapped asset to the top of the bundle to ensure that they are registered
146
+ // before they are used.
147
+ for (let asset of wrappedAssets) {
148
+ if (!this.seenAssets.has(asset.id)) {
149
+ processAsset(asset);
150
+ }
151
+ }
152
+
153
+ // Add each asset that is directly connected to the bundle. Dependencies will be handled
154
+ // by replacing `import` statements in the code.
155
+ this.bundle.traverseAssets((asset, _, actions) => {
156
+ if (this.seenAssets.has(asset.id)) {
157
+ actions.skipChildren();
158
+ return;
159
+ }
160
+
161
+ processAsset(asset);
162
+ actions.skipChildren();
163
+ });
164
+
165
+ let [prelude, preludeLines] = this.buildBundlePrelude();
166
+ res = prelude + res;
167
+ lineCount += preludeLines;
168
+ sourceMap?.offsetLines(1, preludeLines);
169
+
170
+ let entries = this.bundle.getEntryAssets();
171
+ let mainEntry = this.bundle.getMainEntry();
172
+ if (this.isAsyncBundle) {
173
+ // In async bundles we don't want the main entry to execute until we require it
174
+ // as there might be dependencies in a sibling bundle that hasn't loaded yet.
175
+ entries = entries.filter(a => a.id !== mainEntry?.id);
176
+ mainEntry = null;
177
+ }
178
+
179
+ // If any of the entry assets are wrapped, call parcelRequire so they are executed.
180
+ for (let entry of entries) {
181
+ if (this.wrappedAssets.has(entry.id) && !this.isScriptEntry(entry)) {
182
+ let parcelRequire = `parcelRequire(${JSON.stringify(
183
+ this.bundleGraph.getAssetPublicId(entry),
184
+ )});\n`;
185
+
186
+ let entryExports = entry.symbols.get('*')?.local;
187
+ if (
188
+ entryExports &&
189
+ entry === mainEntry &&
190
+ this.exportedSymbols.has(entryExports)
191
+ ) {
192
+ res += `\nvar ${entryExports} = ${parcelRequire}`;
193
+ } else {
194
+ res += `\n${parcelRequire}`;
195
+ }
196
+
197
+ lineCount += 2;
198
+ }
199
+ }
200
+
201
+ let [postlude, postludeLines] = this.outputFormat.buildBundlePostlude();
202
+ res += postlude;
203
+ lineCount += postludeLines;
204
+
205
+ // The entry asset of a script bundle gets hoisted outside the bundle wrapper so that
206
+ // its top-level variables become globals like a real browser script. We need to replace
207
+ // all dependency references for runtimes with a parcelRequire call.
208
+ if (
209
+ this.bundle.env.outputFormat === 'global' &&
210
+ this.bundle.env.sourceType === 'script'
211
+ ) {
212
+ res += '\n';
213
+ lineCount++;
214
+
215
+ let mainEntry = nullthrows(this.bundle.getMainEntry());
216
+ let {code, map: mapBuffer} = nullthrows(
217
+ this.assetOutputs.get(mainEntry.id),
218
+ );
219
+ let map;
220
+ if (mapBuffer) {
221
+ map = new SourceMap(this.options.projectRoot, mapBuffer);
222
+ }
223
+ res += replaceScriptDependencies(
224
+ this.bundleGraph,
225
+ this.bundle,
226
+ code,
227
+ map,
228
+ this.parcelRequireName,
229
+ );
230
+ if (sourceMap && map) {
231
+ sourceMap.addSourceMap(map, lineCount);
232
+ }
233
+ }
234
+
235
+ return {
236
+ contents: res,
237
+ map: sourceMap,
238
+ };
239
+ }
240
+
241
+ async loadAssets(): Promise<Array<Asset>> {
242
+ let queue = new PromiseQueue({maxConcurrent: 32});
243
+ let wrapped = [];
244
+ this.bundle.traverseAssets((asset, shouldWrap) => {
245
+ queue.add(async () => {
246
+ let [code, map] = await Promise.all([
247
+ asset.getCode(),
248
+ this.bundle.env.sourceMap ? asset.getMapBuffer() : null,
249
+ ]);
250
+ return [asset.id, {code, map}];
251
+ });
252
+
253
+ if (
254
+ shouldWrap ||
255
+ asset.meta.shouldWrap ||
256
+ this.isAsyncBundle ||
257
+ this.bundle.env.sourceType === 'script' ||
258
+ this.bundleGraph.isAssetReferenced(this.bundle, asset) ||
259
+ this.bundleGraph
260
+ .getIncomingDependencies(asset)
261
+ .some(dep => dep.meta.shouldWrap && dep.specifierType !== 'url')
262
+ ) {
263
+ this.wrappedAssets.add(asset.id);
264
+ wrapped.push(asset);
265
+ return true;
266
+ }
267
+ });
268
+
269
+ this.assetOutputs = new Map(await queue.run());
270
+ return wrapped;
271
+ }
272
+
273
+ buildExportedSymbols() {
274
+ if (
275
+ this.isAsyncBundle ||
276
+ !this.bundle.env.isLibrary ||
277
+ this.bundle.env.outputFormat !== 'esmodule'
278
+ ) {
279
+ return;
280
+ }
281
+
282
+ // TODO: handle ESM exports of wrapped entry assets...
283
+ let entry = this.bundle.getMainEntry();
284
+ if (entry && !this.wrappedAssets.has(entry.id)) {
285
+ for (let {
286
+ asset,
287
+ exportAs,
288
+ symbol,
289
+ exportSymbol,
290
+ } of this.bundleGraph.getExportedSymbols(entry)) {
291
+ if (typeof symbol === 'string') {
292
+ let symbols = this.exportedSymbols.get(
293
+ symbol === '*' ? nullthrows(entry.symbols.get('*')?.local) : symbol,
294
+ )?.exportAs;
295
+
296
+ if (!symbols) {
297
+ symbols = [];
298
+ this.exportedSymbols.set(symbol, {
299
+ asset,
300
+ exportSymbol,
301
+ local: symbol,
302
+ exportAs: symbols,
303
+ });
304
+ }
305
+
306
+ if (exportAs === '*') {
307
+ exportAs = 'default';
308
+ }
309
+
310
+ symbols.push(exportAs);
311
+ } else if (symbol === null) {
312
+ // TODO `meta.exportsIdentifier[exportSymbol]` should be exported
313
+ // let relativePath = relative(options.projectRoot, asset.filePath);
314
+ // throw getThrowableDiagnosticForNode(
315
+ // md`${relativePath} couldn't be statically analyzed when importing '${exportSymbol}'`,
316
+ // entry.filePath,
317
+ // loc,
318
+ // );
319
+ } else if (symbol !== false) {
320
+ // let relativePath = relative(options.projectRoot, asset.filePath);
321
+ // throw getThrowableDiagnosticForNode(
322
+ // md`${relativePath} does not export '${exportSymbol}'`,
323
+ // entry.filePath,
324
+ // loc,
325
+ // );
326
+ }
327
+ }
328
+ }
329
+ }
330
+
331
+ getTopLevelName(name: string): string {
332
+ name = name.replace(NON_ID_CONTINUE_RE, '');
333
+ if (!ID_START_RE.test(name) || this.globalNames.has(name)) {
334
+ name = '_' + name;
335
+ }
336
+
337
+ let count = this.topLevelNames.get(name);
338
+ if (count == null) {
339
+ this.topLevelNames.set(name, 1);
340
+ return name;
341
+ }
342
+
343
+ this.topLevelNames.set(name, count + 1);
344
+ return name + count;
345
+ }
346
+
347
+ getPropertyAccess(obj: string, property: string): string {
348
+ if (IDENTIFIER_RE.test(property)) {
349
+ return `${obj}.${property}`;
350
+ }
351
+
352
+ return `${obj}[${JSON.stringify(property)}]`;
353
+ }
354
+
355
+ visitAsset(asset: Asset): [string, ?SourceMap, number] {
356
+ invariant(!this.seenAssets.has(asset.id), 'Already visited asset');
357
+ this.seenAssets.add(asset.id);
358
+
359
+ let {code, map} = nullthrows(this.assetOutputs.get(asset.id));
360
+ return this.buildAsset(asset, code, map);
361
+ }
362
+
363
+ buildAsset(
364
+ asset: Asset,
365
+ code: string,
366
+ map: ?Buffer,
367
+ ): [string, ?SourceMap, number] {
368
+ let shouldWrap = this.wrappedAssets.has(asset.id);
369
+ let deps = this.bundleGraph.getDependencies(asset);
370
+
371
+ let sourceMap =
372
+ this.bundle.env.sourceMap && map
373
+ ? new SourceMap(this.options.projectRoot, map)
374
+ : null;
375
+
376
+ // If this asset is skipped, just add dependencies and not the asset's content.
377
+ if (this.shouldSkipAsset(asset)) {
378
+ let depCode = '';
379
+ let lineCount = 0;
380
+ for (let dep of deps) {
381
+ let resolved = this.bundleGraph.getResolvedAsset(dep, this.bundle);
382
+ let skipped = this.bundleGraph.isDependencySkipped(dep);
383
+ if (!resolved || skipped) {
384
+ continue;
385
+ }
386
+
387
+ if (
388
+ this.bundle.hasAsset(resolved) &&
389
+ !this.seenAssets.has(resolved.id)
390
+ ) {
391
+ let [code, map, lines] = this.visitAsset(resolved);
392
+ depCode += code + '\n';
393
+ if (sourceMap && map) {
394
+ sourceMap.addSourceMap(map, lineCount);
395
+ }
396
+ lineCount += lines + 1;
397
+ }
398
+ }
399
+
400
+ return [depCode, sourceMap, lineCount];
401
+ }
402
+
403
+ // TODO: maybe a meta prop?
404
+ if (code.includes('$parcel$global')) {
405
+ this.usedHelpers.add('$parcel$global');
406
+ }
407
+
408
+ let [depMap, replacements] = this.buildReplacements(asset, deps);
409
+ let [prepend, prependLines, append] = this.buildAssetPrelude(asset, deps);
410
+ if (prependLines > 0) {
411
+ sourceMap?.offsetLines(1, prependLines);
412
+ code = prepend + code;
413
+ }
414
+
415
+ code += append;
416
+
417
+ let lineCount = 0;
418
+ let depContent = [];
419
+ if (depMap.size === 0 && replacements.size === 0) {
420
+ // If there are no dependencies or replacements, use a simple function to count the number of lines.
421
+ lineCount = countLines(code) - 1;
422
+ } else {
423
+ // Otherwise, use a regular expression to perform replacements.
424
+ // We need to track how many newlines there are for source maps, replace
425
+ // all import statements with dependency code, and perform inline replacements
426
+ // of all imported symbols with their resolved export symbols. This is all done
427
+ // in a single regex so that we only do one pass over the whole code.
428
+ let offset = 0;
429
+ let columnStartIndex = 0;
430
+ code = code.replace(REPLACEMENT_RE, (m, d, i) => {
431
+ if (m === '\n') {
432
+ columnStartIndex = i + offset + 1;
433
+ lineCount++;
434
+ return '\n';
435
+ }
436
+
437
+ // If we matched an import, replace with the source code for the dependency.
438
+ if (d != null) {
439
+ let dep = depMap.get(d);
440
+ if (!dep) {
441
+ return m;
442
+ }
443
+
444
+ let resolved = this.bundleGraph.getResolvedAsset(dep, this.bundle);
445
+ let skipped = this.bundleGraph.isDependencySkipped(dep);
446
+ if (resolved && !skipped) {
447
+ // Hoist variable declarations for the referenced parcelRequire dependencies
448
+ // after the dependency is declared. This handles the case where the resulting asset
449
+ // is wrapped, but the dependency in this asset is not marked as wrapped. This means
450
+ // that it was imported/required at the top-level, so its side effects should run immediately.
451
+ let [res, lines] = this.getHoistedParcelRequires(
452
+ asset,
453
+ dep,
454
+ resolved,
455
+ );
456
+ let map;
457
+ if (
458
+ this.bundle.hasAsset(resolved) &&
459
+ !this.seenAssets.has(resolved.id)
460
+ ) {
461
+ // If this asset is wrapped, we need to hoist the code for the dependency
462
+ // outside our parcelRequire.register wrapper. This is safe because all
463
+ // assets referenced by this asset will also be wrapped. Otherwise, inline the
464
+ // asset content where the import statement was.
465
+ if (shouldWrap) {
466
+ depContent.push(this.visitAsset(resolved));
467
+ } else {
468
+ let [depCode, depMap, depLines] = this.visitAsset(resolved);
469
+ res = depCode + '\n' + res;
470
+ lines += 1 + depLines;
471
+ map = depMap;
472
+ }
473
+ }
474
+
475
+ // Push this asset's source mappings down by the number of lines in the dependency
476
+ // plus the number of hoisted parcelRequires. Then insert the source map for the dependency.
477
+ if (sourceMap) {
478
+ if (lines > 0) {
479
+ sourceMap.offsetLines(lineCount + 1, lines);
480
+ }
481
+
482
+ if (map) {
483
+ sourceMap.addSourceMap(map, lineCount);
484
+ }
485
+ }
486
+
487
+ lineCount += lines;
488
+ return res;
489
+ }
490
+
491
+ return '';
492
+ }
493
+
494
+ // If it wasn't a dependency, then it was an inline replacement (e.g. $id$import$foo -> $id$export$foo).
495
+ let replacement = replacements.get(m) ?? m;
496
+ if (sourceMap) {
497
+ // Offset the source map columns for this line if the replacement was a different length.
498
+ // This assumes that the match and replacement both do not contain any newlines.
499
+ let lengthDifference = replacement.length - m.length;
500
+ if (lengthDifference !== 0) {
501
+ sourceMap.offsetColumns(
502
+ lineCount + 1,
503
+ i + offset - columnStartIndex + m.length,
504
+ lengthDifference,
505
+ );
506
+ offset += lengthDifference;
507
+ }
508
+ }
509
+ return replacement;
510
+ });
511
+ }
512
+
513
+ // If the asset is wrapped, we need to insert the dependency code outside the parcelRequire.register
514
+ // wrapper. Dependencies must be inserted AFTER the asset is registered so that circular dependencies work.
515
+ if (shouldWrap) {
516
+ // Offset by one line for the parcelRequire.register wrapper.
517
+ sourceMap?.offsetLines(1, 1);
518
+ lineCount++;
519
+
520
+ code = `parcelRequire.register(${JSON.stringify(
521
+ this.bundleGraph.getAssetPublicId(asset),
522
+ )}, function(module, exports) {
523
+ ${code}
524
+ });
525
+ `;
526
+
527
+ lineCount += 2;
528
+
529
+ for (let [depCode, map, lines] of depContent) {
530
+ if (!depCode) continue;
531
+ code += depCode + '\n';
532
+ if (sourceMap && map) {
533
+ sourceMap.addSourceMap(map, lineCount);
534
+ }
535
+ lineCount += lines + 1;
536
+ }
537
+
538
+ this.needsPrelude = true;
539
+ }
540
+
541
+ return [code, sourceMap, lineCount];
542
+ }
543
+
544
+ buildReplacements(
545
+ asset: Asset,
546
+ deps: Array<Dependency>,
547
+ ): [Map<string, Dependency>, Map<string, string>] {
548
+ let assetId = asset.meta.id;
549
+ invariant(typeof assetId === 'string');
550
+
551
+ // Build two maps: one of import specifiers, and one of imported symbols to replace.
552
+ // These will be used to build a regex below.
553
+ let depMap = new Map();
554
+ let replacements = new Map();
555
+ for (let dep of deps) {
556
+ depMap.set(`${assetId}:${getSpecifier(dep)}`, dep);
557
+
558
+ let asyncResolution = this.bundleGraph.resolveAsyncDependency(
559
+ dep,
560
+ this.bundle,
561
+ );
562
+ let resolved =
563
+ asyncResolution?.type === 'asset'
564
+ ? // Prefer the underlying asset over a runtime to load it. It will
565
+ // be wrapped in Promise.resolve() later.
566
+ asyncResolution.value
567
+ : this.bundleGraph.getResolvedAsset(dep, this.bundle);
568
+ if (
569
+ !resolved &&
570
+ !dep.isOptional &&
571
+ !this.bundleGraph.isDependencySkipped(dep)
572
+ ) {
573
+ let external = this.addExternal(dep);
574
+ for (let [imported, {local}] of dep.symbols) {
575
+ // If already imported, just add the already renamed variable to the mapping.
576
+ let renamed = external.get(imported);
577
+ if (renamed && local !== '*') {
578
+ replacements.set(local, renamed);
579
+ continue;
580
+ }
581
+
582
+ // For CJS output, always use a property lookup so that exports remain live.
583
+ // For ESM output, use named imports which are always live.
584
+ if (this.bundle.env.outputFormat === 'commonjs') {
585
+ renamed = external.get('*');
586
+ if (!renamed) {
587
+ renamed = this.getTopLevelName(
588
+ `$${this.bundle.publicId}$${dep.specifier}`,
589
+ );
590
+
591
+ external.set('*', renamed);
592
+ }
593
+
594
+ if (local !== '*') {
595
+ let replacement;
596
+ if (imported === '*') {
597
+ replacement = renamed;
598
+ } else if (imported === 'default') {
599
+ replacement = `($parcel$interopDefault(${renamed}))`;
600
+ this.usedHelpers.add('$parcel$interopDefault');
601
+ } else {
602
+ replacement = this.getPropertyAccess(renamed, imported);
603
+ }
604
+
605
+ replacements.set(local, replacement);
606
+ }
607
+ } else {
608
+ // Rename the specifier so that multiple local imports of the same imported specifier
609
+ // are deduplicated. We have to prefix the imported name with the bundle id so that
610
+ // local variables do not shadow it.
611
+ if (this.exportedSymbols.has(local)) {
612
+ renamed = local;
613
+ } else if (imported === 'default' || imported === '*') {
614
+ renamed = this.getTopLevelName(
615
+ `$${this.bundle.publicId}$${dep.specifier}`,
616
+ );
617
+ } else {
618
+ renamed = this.getTopLevelName(
619
+ `$${this.bundle.publicId}$${imported}`,
620
+ );
621
+ }
622
+
623
+ external.set(imported, renamed);
624
+ if (local !== '*') {
625
+ replacements.set(local, renamed);
626
+ }
627
+ }
628
+ }
629
+ }
630
+
631
+ if (!resolved) {
632
+ continue;
633
+ }
634
+
635
+ for (let [imported, {local}] of dep.symbols) {
636
+ if (local === '*') {
637
+ continue;
638
+ }
639
+
640
+ let symbol = this.getSymbolResolution(asset, resolved, imported, dep);
641
+ replacements.set(
642
+ local,
643
+ // If this was an internalized async asset, wrap in a Promise.resolve.
644
+ asyncResolution?.type === 'asset'
645
+ ? `Promise.resolve(${symbol})`
646
+ : symbol,
647
+ );
648
+ }
649
+
650
+ // Async dependencies need a namespace object even if all used symbols were statically analyzed.
651
+ // This is recorded in the promiseSymbol meta property set by the transformer rather than in
652
+ // symbols so that we don't mark all symbols as used.
653
+ if (dep.priority === 'lazy' && dep.meta.promiseSymbol) {
654
+ let promiseSymbol = dep.meta.promiseSymbol;
655
+ invariant(typeof promiseSymbol === 'string');
656
+ let symbol = this.getSymbolResolution(asset, resolved, '*', dep);
657
+ replacements.set(
658
+ promiseSymbol,
659
+ asyncResolution?.type === 'asset'
660
+ ? `Promise.resolve(${symbol})`
661
+ : symbol,
662
+ );
663
+ }
664
+ }
665
+
666
+ // If this asset is wrapped, we need to replace the exports namespace with `module.exports`,
667
+ // which will be provided to us by the wrapper.
668
+ if (
669
+ this.wrappedAssets.has(asset.id) ||
670
+ (this.bundle.env.outputFormat === 'commonjs' &&
671
+ asset === this.bundle.getMainEntry())
672
+ ) {
673
+ let exportsName = asset.symbols.get('*')?.local || `$${assetId}$exports`;
674
+ replacements.set(exportsName, 'module.exports');
675
+ }
676
+
677
+ return [depMap, replacements];
678
+ }
679
+
680
+ addExternal(dep: Dependency): Map<string, string> {
681
+ if (this.bundle.env.outputFormat === 'global') {
682
+ throw new ThrowableDiagnostic({
683
+ diagnostic: {
684
+ message:
685
+ 'External modules are not supported when building for browser',
686
+ codeFrames: [
687
+ {
688
+ filePath: nullthrows(dep.sourcePath),
689
+ codeHighlights: dep.loc
690
+ ? [
691
+ {
692
+ start: dep.loc.start,
693
+ end: dep.loc.end,
694
+ },
695
+ ]
696
+ : [],
697
+ },
698
+ ],
699
+ },
700
+ });
701
+ }
702
+
703
+ // Map of DependencySpecifier -> Map<ExportedSymbol, Identifier>>
704
+ let external = this.externals.get(dep.specifier);
705
+ if (!external) {
706
+ external = new Map();
707
+ this.externals.set(dep.specifier, external);
708
+ }
709
+
710
+ return external;
711
+ }
712
+
713
+ getSymbolResolution(
714
+ parentAsset: Asset,
715
+ resolved: Asset,
716
+ imported: string,
717
+ dep?: Dependency,
718
+ ): string {
719
+ let {
720
+ asset: resolvedAsset,
721
+ exportSymbol,
722
+ symbol,
723
+ } = this.bundleGraph.getSymbolResolution(resolved, imported, this.bundle);
724
+ if (resolvedAsset.type !== 'js') {
725
+ // Graceful fallback for non-js imports
726
+ return '{}';
727
+ }
728
+ let isWrapped =
729
+ !this.bundle.hasAsset(resolvedAsset) ||
730
+ (this.wrappedAssets.has(resolvedAsset.id) &&
731
+ resolvedAsset !== parentAsset);
732
+ let staticExports = resolvedAsset.meta.staticExports !== false;
733
+ let publicId = this.bundleGraph.getAssetPublicId(resolvedAsset);
734
+
735
+ // If the rsolved asset is wrapped, but imported at the top-level by this asset,
736
+ // then we hoist parcelRequire calls to the top of this asset so side effects run immediately.
737
+ if (isWrapped && dep && !dep?.meta.shouldWrap && symbol !== false) {
738
+ let hoisted = this.hoistedRequires.get(dep.id);
739
+ if (!hoisted) {
740
+ hoisted = new Map();
741
+ this.hoistedRequires.set(dep.id, hoisted);
742
+ }
743
+
744
+ hoisted.set(
745
+ resolvedAsset.id,
746
+ `var $${publicId} = parcelRequire(${JSON.stringify(publicId)});`,
747
+ );
748
+ }
749
+
750
+ if (isWrapped) {
751
+ this.needsPrelude = true;
752
+ }
753
+
754
+ // If this is an ESM default import of a CJS module with a `default` symbol,
755
+ // and no __esModule flag, we need to resolve to the namespace instead.
756
+ let isDefaultInterop =
757
+ exportSymbol === 'default' &&
758
+ staticExports &&
759
+ !isWrapped &&
760
+ (dep?.meta.kind === 'Import' || dep?.meta.kind === 'Export') &&
761
+ resolvedAsset.symbols.hasExportSymbol('*') &&
762
+ resolvedAsset.symbols.hasExportSymbol('default') &&
763
+ !resolvedAsset.symbols.hasExportSymbol('__esModule');
764
+
765
+ // Find the namespace object for the resolved module. If wrapped and this
766
+ // is an inline require (not top-level), use a parcelRequire call, otherwise
767
+ // the hoisted variable declared above. Otherwise, if not wrapped, use the
768
+ // namespace export symbol.
769
+ let assetId = resolvedAsset.meta.id;
770
+ invariant(typeof assetId === 'string');
771
+ let obj =
772
+ isWrapped && (!dep || dep?.meta.shouldWrap)
773
+ ? // Wrap in extra parenthesis to not change semantics, e.g.`new (parcelRequire("..."))()`.
774
+ `(parcelRequire(${JSON.stringify(publicId)}))`
775
+ : isWrapped && dep
776
+ ? `$${publicId}`
777
+ : resolvedAsset.symbols.get('*')?.local || `$${assetId}$exports`;
778
+
779
+ if (imported === '*' || exportSymbol === '*' || isDefaultInterop) {
780
+ // Resolve to the namespace object if requested or this is a CJS default interop reqiure.
781
+ return obj;
782
+ } else if (
783
+ (!staticExports || isWrapped || !symbol) &&
784
+ resolvedAsset !== parentAsset
785
+ ) {
786
+ // If the resolved asset is wrapped or has non-static exports,
787
+ // we need to use a member access off the namespace object rather
788
+ // than a direct reference. If importing default from a CJS module,
789
+ // use a helper to check the __esModule flag at runtime.
790
+ let kind = dep?.meta.kind;
791
+ if (
792
+ (!dep || kind === 'Import' || kind === 'Export') &&
793
+ exportSymbol === 'default' &&
794
+ resolvedAsset.symbols.hasExportSymbol('*') &&
795
+ this.needsDefaultInterop(resolvedAsset)
796
+ ) {
797
+ this.usedHelpers.add('$parcel$interopDefault');
798
+ return `(/*@__PURE__*/$parcel$interopDefault(${obj}))`;
799
+ } else {
800
+ return this.getPropertyAccess(obj, exportSymbol);
801
+ }
802
+ } else if (!symbol) {
803
+ invariant(false, 'Asset was skipped or not found.');
804
+ } else {
805
+ return symbol;
806
+ }
807
+ }
808
+
809
+ getHoistedParcelRequires(
810
+ parentAsset: Asset,
811
+ dep: Dependency,
812
+ resolved: Asset,
813
+ ): [string, number] {
814
+ if (resolved.type !== 'js') {
815
+ return ['', 0];
816
+ }
817
+
818
+ let hoisted = this.hoistedRequires.get(dep.id);
819
+ let res = '';
820
+ let lineCount = 0;
821
+ let isWrapped =
822
+ !this.bundle.hasAsset(resolved) ||
823
+ (this.wrappedAssets.has(resolved.id) && resolved !== parentAsset);
824
+
825
+ // If the resolved asset is wrapped and is imported in the top-level by this asset,
826
+ // we need to run side effects when this asset runs. If the resolved asset is not
827
+ // the first one in the hoisted requires, we need to insert a parcelRequire here
828
+ // so it runs first.
829
+ if (
830
+ isWrapped &&
831
+ !dep.meta.shouldWrap &&
832
+ (!hoisted || hoisted.keys().next().value !== resolved.id) &&
833
+ !this.bundleGraph.isDependencySkipped(dep) &&
834
+ !this.shouldSkipAsset(resolved)
835
+ ) {
836
+ this.needsPrelude = true;
837
+ res += `parcelRequire(${JSON.stringify(
838
+ this.bundleGraph.getAssetPublicId(resolved),
839
+ )});`;
840
+ }
841
+
842
+ if (hoisted) {
843
+ this.needsPrelude = true;
844
+ res += '\n' + [...hoisted.values()].join('\n');
845
+ lineCount += hoisted.size;
846
+ }
847
+
848
+ return [res, lineCount];
849
+ }
850
+
851
+ buildAssetPrelude(
852
+ asset: Asset,
853
+ deps: Array<Dependency>,
854
+ ): [string, number, string] {
855
+ let prepend = '';
856
+ let prependLineCount = 0;
857
+ let append = '';
858
+
859
+ let shouldWrap = this.wrappedAssets.has(asset.id);
860
+ let usedSymbols = nullthrows(this.bundleGraph.getUsedSymbols(asset));
861
+ let assetId = asset.meta.id;
862
+ invariant(typeof assetId === 'string');
863
+
864
+ // If the asset has a namespace export symbol, it is CommonJS.
865
+ // If there's no __esModule flag, and default is a used symbol, we need
866
+ // to insert an interop helper.
867
+ let defaultInterop =
868
+ asset.symbols.hasExportSymbol('*') &&
869
+ usedSymbols.has('default') &&
870
+ !asset.symbols.hasExportSymbol('__esModule');
871
+
872
+ let usedNamespace =
873
+ // If the asset has * in its used symbols, we might need the exports namespace.
874
+ // The one case where this isn't true is in ESM library entries, where the only
875
+ // dependency on * is the entry dependency. In this case, we will use ESM exports
876
+ // instead of the namespace object.
877
+ (usedSymbols.has('*') &&
878
+ (this.bundle.env.outputFormat !== 'esmodule' ||
879
+ !this.bundle.env.isLibrary ||
880
+ asset !== this.bundle.getMainEntry() ||
881
+ this.bundleGraph
882
+ .getIncomingDependencies(asset)
883
+ .some(
884
+ dep =>
885
+ !dep.isEntry &&
886
+ nullthrows(this.bundleGraph.getUsedSymbols(dep)).has('*'),
887
+ ))) ||
888
+ // If a symbol is imported (used) from a CJS asset but isn't listed in the symbols,
889
+ // we fallback on the namespace object.
890
+ (asset.symbols.hasExportSymbol('*') &&
891
+ [...usedSymbols].some(s => !asset.symbols.hasExportSymbol(s))) ||
892
+ // If the exports has this asset's namespace (e.g. ESM output from CJS input),
893
+ // include the namespace object for the default export.
894
+ this.exportedSymbols.has(`$${assetId}$exports`);
895
+
896
+ // If the asset doesn't have static exports, should wrap, the namespace is used,
897
+ // or we need default interop, then we need to synthesize a namespace object for
898
+ // this asset.
899
+ if (
900
+ asset.meta.staticExports === false ||
901
+ shouldWrap ||
902
+ usedNamespace ||
903
+ defaultInterop
904
+ ) {
905
+ // Insert a declaration for the exports namespace object. If the asset is wrapped
906
+ // we don't need to do this, because we'll use the `module.exports` object provided
907
+ // by the wrapper instead. This is also true of CommonJS entry assets, which will use
908
+ // the `module.exports` object provided by CJS.
909
+ if (
910
+ !shouldWrap &&
911
+ (this.bundle.env.outputFormat !== 'commonjs' ||
912
+ asset !== this.bundle.getMainEntry())
913
+ ) {
914
+ prepend += `var $${assetId}$exports = {};\n`;
915
+ prependLineCount++;
916
+ }
917
+
918
+ // Insert the __esModule interop flag for this module if it has a `default` export
919
+ // and the namespace symbol is used.
920
+ // TODO: only if required by CJS?
921
+ if (asset.symbols.hasExportSymbol('default') && usedSymbols.has('*')) {
922
+ prepend += `\n$parcel$defineInteropFlag($${assetId}$exports);\n`;
923
+ prependLineCount += 2;
924
+ this.usedHelpers.add('$parcel$defineInteropFlag');
925
+ }
926
+
927
+ // Find the used exports of this module. This is based on the used symbols of
928
+ // incoming dependencies rather than the asset's own used exports so that we include
929
+ // re-exported symbols rather than only symbols declared in this asset.
930
+ let incomingDeps = this.bundleGraph.getIncomingDependencies(asset);
931
+ let usedExports = [...asset.symbols.exportSymbols()].filter(symbol => {
932
+ if (symbol === '*') {
933
+ return false;
934
+ }
935
+
936
+ // If we need default interop, then all symbols are needed because the `default`
937
+ // symbol really maps to the whole namespace.
938
+ if (defaultInterop) {
939
+ return true;
940
+ }
941
+
942
+ let unused = incomingDeps.every(d => {
943
+ let symbols = nullthrows(this.bundleGraph.getUsedSymbols(d));
944
+ return !symbols.has(symbol) && !symbols.has('*');
945
+ });
946
+ return !unused;
947
+ });
948
+
949
+ if (usedExports.length > 0) {
950
+ // Insert $parcel$export calls for each of the used exports. This creates a getter/setter
951
+ // for the symbol so that when the value changes the object property also changes. This is
952
+ // required to simulate ESM live bindings. It's easier to do it this way rather than inserting
953
+ // additional assignments after each mutation of the original binding.
954
+ prepend += `\n${usedExports
955
+ .map(exp => {
956
+ let resolved = this.getSymbolResolution(asset, asset, exp);
957
+ let get = this.buildFunctionExpression([], resolved);
958
+ let set = asset.meta.hasCJSExports
959
+ ? ', ' + this.buildFunctionExpression(['v'], `${resolved} = v`)
960
+ : '';
961
+ return `$parcel$export($${assetId}$exports, ${JSON.stringify(
962
+ exp,
963
+ )}, ${get}${set});`;
964
+ })
965
+ .join('\n')}\n`;
966
+ this.usedHelpers.add('$parcel$export');
967
+ prependLineCount += 1 + usedExports.length;
968
+ }
969
+
970
+ // Find wildcard re-export dependencies, and make sure their exports are also included in ours.
971
+ for (let dep of deps) {
972
+ let resolved = this.bundleGraph.getResolvedAsset(dep, this.bundle);
973
+ if (dep.isOptional || this.bundleGraph.isDependencySkipped(dep)) {
974
+ continue;
975
+ }
976
+
977
+ let isWrapped = resolved && resolved.meta.shouldWrap;
978
+
979
+ for (let [imported, {local}] of dep.symbols) {
980
+ if (imported === '*' && local === '*') {
981
+ if (!resolved) {
982
+ // Re-exporting an external module. This should have already been handled in buildReplacements.
983
+ let external = nullthrows(
984
+ nullthrows(this.externals.get(dep.specifier)).get('*'),
985
+ );
986
+ append += `$parcel$exportWildcard($${assetId}$exports, ${external});\n`;
987
+ this.usedHelpers.add('$parcel$exportWildcard');
988
+ continue;
989
+ }
990
+
991
+ // If the resolved asset has an exports object, use the $parcel$exportWildcard helper
992
+ // to re-export all symbols. Otherwise, if there's no namespace object available, add
993
+ // $parcel$export calls for each used symbol of the dependency.
994
+ if (
995
+ isWrapped ||
996
+ resolved.meta.staticExports === false ||
997
+ nullthrows(this.bundleGraph.getUsedSymbols(resolved)).has('*') ||
998
+ // an empty asset
999
+ (!resolved.meta.hasCJSExports &&
1000
+ resolved.symbols.hasExportSymbol('*'))
1001
+ ) {
1002
+ let obj = this.getSymbolResolution(asset, resolved, '*', dep);
1003
+ append += `$parcel$exportWildcard($${assetId}$exports, ${obj});\n`;
1004
+ this.usedHelpers.add('$parcel$exportWildcard');
1005
+ } else {
1006
+ for (let symbol of nullthrows(
1007
+ this.bundleGraph.getUsedSymbols(dep),
1008
+ )) {
1009
+ if (
1010
+ symbol === 'default' || // `export * as ...` does not include the default export
1011
+ symbol === '__esModule'
1012
+ ) {
1013
+ continue;
1014
+ }
1015
+
1016
+ let resolvedSymbol = this.getSymbolResolution(
1017
+ asset,
1018
+ resolved,
1019
+ symbol,
1020
+ );
1021
+ let get = this.buildFunctionExpression([], resolvedSymbol);
1022
+ let set = asset.meta.hasCJSExports
1023
+ ? ', ' +
1024
+ this.buildFunctionExpression(['v'], `${resolvedSymbol} = v`)
1025
+ : '';
1026
+ prepend += `$parcel$export($${assetId}$exports, ${JSON.stringify(
1027
+ symbol,
1028
+ )}, ${get}${set});\n`;
1029
+ this.usedHelpers.add('$parcel$export');
1030
+ prependLineCount++;
1031
+ }
1032
+ }
1033
+ }
1034
+ }
1035
+ }
1036
+ }
1037
+
1038
+ return [prepend, prependLineCount, append];
1039
+ }
1040
+
1041
+ buildBundlePrelude(): [string, number] {
1042
+ let enableSourceMaps = this.bundle.env.sourceMap;
1043
+ let res = '';
1044
+ let lines = 0;
1045
+
1046
+ // Add hashbang if the entry asset recorded an interpreter.
1047
+ let mainEntry = this.bundle.getMainEntry();
1048
+ if (
1049
+ mainEntry &&
1050
+ !this.isAsyncBundle &&
1051
+ !this.bundle.target.env.isBrowser()
1052
+ ) {
1053
+ let interpreter = mainEntry.meta.interpreter;
1054
+ invariant(interpreter == null || typeof interpreter === 'string');
1055
+ if (interpreter != null) {
1056
+ res += `#!${interpreter}\n`;
1057
+ lines++;
1058
+ }
1059
+ }
1060
+
1061
+ // The output format may have specific things to add at the start of the bundle (e.g. imports).
1062
+ let [outputFormatPrelude, outputFormatLines] =
1063
+ this.outputFormat.buildBundlePrelude();
1064
+ res += outputFormatPrelude;
1065
+ lines += outputFormatLines;
1066
+
1067
+ // Add used helpers.
1068
+ if (this.needsPrelude) {
1069
+ this.usedHelpers.add('$parcel$global');
1070
+ }
1071
+
1072
+ for (let helper of this.usedHelpers) {
1073
+ res += helpers[helper];
1074
+ if (enableSourceMaps) {
1075
+ lines += countLines(helpers[helper]) - 1;
1076
+ }
1077
+ }
1078
+
1079
+ if (this.needsPrelude) {
1080
+ // Add the prelude if this is potentially the first JS bundle to load in a
1081
+ // particular context (e.g. entry scripts in HTML, workers, etc.).
1082
+ let parentBundles = this.bundleGraph.getParentBundles(this.bundle);
1083
+ let mightBeFirstJS =
1084
+ parentBundles.length === 0 ||
1085
+ parentBundles.some(b => b.type !== 'js') ||
1086
+ this.bundleGraph
1087
+ .getBundleGroupsContainingBundle(this.bundle)
1088
+ .some(g => this.bundleGraph.isEntryBundleGroup(g)) ||
1089
+ this.bundle.env.isIsolated() ||
1090
+ this.bundle.bundleBehavior === 'isolated';
1091
+
1092
+ if (mightBeFirstJS) {
1093
+ let preludeCode = prelude(this.parcelRequireName);
1094
+ res += preludeCode;
1095
+ if (enableSourceMaps) {
1096
+ lines += countLines(preludeCode) - 1;
1097
+ }
1098
+ } else {
1099
+ // Otherwise, get the current parcelRequire global.
1100
+ res += `var parcelRequire = $parcel$global[${JSON.stringify(
1101
+ this.parcelRequireName,
1102
+ )}];\n`;
1103
+ lines++;
1104
+ }
1105
+ }
1106
+
1107
+ // Add importScripts for sibling bundles in workers.
1108
+ if (this.bundle.env.isWorker() || this.bundle.env.isWorklet()) {
1109
+ let importScripts = '';
1110
+ let bundles = this.bundleGraph.getReferencedBundles(this.bundle);
1111
+ for (let b of bundles) {
1112
+ if (this.bundle.env.outputFormat === 'esmodule') {
1113
+ // importScripts() is not allowed in native ES module workers.
1114
+ importScripts += `import "${relativeBundlePath(this.bundle, b)}";\n`;
1115
+ } else {
1116
+ importScripts += `importScripts("${relativeBundlePath(
1117
+ this.bundle,
1118
+ b,
1119
+ )}");\n`;
1120
+ }
1121
+ }
1122
+
1123
+ res += importScripts;
1124
+ lines += bundles.length;
1125
+ }
1126
+
1127
+ return [res, lines];
1128
+ }
1129
+
1130
+ needsDefaultInterop(asset: Asset): boolean {
1131
+ if (
1132
+ asset.symbols.hasExportSymbol('*') &&
1133
+ !asset.symbols.hasExportSymbol('default')
1134
+ ) {
1135
+ let deps = this.bundleGraph.getIncomingDependencies(asset);
1136
+ return deps.some(
1137
+ dep =>
1138
+ this.bundle.hasDependency(dep) &&
1139
+ // dep.meta.isES6Module &&
1140
+ dep.symbols.hasExportSymbol('default'),
1141
+ );
1142
+ }
1143
+
1144
+ return false;
1145
+ }
1146
+
1147
+ shouldSkipAsset(asset: Asset): boolean {
1148
+ if (this.isScriptEntry(asset)) {
1149
+ return true;
1150
+ }
1151
+
1152
+ return (
1153
+ asset.sideEffects === false &&
1154
+ nullthrows(this.bundleGraph.getUsedSymbols(asset)).size == 0 &&
1155
+ !this.bundleGraph.isAssetReferenced(this.bundle, asset)
1156
+ );
1157
+ }
1158
+
1159
+ isScriptEntry(asset: Asset): boolean {
1160
+ return (
1161
+ this.bundle.env.outputFormat === 'global' &&
1162
+ this.bundle.env.sourceType === 'script' &&
1163
+ asset === this.bundle.getMainEntry()
1164
+ );
1165
+ }
1166
+
1167
+ buildFunctionExpression(args: Array<string>, expr: string): string {
1168
+ return this.bundle.env.supports('arrow-functions', true)
1169
+ ? `(${args.join(', ')}) => ${expr}`
1170
+ : `function (${args.join(', ')}) { return ${expr}; }`;
1171
+ }
1172
+ }