@parcel/packager-js 2.0.0-beta.3.1 → 2.0.0-nightly.1006

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.
@@ -19,12 +19,18 @@ import {ESMOutputFormat} from './ESMOutputFormat';
19
19
  import {CJSOutputFormat} from './CJSOutputFormat';
20
20
  import {GlobalOutputFormat} from './GlobalOutputFormat';
21
21
  import {prelude, helpers} from './helpers';
22
+ import {replaceScriptDependencies, getSpecifier} from './utils';
22
23
 
23
24
  // https://262.ecma-international.org/6.0/#sec-names-and-keywords
24
25
  const IDENTIFIER_RE = /^[$_\p{ID_Start}][$_\u200C\u200D\p{ID_Continue}]*$/u;
25
26
  const ID_START_RE = /^[$_\p{ID_Start}]/u;
26
27
  const NON_ID_CONTINUE_RE = /[^$_\u200C\u200D\p{ID_Continue}]/gu;
27
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
+
28
34
  const BUILTINS = Object.keys(globals.builtin);
29
35
  const GLOBALS_BY_CONTEXT = {
30
36
  browser: new Set([...BUILTINS, ...Object.keys(globals.browser)]),
@@ -33,6 +39,7 @@ const GLOBALS_BY_CONTEXT = {
33
39
  ...BUILTINS,
34
40
  ...Object.keys(globals.serviceworker),
35
41
  ]),
42
+ worklet: new Set([...BUILTINS]),
36
43
  node: new Set([...BUILTINS, ...Object.keys(globals.node)]),
37
44
  'electron-main': new Set([...BUILTINS, ...Object.keys(globals.node)]),
