@parcel/packager-js 2.0.0-beta.3.1 → 2.0.0-dev.1510

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