38
45
  'electron-renderer': new Set([
@@ -50,7 +57,7 @@ const OUTPUT_FORMATS = {
50
57
 
51
58
  export interface OutputFormat {
52
59
  buildBundlePrelude(): [string, number];
53
- buildBundlePostlude(): string;
60
+ buildBundlePostlude(): [string, number];
54
61
  }
55
62
 
56
63
  export class ScopeHoistingPackager {
@@ -64,7 +71,12 @@ export class ScopeHoistingPackager {
64
71
  assetOutputs: Map<string, {|code: string, map: ?Buffer|}>;
65
72
  exportedSymbols: Map<
66
73
  string,
67
- Array<{|exportAs: string, local: string|}>,
74
+ {|
75
+ asset: Asset,
76
+ exportSymbol: string,
77
+ local: string,
78
+ exportAs: Array<string>,
79
+ |},
68
80
  > = new Map();
69
81
  externals: Map<string, Map<string, string>> = new Map();
70
82
  topLevelNames: Map<string, number> = new Map();
@@ -91,13 +103,13 @@ export class ScopeHoistingPackager {
91
103
  this.isAsyncBundle =
92
104
  this.bundleGraph.hasParentBundleOfType(this.bundle, 'js') &&
93
105
  !this.bundle.env.isIsolated() &&
94
- !this.bundle.getMainEntry()?.isIsolated;
106
+ this.bundle.bundleBehavior !== 'isolated';
95
107
 
96
108
  this.globalNames = GLOBALS_BY_CONTEXT[bundle.env.context];
97
109
  }
98
110
 
99
111
  async package(): Promise<{|contents: string, map: ?SourceMap|}> {
100
- await this.loadAssets();
112
+ let wrappedAssets = await this.loadAssets();
101
113
  this.buildExportedSymbols();
102
114
 
103
115
  // If building a library, the target is actually another bundler rather
@@ -115,31 +127,44 @@ export class ScopeHoistingPackager {
115
127
  }
116
128
  }
117
129
 
118
- // Add each asset that is directly connected to the bundle. Dependencies will be handled
119
- // by replacing `import` statements in the code.
120
130
  let res = '';
121
131
  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
-
132
+ let sourceMap = null;
133
+ let processAsset = asset => {
131
134
  let [content, map, lines] = this.visitAsset(asset);
132
135
  if (sourceMap && map) {
133
136
  sourceMap.addSourceMap(map, lineCount);
137
+ } else if (this.bundle.env.sourceMap) {
138
+ sourceMap = map;
134
139
  }
135
140
 
136
141
  res += content + '\n';
137
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);
138
162
  actions.skipChildren();
139
163
  });
140
164
 
141
165
  let [prelude, preludeLines] = this.buildBundlePrelude();
142
166
  res = prelude + res;
167
+ lineCount += preludeLines;
143
168
  sourceMap?.offsetLines(1, preludeLines);
144
169
 
145
170
  let entries = this.bundle.getEntryAssets();
@@ -153,7 +178,7 @@ export class ScopeHoistingPackager {
153
178
 
154
179
  // If any of the entry assets are wrapped, call parcelRequire so they are executed.
155
180
  for (let entry of entries) {
156
- if (this.wrappedAssets.has(entry.id)) {
181
+ if (this.wrappedAssets.has(entry.id) && !this.isScriptEntry(entry)) {
157
182
  let parcelRequire = `parcelRequire(${JSON.stringify(
158
183
  this.bundleGraph.getAssetPublicId(entry),
159
184
  )});\n`;
@@ -168,10 +193,44 @@ export class ScopeHoistingPackager {
168
193
  } else {
169
194
  res += `\n${parcelRequire}`;
170
195
  }
196
+
197
+ lineCount += 2;
171
198
  }
172
199
  }
173
200
 
174
- res += this.outputFormat.buildBundlePostlude();
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
+ }
175
234
 
176
235
  return {
177
236
  contents: res,
@@ -179,78 +238,76 @@ export class ScopeHoistingPackager {
179
238
  };
180
239
  }
181
240
 
182
- async loadAssets() {
241
+ async loadAssets(): Promise<Array<Asset>> {
183
242
  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
- }
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;
220
266
  }
221
267
  });
222
268
 
223
269
  this.assetOutputs = new Map(await queue.run());
270
+ return wrapped;
224
271
  }
225
272
 
226
273
  buildExportedSymbols() {
227
- if (this.isAsyncBundle || this.bundle.env.outputFormat !== 'esmodule') {
274
+ if (
275
+ this.isAsyncBundle ||
276
+ !this.bundle.env.isLibrary ||
277
+ this.bundle.env.outputFormat !== 'esmodule'
278
+ ) {
228
279
  return;
229
280
  }
230
281
 
282
+ // TODO: handle ESM exports of wrapped entry assets...
231
283
  let entry = this.bundle.getMainEntry();
232
- if (entry) {
233
- for (let {exportAs, symbol} of this.bundleGraph.getExportedSymbols(
234
- entry,
235
- )) {
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)) {
236
291
  if (typeof symbol === 'string') {
237
292
  let symbols = this.exportedSymbols.get(
238
293
  symbol === '*' ? nullthrows(entry.symbols.get('*')?.local) : symbol,
239
- );
294
+ )?.exportAs;
240
295
 
241
- let local = symbol;
242
- if (symbols) {
243
- local = symbols[0].local;
244
- } else {
296
+ if (!symbols) {
245
297
  symbols = [];
246
- this.exportedSymbols.set(symbol, symbols);
298
+ this.exportedSymbols.set(symbol, {
299
+ asset,
300
+ exportSymbol,
301
+ local: symbol,
302
+ exportAs: symbols,
303
+ });
247
304
  }
248
305
 
249
306
  if (exportAs === '*') {
250
307
  exportAs = 'default';
251
308
  }
252
309
 
253
- symbols.push({exportAs, local});
310
+ symbols.push(exportAs);
254
311
  } else if (symbol === null) {
255
312
  // TODO `meta.exportsIdentifier[exportSymbol]` should be exported
256
313
  // let relativePath = relative(options.projectRoot, asset.filePath);
@@ -287,6 +344,14 @@ export class ScopeHoistingPackager {
287
344
  return name + count;
288
345
  }
289
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
+
290
355
  visitAsset(asset: Asset): [string, ?SourceMap, number] {
291
356
  invariant(!this.seenAssets.has(asset.id), 'Already visited asset');
292
357
  this.seenAssets.add(asset.id);
@@ -303,22 +368,17 @@ export class ScopeHoistingPackager {
303
368
  let shouldWrap = this.wrappedAssets.has(asset.id);
304
369
  let deps = this.bundleGraph.getDependencies(asset);
305
370
 
306
- let sourceMap = this.bundle.env.sourceMap
307
- ? new SourceMap(this.options.projectRoot)
308
- : null;
309
- if (sourceMap && map) {
310
- sourceMap?.addBuffer(map);
311
- }
371
+ let sourceMap =
372
+ this.bundle.env.sourceMap && map
373
+ ? new SourceMap(this.options.projectRoot, map)
374
+ : null;
312
375
 
313
376
  // If this asset is skipped, just add dependencies and not the asset's content.
314
377
  if (this.shouldSkipAsset(asset)) {
315
378
  let depCode = '';
316
379
  let lineCount = 0;
317
380
  for (let dep of deps) {
318
- let resolved = this.bundleGraph.getDependencyResolution(
319
- dep,
320
- this.bundle,
321
- );
381
+ let resolved = this.bundleGraph.getResolvedAsset(dep, this.bundle);
322
382
  let skipped = this.bundleGraph.isDependencySkipped(dep);
323
383
  if (!resolved || skipped) {
324
384
  continue;
@@ -354,107 +414,101 @@ export class ScopeHoistingPackager {
354
414
 
355
415
  code += append;
356
416
 
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
417
  let lineCount = 0;
375
- let offset = 0;
376
- let columnStartIndex = 0;
377
418
  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
- }
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
+ }
384
436
 
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
- }
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;
420
442
  }
421
443
 
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);
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
+ }
427
473
  }
428
474
 
429
- if (map) {
430
- sourceMap.addSourceMap(map, lineCount, 0);
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
+ }
431
485
  }
486
+
487
+ lineCount += lines;
488
+ return res;
432
489
  }
433
490
 
434
- lineCount += lines;
435
- return res;
491
+ return '';
436
492
  }
437
493
 
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;
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
+ }
454
508
  }
455
- }
456
- return replacement;
457
- });
509
+ return replacement;
510
+ });
511
+ }
458
512
 
459
513
  // If the asset is wrapped, we need to insert the dependency code outside the parcelRequire.register
460
514
  // wrapper. Dependencies must be inserted AFTER the asset is registered so that circular dependencies work.
@@ -476,7 +530,7 @@ ${code}
476
530
  if (!depCode) continue;
477
531
  code += depCode + '\n';
478
532
  if (sourceMap && map) {
479
- sourceMap.addSourceMap(map, lineCount, 0);
533
+ sourceMap.addSourceMap(map, lineCount);
480
534
  }
481
535
  lineCount += lines + 1;
482
536
  }
@@ -499,7 +553,7 @@ ${code}
499
553
  let depMap = new Map();
500
554
  let replacements = new Map();
501
555
  for (let dep of deps) {
502
- depMap.set(`${assetId}:${dep.moduleSpecifier}`, dep);
556
+ depMap.set(`${assetId}:${getSpecifier(dep)}`, dep);
503
557
 
504
558
  let asyncResolution = this.bundleGraph.resolveAsyncDependency(
505
559
  dep,
@@ -510,7 +564,7 @@ ${code}
510
564
  ? // Prefer the underlying asset over a runtime to load it. It will
511
565
  // be wrapped in Promise.resolve() later.
512
566
  asyncResolution.value
513
- : this.bundleGraph.getDependencyResolution(dep, this.bundle);
567
+ : this.bundleGraph.getResolvedAsset(dep, this.bundle);
514
568
  if (
515
569
  !resolved &&
516
570
  !dep.isOptional &&
@@ -525,29 +579,56 @@ ${code}
525
579
  continue;
526
580
  }
527
581
 
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
- );
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
+ }
537
607
  } else {
538
- renamed = this.getTopLevelName(
539
- `$${this.bundle.publicId}$${imported}`,
540
- );
541
- }
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
+ }
542
622
 
543
- external.set(imported, renamed);
544
- if (local !== '*') {
545
- replacements.set(local, renamed);
623
+ external.set(imported, renamed);
624
+ if (local !== '*') {
625
+ replacements.set(local, renamed);
626
+ }
546
627
  }
547
628
  }
548
629
  }
549
630
 
550
- if (!resolved || resolved === asset) {
631
+ if (!resolved) {
551
632
  continue;
552
633
  }
553
634
 
@@ -556,7 +637,7 @@ ${code}
556
637
  continue;
557
638
  }
558
639
 
559
- let symbol = this.resolveSymbol(asset, resolved, imported, dep);
640
+ let symbol = this.getSymbolResolution(asset, resolved, imported, dep);
560
641
  replacements.set(
561
642
  local,
562
643
  // If this was an internalized async asset, wrap in a Promise.resolve.
@@ -569,10 +650,10 @@ ${code}
569
650
  // Async dependencies need a namespace object even if all used symbols were statically analyzed.
570
651
  // This is recorded in the promiseSymbol meta property set by the transformer rather than in
571
652
  // symbols so that we don't mark all symbols as used.
572
- if (dep.isAsync && dep.meta.promiseSymbol) {
653
+ if (dep.priority === 'lazy' && dep.meta.promiseSymbol) {
573
654
  let promiseSymbol = dep.meta.promiseSymbol;
574
655
  invariant(typeof promiseSymbol === 'string');
575
- let symbol = this.resolveSymbol(asset, resolved, '*', dep);
656
+ let symbol = this.getSymbolResolution(asset, resolved, '*', dep);
576
657
  replacements.set(
577
658
  promiseSymbol,
578
659
  asyncResolution?.type === 'asset'
@@ -602,32 +683,34 @@ ${code}
602
683
  diagnostic: {
603
684
  message:
604
685
  '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
- },
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
+ ],
616
699
  },
617
700
  });
618
701
  }
619
702
 
620
- // Map of ModuleSpecifier -> Map<ExportedSymbol, Identifier>>
621
- let external = this.externals.get(dep.moduleSpecifier);
703
+ // Map of DependencySpecifier -> Map<ExportedSymbol, Identifier>>
704
+ let external = this.externals.get(dep.specifier);
622
705
  if (!external) {
623
706
  external = new Map();
624
- this.externals.set(dep.moduleSpecifier, external);
707
+ this.externals.set(dep.specifier, external);
625
708
  }
626
709
 
627
710
  return external;
628
711
  }
629
712
 
630
- resolveSymbol(
713
+ getSymbolResolution(
631
714
  parentAsset: Asset,
632
715
  resolved: Asset,
633
716
  imported: string,
@@ -637,7 +720,11 @@ ${code}
637
720
  asset: resolvedAsset,
638
721
  exportSymbol,
639
722
  symbol,
640
- } = this.bundleGraph.resolveSymbol(resolved, imported, this.bundle);
723
+ } = this.bundleGraph.getSymbolResolution(resolved, imported, this.bundle);
724
+ if (resolvedAsset.type !== 'js') {
725
+ // Graceful fallback for non-js imports
726
+ return '{}';
727
+ }
641
728
  let isWrapped =
642
729
  !this.bundle.hasAsset(resolvedAsset) ||
643
730
  (this.wrappedAssets.has(resolvedAsset.id) &&
@@ -670,7 +757,7 @@ ${code}
670
757
  exportSymbol === 'default' &&
671
758
  staticExports &&
672
759
  !isWrapped &&
673
- dep?.meta.kind === 'Import' &&
760
+ (dep?.meta.kind === 'Import' || dep?.meta.kind === 'Export') &&
674
761
  resolvedAsset.symbols.hasExportSymbol('*') &&
675
762
  resolvedAsset.symbols.hasExportSymbol('default') &&
676
763
  !resolvedAsset.symbols.hasExportSymbol('__esModule');
@@ -700,8 +787,9 @@ ${code}
700
787
  // we need to use a member access off the namespace object rather
701
788
  // than a direct reference. If importing default from a CJS module,
702
789
  // use a helper to check the __esModule flag at runtime.
790
+ let kind = dep?.meta.kind;
703
791
  if (
704
- dep?.meta.kind === 'Import' &&
792
+ (!dep || kind === 'Import' || kind === 'Export') &&
705
793
  exportSymbol === 'default' &&
706
794
  resolvedAsset.symbols.hasExportSymbol('*') &&
707
795
  this.needsDefaultInterop(resolvedAsset)
@@ -709,11 +797,7 @@ ${code}
709
797
  this.usedHelpers.add('$parcel$interopDefault');
710
798
  return `(/*@__PURE__*/$parcel$interopDefault(${obj}))`;
711
799
  } else {
712
- if (IDENTIFIER_RE.test(exportSymbol)) {
713
- return `${obj}.${exportSymbol}`;
714
- }
715
-
716
- return `${obj}[${JSON.stringify(exportSymbol)}]`;
800
+ return this.getPropertyAccess(obj, exportSymbol);
717
801
  }
718
802
  } else if (!symbol) {
719
803
  invariant(false, 'Asset was skipped or not found.');
@@ -773,7 +857,7 @@ ${code}
773
857
  let append = '';
774
858
 
775
859
  let shouldWrap = this.wrappedAssets.has(asset.id);
776
- let usedSymbols = this.bundleGraph.getUsedSymbols(asset);
860
+ let usedSymbols = nullthrows(this.bundleGraph.getUsedSymbols(asset));
777
861
  let assetId = asset.meta.id;
778
862
  invariant(typeof assetId === 'string');
779
863
 
@@ -785,21 +869,29 @@ ${code}
785
869
  usedSymbols.has('default') &&
786
870
  !asset.symbols.hasExportSymbol('__esModule');
787
871
 
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
872
  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
- ));
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`);
803
895
 
804
896
  // If the asset doesn't have static exports, should wrap, the namespace is used,
805
897
  // or we need default interop, then we need to synthesize a namespace object for
@@ -848,7 +940,7 @@ ${code}
848
940
  }
849
941
 
850
942
  let unused = incomingDeps.every(d => {
851
- let symbols = this.bundleGraph.getUsedSymbols(d);
943
+ let symbols = nullthrows(this.bundleGraph.getUsedSymbols(d));
852
944
  return !symbols.has(symbol) && !symbols.has('*');
853
945
  });
854
946
  return !unused;
@@ -861,12 +953,14 @@ ${code}
861
953
  // additional assignments after each mutation of the original binding.
862
954
  prepend += `\n${usedExports
863
955
  .map(exp => {
864
- let resolved = this.resolveSymbol(asset, asset, 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
+ : '';
865
961
  return `$parcel$export($${assetId}$exports, ${JSON.stringify(
866
962
  exp,
867
- )}, () => ${resolved}${
868
- asset.meta.hasCJSExports ? `, (v) => ${resolved} = v` : ''
869
- });`;
963
+ )}, ${get}${set});`;
870
964
  })
871
965
  .join('\n')}\n`;
872
966
  this.usedHelpers.add('$parcel$export');
@@ -875,22 +969,19 @@ ${code}
875
969
 
876
970
  // Find wildcard re-export dependencies, and make sure their exports are also included in ours.
877
971
  for (let dep of deps) {
878
- let resolved = this.bundleGraph.getDependencyResolution(
879
- dep,
880
- this.bundle,
881
- );
972
+ let resolved = this.bundleGraph.getResolvedAsset(dep, this.bundle);
882
973
  if (dep.isOptional || this.bundleGraph.isDependencySkipped(dep)) {
883
974
  continue;
884
975
  }
885
976
 
886
- let isWrapped = resolved && this.wrappedAssets.has(resolved.id);
977
+ let isWrapped = resolved && resolved.meta.shouldWrap;
887
978
 
888
979
  for (let [imported, {local}] of dep.symbols) {
889
980
  if (imported === '*' && local === '*') {
890
981
  if (!resolved) {
891
982
  // Re-exporting an external module. This should have already been handled in buildReplacements.
892
983
  let external = nullthrows(
893
- nullthrows(this.externals.get(dep.moduleSpecifier)).get('*'),
984
+ nullthrows(this.externals.get(dep.specifier)).get('*'),
894
985
  );
895
986
  append += `$parcel$exportWildcard($${assetId}$exports, ${external});\n`;
896
987
  this.usedHelpers.add('$parcel$exportWildcard');
@@ -903,25 +994,39 @@ ${code}
903
994
  if (
904
995
  isWrapped ||
905
996
  resolved.meta.staticExports === false ||
906
- this.bundleGraph.getUsedSymbols(resolved).has('*')
997
+ nullthrows(this.bundleGraph.getUsedSymbols(resolved)).has('*') ||
998
+ // an empty asset
999
+ (!resolved.meta.hasCJSExports &&
1000
+ resolved.symbols.hasExportSymbol('*'))
907
1001
  ) {
908
- let obj = this.resolveSymbol(asset, resolved, '*', dep);
1002
+ let obj = this.getSymbolResolution(asset, resolved, '*', dep);
909
1003
  append += `$parcel$exportWildcard($${assetId}$exports, ${obj});\n`;
910
1004
  this.usedHelpers.add('$parcel$exportWildcard');
911
1005
  } else {
912
- for (let symbol of this.bundleGraph.getUsedSymbols(dep)) {
913
- let resolvedSymbol = this.resolveSymbol(
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(
914
1017
  asset,
915
1018
  resolved,
916
1019
  symbol,
917
1020
  );
1021
+ let get = this.buildFunctionExpression([], resolvedSymbol);
1022
+ let set = asset.meta.hasCJSExports
1023
+ ? ', ' +
1024
+ this.buildFunctionExpression(['v'], `${resolvedSymbol} = v`)
1025
+ : '';
918
1026
  prepend += `$parcel$export($${assetId}$exports, ${JSON.stringify(
919
1027
  symbol,
920
- )}, () => ${resolvedSymbol}${
921
- asset.meta.hasCJSExports
922
- ? `, (v) => ${resolvedSymbol} = v`
923
- : ''
924
- });\n`;
1028
+ )}, ${get}${set});\n`;
1029
+ this.usedHelpers.add('$parcel$export');
925
1030
  prependLineCount++;
926
1031
  }
927
1032
  }
@@ -954,10 +1059,8 @@ ${code}
954
1059
  }
955
1060
 
956
1061
  // 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();
1062
+ let [outputFormatPrelude, outputFormatLines] =
1063
+ this.outputFormat.buildBundlePrelude();
961
1064
  res += outputFormatPrelude;
962
1065
  lines += outputFormatLines;
963
1066
 
@@ -984,7 +1087,7 @@ ${code}
984
1087
  .getBundleGroupsContainingBundle(this.bundle)
985
1088
  .some(g => this.bundleGraph.isEntryBundleGroup(g)) ||
986
1089
  this.bundle.env.isIsolated() ||
987
- !!this.bundle.getMainEntry()?.isIsolated;
1090
+ this.bundle.bundleBehavior === 'isolated';
988
1091
 
989
1092
  if (mightBeFirstJS) {
990
1093
  let preludeCode = prelude(this.parcelRequireName);
@@ -1002,14 +1105,19 @@ ${code}
1002
1105
  }
1003
1106
 
1004
1107
  // Add importScripts for sibling bundles in workers.
1005
- if (this.bundle.env.isWorker()) {
1108
+ if (this.bundle.env.isWorker() || this.bundle.env.isWorklet()) {
1006
1109
  let importScripts = '';
1007
1110
  let bundles = this.bundleGraph.getReferencedBundles(this.bundle);
1008
1111
  for (let b of bundles) {
1009
- importScripts += `importScripts("${relativeBundlePath(
1010
- this.bundle,
1011
- b,
1012
- )}");\n`;
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
+ }
1013
1121
  }
1014
1122
 
1015
1123
  res += importScripts;
@@ -1037,10 +1145,28 @@ ${code}
1037
1145
  }
1038
1146
 
1039
1147
  shouldSkipAsset(asset: Asset): boolean {
1148
+ if (this.isScriptEntry(asset)) {
1149
+ return true;
1150
+ }
1151
+
1040
1152
  return (
1041
1153
  asset.sideEffects === false &&
1042
- this.bundleGraph.getUsedSymbols(asset).size == 0 &&
1043
- !this.bundleGraph.isAssetReferencedByDependant(this.bundle, asset)
1154
+ nullthrows(this.bundleGraph.getUsedSymbols(asset)).size == 0 &&
1155
+ !this.bundleGraph.isAssetReferenced(this.bundle, asset)
1044
1156
  );
1045
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
+ }
1046
1172
  }