@atlaspack/packager-js 2.14.5-canary.28 → 2.14.5-canary.281

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.
@@ -1,5 +1,3 @@
1
- // @flow
2
-
3
1
  import type {
4
2
  Asset,
5
3
  BundleGraph,
@@ -15,6 +13,8 @@ import {
15
13
  relativeBundlePath,
16
14
  countLines,
17
15
  normalizeSeparators,
16
+ debugTools,
17
+ globToRegex,
18
18
  } from '@atlaspack/utils';
19
19
  import SourceMap from '@parcel/source-map';
20
20
  import nullthrows from 'nullthrows';
@@ -25,11 +25,18 @@ import ThrowableDiagnostic, {
25
25
  import globals from 'globals';
26
26
  import path from 'path';
27
27
  import {getFeatureFlag} from '@atlaspack/feature-flags';
28
+ import {outdent} from 'outdent';
28
29
 
29
30
  import {ESMOutputFormat} from './ESMOutputFormat';
30
31
  import {CJSOutputFormat} from './CJSOutputFormat';
31
32
  import {GlobalOutputFormat} from './GlobalOutputFormat';
32
- import {prelude, helpers, bundleQueuePrelude, fnExpr} from './helpers';
33
+ import {
34
+ preludeOld,
35
+ preludeNew,
36
+ helpers,
37
+ bundleQueuePrelude,
38
+ fnExpr,
39
+ } from './helpers';
33
40
  import {
34
41
  replaceScriptDependencies,
35
42
  getSpecifier,
@@ -39,7 +46,6 @@ import {
39
46
 
40
47
  // General regex used to replace imports with the resolved code, references with resolutions,
41
48
  // and count the number of newlines in the file for source maps.
42
- //
43
49
  // For conditional bundling the only difference in this regex is adding `importCond` where we have `importAsync` etc..
44
50
  const REPLACEMENT_RE_CONDITIONAL =
45
51
  /\n|import\s+"([0-9a-f]{16,20}:.+?)";|(?:\$[0-9a-f]{16,20}\$exports)|(?:\$[0-9a-f]{16,20}\$(?:import|importAsync|require|importCond)\$[0-9a-f]+(?:\$[0-9a-f]+)?)/g;
@@ -55,6 +61,7 @@ const GLOBALS_BY_CONTEXT = {
55
61
  ...Object.keys(globals.serviceworker),
56
62
  ]),
57
63
  worklet: new Set([...BUILTINS]),
64
+ tesseract: new Set([...BUILTINS, ...Object.keys(globals.worker)]),
58
65
  node: new Set([...BUILTINS, ...Object.keys(globals.node)]),
59
66
  'electron-main': new Set([...BUILTINS, ...Object.keys(globals.node)]),
60
67
  'electron-renderer': new Set([
@@ -62,19 +69,28 @@ const GLOBALS_BY_CONTEXT = {
62
69
  ...Object.keys(globals.node),
63
70
  ...Object.keys(globals.browser),
64
71
  ]),
65
- };
72
+ } as const;
66
73
 
67
74
  const OUTPUT_FORMATS = {
68
75
  esmodule: ESMOutputFormat,
69
76
  commonjs: CJSOutputFormat,
70
77
  global: GlobalOutputFormat,
71
- };
78
+ } as const;
72
79
 
73
80
  export interface OutputFormat {
74
81
  buildBundlePrelude(): [string, number];
75
82
  buildBundlePostlude(): [string, number];
76
83
  }
77
84
 
85
+ export type PackageResult = {
86
+ contents: string;
87
+ map: SourceMap | null | undefined;
88
+ scopeHoistingStats?: {
89
+ totalAssets: number;
90
+ wrappedAssets: number;
91
+ };
92
+ };
93
+
78
94
  export class ScopeHoistingPackager {
79
95
  options: PluginOptions;
80
96
  bundleGraph: BundleGraph<NamedBundle>;
@@ -83,27 +99,38 @@ export class ScopeHoistingPackager {
83
99
  useAsyncBundleRuntime: boolean;
84
100
  outputFormat: OutputFormat;
85
101
  isAsyncBundle: boolean;
86
- globalNames: $ReadOnlySet<string>;
87
- assetOutputs: Map<string, {|code: string, map: ?Buffer|}>;
102
+ globalNames: ReadonlySet<string>;
103
+ manualStaticBindingExports: RegExp[] | null;
104
+ assetOutputs: Map<
105
+ Asset,
106
+ {
107
+ code: string;
108
+ map: Buffer | null | undefined;
109
+ }
110
+ > = new Map();
88
111
  exportedSymbols: Map<
89
112
  string,
90
- {|
91
- asset: Asset,
92
- exportSymbol: string,
93
- local: string,
94
- exportAs: Array<string>,
95
- |},
113
+ {
114
+ asset: Asset;
115
+ exportSymbol: string;
116
+ local: string;
117
+ exportAs: Array<string>;
118
+ }
96
119
  > = new Map();
97
120
  externals: Map<string, Map<string, string>> = new Map();
98
121
  topLevelNames: Map<string, number> = new Map();
99
- seenAssets: Set<string> = new Set();
100
- wrappedAssets: Set<string> = new Set();
101
- hoistedRequires: Map<string, Map<string, string>> = new Map();
122
+ seenAssets: Set<Asset> = new Set();
123
+ wrappedAssets: Set<Asset> = new Set();
124
+ constantAssets: Set<Asset> = new Set();
125
+ hoistedRequires: Map<Dependency, Map<Asset, string>> = new Map();
126
+ seenHoistedRequires: Set<string> = new Set();
102
127
  needsPrelude: boolean = false;
103
128
  usedHelpers: Set<string> = new Set();
104
129
  externalAssets: Set<Asset> = new Set();
105
- forceSkipWrapAssets: Array<string> = [];
106
130
  logger: PluginLogger;
131
+ useBothScopeHoistingImprovements: boolean =
132
+ getFeatureFlag('applyScopeHoistingImprovementV2') ||
133
+ getFeatureFlag('applyScopeHoistingImprovement');
107
134
 
108
135
  constructor(
109
136
  options: PluginOptions,
@@ -111,7 +138,7 @@ export class ScopeHoistingPackager {
111
138
  bundle: NamedBundle,
112
139
  parcelRequireName: string,
113
140
  useAsyncBundleRuntime: boolean,
114
- forceSkipWrapAssets: Array<string>,
141
+ manualStaticBindingExports: string[] | null,
115
142
  logger: PluginLogger,
116
143
  ) {
117
144
  this.options = options;
@@ -119,7 +146,8 @@ export class ScopeHoistingPackager {
119
146
  this.bundle = bundle;
120
147
  this.parcelRequireName = parcelRequireName;
121
148
  this.useAsyncBundleRuntime = useAsyncBundleRuntime;
122
- this.forceSkipWrapAssets = forceSkipWrapAssets ?? [];
149
+ this.manualStaticBindingExports =
150
+ manualStaticBindingExports?.map((glob) => globToRegex(glob)) ?? null;
123
151
  this.logger = logger;
124
152
 
125
153
  let OutputFormat = OUTPUT_FORMATS[this.bundle.env.outputFormat];
@@ -128,13 +156,14 @@ export class ScopeHoistingPackager {
128
156
  this.isAsyncBundle =
129
157
  this.bundleGraph.hasParentBundleOfType(this.bundle, 'js') &&
130
158
  !this.bundle.env.isIsolated() &&
131
- this.bundle.bundleBehavior !== 'isolated';
159
+ this.bundle.bundleBehavior !== 'isolated' &&
160
+ this.bundle.bundleBehavior !== 'inlineIsolated';
132
161
 
133
162
  this.globalNames = GLOBALS_BY_CONTEXT[bundle.env.context];
134
163
  }
135
164
 
136
- async package(): Promise<{|contents: string, map: ?SourceMap|}> {
137
- let wrappedAssets = await this.loadAssets();
165
+ async package(): Promise<PackageResult> {
166
+ await this.loadAssets();
138
167
  this.buildExportedSymbols();
139
168
 
140
169
  // If building a library, the target is actually another bundler rather
@@ -155,10 +184,13 @@ export class ScopeHoistingPackager {
155
184
 
156
185
  let res = '';
157
186
  let lineCount = 0;
158
- let sourceMap = null;
159
- let processAsset = (asset) => {
187
+ let sourceMap: SourceMap | null | undefined = null;
188
+ let processAsset = (asset: Asset) => {
189
+ this.seenHoistedRequires.clear();
160
190
  let [content, map, lines] = this.visitAsset(asset);
191
+
161
192
  if (sourceMap && map) {
193
+ // @ts-expect-error TS2551 - addSourceMap method exists but missing from @parcel/source-map type definitions
162
194
  sourceMap.addSourceMap(map, lineCount);
163
195
  } else if (this.bundle.env.sourceMap) {
164
196
  sourceMap = map;
@@ -168,10 +200,22 @@ export class ScopeHoistingPackager {
168
200
  lineCount += lines + 1;
169
201
  };
170
202
 
203
+ if (
204
+ getFeatureFlag('inlineConstOptimisationFix') ||
205
+ this.useBothScopeHoistingImprovements
206
+ ) {
207
+ // Write out all constant modules used by this bundle
208
+ for (let asset of this.constantAssets) {
209
+ if (!this.seenAssets.has(asset)) {
210
+ processAsset(asset);
211
+ }
212
+ }
213
+ }
214
+
171
215
  // Hoist wrapped asset to the top of the bundle to ensure that they are registered
172
216
  // before they are used.
173
- for (let asset of wrappedAssets) {
174
- if (!this.seenAssets.has(asset.id)) {
217
+ for (let asset of this.wrappedAssets) {
218
+ if (!this.seenAssets.has(asset)) {
175
219
  processAsset(asset);
176
220
  }
177
221
  }
@@ -179,7 +223,7 @@ export class ScopeHoistingPackager {
179
223
  // Add each asset that is directly connected to the bundle. Dependencies will be handled
180
224
  // by replacing `import` statements in the code.
181
225
  this.bundle.traverseAssets((asset, _, actions) => {
182
- if (this.seenAssets.has(asset.id)) {
226
+ if (this.seenAssets.has(asset)) {
183
227
  actions.skipChildren();
184
228
  return;
185
229
  }
@@ -191,14 +235,28 @@ export class ScopeHoistingPackager {
191
235
  let [prelude, preludeLines] = this.buildBundlePrelude();
192
236
  res = prelude + res;
193
237
  lineCount += preludeLines;
238
+ // @ts-expect-error TS2339 - offsetLines method exists but missing from @parcel/source-map type definitions
194
239
  sourceMap?.offsetLines(1, preludeLines);
195
240
 
196
241
  let entries = this.bundle.getEntryAssets();
197
242
  let mainEntry = this.bundle.getMainEntry();
198
243
  if (this.isAsyncBundle) {
199
- // In async bundles we don't want the main entry to execute until we require it
200
- // as there might be dependencies in a sibling bundle that hasn't loaded yet.
201
- entries = entries.filter((a) => a.id !== mainEntry?.id);
244
+ if (
245
+ this.useBothScopeHoistingImprovements ||
246
+ getFeatureFlag('supportWebpackChunkName')
247
+ ) {
248
+ // Generally speaking, async bundles should not be executed on load, as
249
+ // they're just collections of assets that other assets require.
250
+ // However, there are some special cases where a runtime asset needs to be
251
+ // injected, but no other asset will require it (mostly the bundle
252
+ // manifest).
253
+ // In this case, those assets need to be required on load.
254
+ entries = entries.filter(
255
+ (a) => a.meta?.runtimeAssetRequiringExecutionOnLoad,
256
+ );
257
+ } else {
258
+ entries = entries.filter((a) => a.id !== mainEntry?.id);
259
+ }
202
260
  mainEntry = null;
203
261
  }
204
262
 
@@ -206,7 +264,7 @@ export class ScopeHoistingPackager {
206
264
 
207
265
  // If any of the entry assets are wrapped, call parcelRequire so they are executed.
208
266
  for (let entry of entries) {
209
- if (this.wrappedAssets.has(entry.id) && !this.isScriptEntry(entry)) {
267
+ if (this.wrappedAssets.has(entry) && !this.isScriptEntry(entry)) {
210
268
  let parcelRequire = `parcelRequire(${JSON.stringify(
211
269
  this.bundleGraph.getAssetPublicId(entry),
212
270
  )});\n`;
@@ -250,9 +308,7 @@ export class ScopeHoistingPackager {
250
308
  lineCount++;
251
309
 
252
310
  let mainEntry = nullthrows(this.bundle.getMainEntry());
253
- let {code, map: mapBuffer} = nullthrows(
254
- this.assetOutputs.get(mainEntry.id),
255
- );
311
+ let {code, map: mapBuffer} = nullthrows(this.assetOutputs.get(mainEntry));
256
312
  let map;
257
313
  if (mapBuffer) {
258
314
  map = new SourceMap(this.options.projectRoot, mapBuffer);
@@ -265,14 +321,21 @@ export class ScopeHoistingPackager {
265
321
  this.parcelRequireName,
266
322
  );
267
323
  if (sourceMap && map) {
324
+ // @ts-expect-error TS2339 - addSourceMap method exists but missing from @parcel/source-map type definitions
268
325
  sourceMap.addSourceMap(map, lineCount);
269
326
  }
270
327
  }
271
328
 
272
- return {
273
- contents: res,
274
- map: sourceMap,
275
- };
329
+ const result: PackageResult = {contents: res, map: sourceMap};
330
+
331
+ if (debugTools['scope-hoisting-stats']) {
332
+ result.scopeHoistingStats = {
333
+ totalAssets: this.assetOutputs.size,
334
+ wrappedAssets: this.wrappedAssets.size,
335
+ };
336
+ }
337
+
338
+ return result;
276
339
  }
277
340
 
278
341
  shouldBundleQueue(bundle: NamedBundle): boolean {
@@ -281,10 +344,7 @@ export class ScopeHoistingPackager {
281
344
 
282
345
  let hasConditionalReference = false;
283
346
  let isConditionalBundle = false;
284
- if (
285
- getFeatureFlag('conditionalBundlingApi') &&
286
- getFeatureFlag('conditionalBundlingAsyncRuntime')
287
- ) {
347
+ if (getFeatureFlag('conditionalBundlingApi')) {
288
348
  // If the bundle has a conditional bundle reference (has an importCond)
289
349
  hasConditionalReference =
290
350
  this.bundleGraph.getReferencedConditionalBundles(bundle).length > 0;
@@ -296,6 +356,7 @@ export class ScopeHoistingPackager {
296
356
  this.useAsyncBundleRuntime &&
297
357
  bundle.type === 'js' &&
298
358
  bundle.bundleBehavior !== 'inline' &&
359
+ bundle.bundleBehavior !== 'inlineIsolated' &&
299
360
  bundle.env.outputFormat === 'esmodule' &&
300
361
  !bundle.env.isIsolated() &&
301
362
  bundle.bundleBehavior !== 'isolated' &&
@@ -309,11 +370,8 @@ export class ScopeHoistingPackager {
309
370
  .filter((b) => this.shouldBundleQueue(b))
310
371
  .map((b) => b.publicId);
311
372
 
312
- const conditions = [];
313
- if (
314
- getFeatureFlag('conditionalBundlingApi') &&
315
- getFeatureFlag('conditionalBundlingAsyncRuntime')
316
- ) {
373
+ const conditions: Array<string> = [];
374
+ if (getFeatureFlag('conditionalBundlingApi')) {
317
375
  const conditionSet = this.bundleGraph
318
376
  .getConditionalBundleMapping()
319
377
  .get(bundle.id);
@@ -354,16 +412,20 @@ export class ScopeHoistingPackager {
354
412
  return `$parcel$global.rwr(${params.join(', ')});`;
355
413
  }
356
414
 
357
- async loadAssets(): Promise<Array<Asset>> {
358
- let queue = new PromiseQueue({maxConcurrent: 32});
359
- let wrapped = [];
415
+ async loadAssets() {
416
+ type QueueItem = [Asset, {code: string; map: Buffer | undefined | null}];
417
+ let queue = new PromiseQueue<QueueItem>({
418
+ maxConcurrent: 32,
419
+ });
420
+
360
421
  this.bundle.traverseAssets((asset) => {
361
422
  queue.add(async () => {
362
423
  let [code, map] = await Promise.all([
363
424
  asset.getCode(),
364
425
  this.bundle.env.sourceMap ? asset.getMapBuffer() : null,
365
426
  ]);
366
- return [asset.id, {code, map}];
427
+
428
+ return [asset, {code, map}];
367
429
  });
368
430
 
369
431
  if (
@@ -381,52 +443,113 @@ export class ScopeHoistingPackager {
381
443
  .getIncomingDependencies(asset)
382
444
  .some((dep) => dep.priority === 'lazy')
383
445
  ) {
384
- this.wrappedAssets.add(asset.id);
385
- wrapped.push(asset);
446
+ this.wrappedAssets.add(asset);
447
+ } else if (
448
+ (getFeatureFlag('inlineConstOptimisationFix') ||
449
+ this.useBothScopeHoistingImprovements) &&
450
+ asset.meta.isConstantModule
451
+ ) {
452
+ this.constantAssets.add(asset);
386
453
  }
387
454
  }
388
455
  });
389
456
 
390
- for (let wrappedAssetRoot of [...wrapped]) {
391
- this.bundle.traverseAssets((asset, _, actions) => {
392
- if (asset === wrappedAssetRoot) {
393
- return;
457
+ if (this.useBothScopeHoistingImprovements) {
458
+ // Tracks which assets have been assigned to a wrap group
459
+ let assignedAssets = new Set<Asset>();
460
+
461
+ // In V2 scope hoisting, we iterate from the main entry, rather than
462
+ // wrapping the entry assets
463
+ if (!getFeatureFlag('applyScopeHoistingImprovementV2')) {
464
+ // Make all entry assets wrapped, to avoid any top level hoisting
465
+ for (let entryAsset of this.bundle.getEntryAssets()) {
466
+ if (!this.wrappedAssets.has(entryAsset)) {
467
+ this.wrappedAssets.add(entryAsset);
468
+ }
394
469
  }
470
+ }
395
471
 
396
- if (this.wrappedAssets.has(asset.id)) {
397
- actions.skipChildren();
398
- return;
399
- }
400
- // This prevents children of a wrapped asset also being wrapped - it's an "unsafe" optimisation
401
- // that should only be used when you know (or think you know) what you're doing.
402
- //
403
- // In particular this can force an async bundle to be scope hoisted where it previously would not be
404
- // due to the entry asset being wrapped.
405
- if (
406
- this.forceSkipWrapAssets.length > 0 &&
407
- this.forceSkipWrapAssets.some(
408
- (p) =>
409
- p === path.relative(this.options.projectRoot, asset.filePath),
410
- )
411
- ) {
412
- this.logger.verbose({
413
- message: `Force skipping wrapping of ${path.relative(
414
- this.options.projectRoot,
415
- asset.filePath,
416
- )}`,
417
- });
418
- actions.skipChildren();
419
- return;
420
- }
421
- if (!asset.meta.isConstantModule) {
422
- this.wrappedAssets.add(asset.id);
423
- wrapped.push(asset);
472
+ // We need to make a new copy here so that we can add to the list and
473
+ // iterate the newly added items, without mutating the wrappedAssets set
474
+ let moduleGroupParents = [...this.wrappedAssets.values()];
475
+
476
+ if (getFeatureFlag('applyScopeHoistingImprovementV2')) {
477
+ // The main entry needs to be check to find assets that would have gone in
478
+ // the top level scope
479
+ let mainEntry = this.bundle.getMainEntry();
480
+ if (mainEntry && !this.wrappedAssets.has(mainEntry)) {
481
+ moduleGroupParents.unshift(mainEntry);
424
482
  }
425
- }, wrappedAssetRoot);
483
+ }
484
+
485
+ for (let moduleGroupParentAsset of moduleGroupParents) {
486
+ this.bundle.traverseAssets((asset, _, actions) => {
487
+ if (asset === moduleGroupParentAsset) {
488
+ return;
489
+ }
490
+
491
+ if (this.wrappedAssets.has(asset)) {
492
+ actions.skipChildren();
493
+ return;
494
+ }
495
+
496
+ if (
497
+ !asset.meta.isConstantModule &&
498
+ (assignedAssets.has(asset) || this.isReExported(asset))
499
+ ) {
500
+ this.wrappedAssets.add(asset);
501
+
502
+ // This also needs to be added to the traversal so that we iterate
503
+ // it during this check.
504
+ moduleGroupParents.push(asset);
505
+
506
+ actions.skipChildren();
507
+ return;
508
+ }
509
+
510
+ assignedAssets.add(asset);
511
+ }, moduleGroupParentAsset);
512
+ }
513
+ } else {
514
+ for (let wrappedAssetRoot of this.wrappedAssets) {
515
+ this.bundle.traverseAssets((asset, _, actions) => {
516
+ if (asset === wrappedAssetRoot) {
517
+ return;
518
+ }
519
+
520
+ if (this.wrappedAssets.has(asset)) {
521
+ actions.skipChildren();
522
+ return;
523
+ }
524
+
525
+ if (!asset.meta.isConstantModule) {
526
+ this.wrappedAssets.add(asset);
527
+ }
528
+ }, wrappedAssetRoot);
529
+ }
426
530
  }
427
531
 
428
532
  this.assetOutputs = new Map(await queue.run());
429
- return wrapped;
533
+ }
534
+
535
+ isReExported(asset: Asset): boolean {
536
+ let parentSymbols = this.bundleGraph
537
+ .getIncomingDependencies(asset)
538
+ .map((dep) => this.bundleGraph.getAssetWithDependency(dep))
539
+ .flatMap((parent) => {
540
+ if (parent == null) {
541
+ return [];
542
+ }
543
+ return this.bundleGraph.getExportedSymbols(parent, this.bundle);
544
+ });
545
+
546
+ let assetSymbols = this.bundleGraph.getExportedSymbols(asset, this.bundle);
547
+
548
+ return assetSymbols.some((assetSymbol) =>
549
+ parentSymbols.some(
550
+ (parentSymbol) => parentSymbol.symbol === assetSymbol.symbol,
551
+ ),
552
+ );
430
553
  }
431
554
 
432
555
  buildExportedSymbols() {
@@ -439,7 +562,7 @@ export class ScopeHoistingPackager {
439
562
 
440
563
  // TODO: handle ESM exports of wrapped entry assets...
441
564
  let entry = this.bundle.getMainEntry();
442
- if (entry && !this.wrappedAssets.has(entry.id)) {
565
+ if (entry && !this.wrappedAssets.has(entry)) {
443
566
  let hasNamespace = entry.symbols.hasExportSymbol('*');
444
567
 
445
568
  for (let {
@@ -464,6 +587,7 @@ export class ScopeHoistingPackager {
464
587
  symbols = [];
465
588
  this.exportedSymbols.set(symbol, {
466
589
  asset,
590
+
467
591
  exportSymbol,
468
592
  local: symbol,
469
593
  exportAs: symbols,
@@ -519,20 +643,24 @@ export class ScopeHoistingPackager {
519
643
  return `${obj}[${JSON.stringify(property)}]`;
520
644
  }
521
645
 
522
- visitAsset(asset: Asset): [string, ?SourceMap, number] {
523
- invariant(!this.seenAssets.has(asset.id), 'Already visited asset');
524
- this.seenAssets.add(asset.id);
646
+ visitAsset(asset: Asset): [string, SourceMap | null | undefined, number] {
647
+ invariant(!this.seenAssets.has(asset), 'Already visited asset');
648
+ this.seenAssets.add(asset);
525
649
 
526
- let {code, map} = nullthrows(this.assetOutputs.get(asset.id));
650
+ let {code, map} = nullthrows(this.assetOutputs.get(asset));
527
651
  return this.buildAsset(asset, code, map);
528
652
  }
529
653
 
654
+ getAssetFilePath(asset: Asset): string {
655
+ return path.relative(this.options.projectRoot, asset.filePath);
656
+ }
657
+
530
658
  buildAsset(
531
659
  asset: Asset,
532
660
  code: string,
533
- map: ?Buffer,
534
- ): [string, ?SourceMap, number] {
535
- let shouldWrap = this.wrappedAssets.has(asset.id);
661
+ map?: Buffer | null,
662
+ ): [string, SourceMap | null | undefined, number] {
663
+ let shouldWrap = this.wrappedAssets.has(asset);
536
664
  let deps = this.bundleGraph.getDependencies(asset);
537
665
 
538
666
  let sourceMap =
@@ -559,16 +687,30 @@ export class ScopeHoistingPackager {
559
687
  continue;
560
688
  }
561
689
 
562
- if (
563
- this.bundle.hasAsset(resolved) &&
564
- !this.seenAssets.has(resolved.id)
565
- ) {
566
- let [code, map, lines] = this.visitAsset(resolved);
567
- depCode += code + '\n';
568
- if (sourceMap && map) {
569
- sourceMap.addSourceMap(map, lineCount);
690
+ if (this.bundle.hasAsset(resolved) && !this.seenAssets.has(resolved)) {
691
+ if (
692
+ this.useBothScopeHoistingImprovements &&
693
+ this.wrappedAssets.has(resolved)
694
+ ) {
695
+ if (this.wrappedAssets.has(asset)) {
696
+ // If both the asset and the dep are wrapped there's no need to
697
+ // drop a side-effect require. This is an extremely rare case.
698
+ continue;
699
+ }
700
+
701
+ // When the dep is wrapped then we just need to drop a side effect
702
+ // require instead of inlining
703
+ depCode += `parcelRequire("${this.bundleGraph.getAssetPublicId(resolved)}");\n`;
704
+ lineCount += 1;
705
+ } else {
706
+ let [code, map, lines] = this.visitAsset(resolved);
707
+ depCode += code + '\n';
708
+ if (sourceMap && map) {
709
+ // @ts-expect-error TS2551 - addSourceMap method exists but missing from @parcel/source-map type definitions
710
+ sourceMap.addSourceMap(map, lineCount);
711
+ }
712
+ lineCount += lines + 1;
570
713
  }
571
- lineCount += lines + 1;
572
714
  }
573
715
  }
574
716
 
@@ -602,7 +744,7 @@ export class ScopeHoistingPackager {
602
744
  code += append;
603
745
 
604
746
  let lineCount = 0;
605
- let depContent = [];
747
+ let depContent: Array<[string, SourceMap | null | undefined, number]> = [];
606
748
  if (depMap.size === 0 && replacements.size === 0) {
607
749
  // If there are no dependencies or replacements, use a simple function to count the number of lines.
608
750
  lineCount = countLines(code) - 1;
@@ -647,27 +789,68 @@ export class ScopeHoistingPackager {
647
789
  // after the dependency is declared. This handles the case where the resulting asset
648
790
  // is wrapped, but the dependency in this asset is not marked as wrapped. This means
649
791
  // that it was imported/required at the top-level, so its side effects should run immediately.
650
- let [res, lines] = this.getHoistedParcelRequires(
651
- asset,
652
- dep,
653
- resolved,
654
- );
792
+ let res = '';
793
+ let lines = 0;
655
794
  let map;
795
+
796
+ if (!getFeatureFlag('applyScopeHoistingImprovementV2')) {
797
+ [res, lines] = this.getHoistedParcelRequires(
798
+ asset,
799
+ dep,
800
+ resolved,
801
+ );
802
+ }
803
+
656
804
  if (
657
805
  this.bundle.hasAsset(resolved) &&
658
- !this.seenAssets.has(resolved.id)
806
+ !this.seenAssets.has(resolved)
659
807
  ) {
660
808
  // If this asset is wrapped, we need to hoist the code for the dependency
661
809
  // outside our parcelRequire.register wrapper. This is safe because all
662
810
  // assets referenced by this asset will also be wrapped. Otherwise, inline the
663
811
  // asset content where the import statement was.
664
- if (shouldWrap) {
665
- depContent.push(this.visitAsset(resolved));
812
+ if (this.useBothScopeHoistingImprovements) {
813
+ if (
814
+ !resolved.meta.isConstantModule &&
815
+ !this.wrappedAssets.has(resolved)
816
+ ) {
817
+ let [depCode, depMap, depLines] =
818
+ this.visitAsset(resolved);
819
+ if (debugTools['asset-file-names-in-output']) {
820
+ let resolvedPath = this.getAssetFilePath(resolved);
821
+ res = outdent`
822
+ /* Scope hoisted asset: ${resolvedPath} */
823
+ ${depCode}
824
+ /* End: ${resolvedPath} */
825
+ ${res}
826
+ `;
827
+ lines += 3 + depLines;
828
+ } else {
829
+ res = depCode + '\n' + res;
830
+ lines += 1 + depLines;
831
+ }
832
+ map = depMap;
833
+ }
666
834
  } else {
667
- let [depCode, depMap, depLines] = this.visitAsset(resolved);
668
- res = depCode + '\n' + res;
669
- lines += 1 + depLines;
670
- map = depMap;
835
+ if (shouldWrap) {
836
+ depContent.push(this.visitAsset(resolved));
837
+ } else {
838
+ let [depCode, depMap, depLines] =
839
+ this.visitAsset(resolved);
840
+ res = depCode + '\n' + res;
841
+ lines += 1 + depLines;
842
+ map = depMap;
843
+ }
844
+ }
845
+ }
846
+
847
+ if (getFeatureFlag('applyScopeHoistingImprovementV2')) {
848
+ let [requiresCode, requiresLines] =
849
+ this.getHoistedParcelRequires(asset, dep, resolved);
850
+
851
+ if (requiresCode) {
852
+ res = requiresCode + '\n' + res;
853
+ lines += requiresLines + 1;
671
854
  }
672
855
  }
673
856
 
@@ -679,6 +862,7 @@ export class ScopeHoistingPackager {
679
862
  }
680
863
 
681
864
  if (map) {
865
+ // @ts-expect-error TS2551 - addSourceMap method exists but missing from @parcel/source-map type definitions
682
866
  sourceMap.addSourceMap(map, lineCount);
683
867
  }
684
868
  }
@@ -726,10 +910,16 @@ ${code}
726
910
 
727
911
  lineCount += 2;
728
912
 
913
+ if (debugTools['asset-file-names-in-output']) {
914
+ code = `/* ${this.getAssetFilePath(asset)} */\n` + code;
915
+ lineCount += 1;
916
+ }
917
+
729
918
  for (let [depCode, map, lines] of depContent) {
730
919
  if (!depCode) continue;
731
920
  code += depCode + '\n';
732
921
  if (sourceMap && map) {
922
+ // @ts-expect-error TS2551 - addSourceMap method exists but missing from @parcel/source-map type definitions
733
923
  sourceMap.addSourceMap(map, lineCount);
734
924
  }
735
925
  lineCount += lines + 1;
@@ -848,7 +1038,7 @@ ${code}
848
1038
  // If this asset is wrapped, we need to replace the exports namespace with `module.exports`,
849
1039
  // which will be provided to us by the wrapper.
850
1040
  if (
851
- this.wrappedAssets.has(asset.id) ||
1041
+ this.wrappedAssets.has(asset) ||
852
1042
  (this.bundle.env.outputFormat === 'commonjs' &&
853
1043
  asset === this.bundle.getMainEntry())
854
1044
  ) {
@@ -895,7 +1085,9 @@ ${code}
895
1085
 
896
1086
  for (let [imported, {local}] of dep.symbols) {
897
1087
  // If already imported, just add the already renamed variable to the mapping.
1088
+
898
1089
  let renamed = external.get(imported);
1090
+
899
1091
  if (renamed && local !== '*' && replacements) {
900
1092
  replacements.set(local, renamed);
901
1093
  continue;
@@ -908,6 +1100,7 @@ ${code}
908
1100
  if (!renamed) {
909
1101
  if (referencedBundle) {
910
1102
  let entry = nullthrows(referencedBundle.getMainEntry());
1103
+
911
1104
  renamed =
912
1105
  entry.symbols.get('*')?.local ??
913
1106
  `$${String(entry.meta.id)}$exports`;
@@ -922,6 +1115,7 @@ ${code}
922
1115
 
923
1116
  if (local !== '*' && replacements) {
924
1117
  let replacement;
1118
+
925
1119
  if (imported === '*') {
926
1120
  replacement = renamed;
927
1121
  } else if (imported === 'default') {
@@ -946,10 +1140,12 @@ ${code}
946
1140
  let property;
947
1141
  if (referencedBundle) {
948
1142
  let entry = nullthrows(referencedBundle.getMainEntry());
1143
+
949
1144
  if (entry.symbols.hasExportSymbol('*')) {
950
1145
  // If importing * and the referenced module has a * export (e.g. CJS), use default instead.
951
1146
  // This mirrors the logic in buildExportedSymbols.
952
1147
  property = imported;
1148
+
953
1149
  imported =
954
1150
  referencedBundle?.env.outputFormat === 'esmodule'
955
1151
  ? 'default'
@@ -957,6 +1153,7 @@ ${code}
957
1153
  } else {
958
1154
  if (imported === '*') {
959
1155
  let exportedSymbols = this.bundleGraph.getExportedSymbols(entry);
1156
+
960
1157
  if (local === '*') {
961
1158
  // Re-export all symbols.
962
1159
  for (let exported of exportedSymbols) {
@@ -967,11 +1164,10 @@ ${code}
967
1164
  continue;
968
1165
  }
969
1166
  }
970
- renamed = this.bundleGraph.getSymbolResolution(
971
- entry,
972
- imported,
973
- this.bundle,
974
- ).symbol;
1167
+
1168
+ renamed =
1169
+ this.bundleGraph.getSymbolResolution(entry, imported, this.bundle)
1170
+ .symbol || undefined;
975
1171
  }
976
1172
  }
977
1173
 
@@ -993,8 +1189,10 @@ ${code}
993
1189
  }
994
1190
 
995
1191
  external.set(imported, renamed);
1192
+
996
1193
  if (local !== '*' && replacements) {
997
1194
  let replacement = renamed;
1195
+
998
1196
  if (property === '*') {
999
1197
  replacement = renamed;
1000
1198
  } else if (property === 'default') {
@@ -1003,6 +1201,7 @@ ${code}
1003
1201
  } else if (property) {
1004
1202
  replacement = this.getPropertyAccess(renamed, property);
1005
1203
  }
1204
+
1006
1205
  replacements.set(local, replacement);
1007
1206
  }
1008
1207
  }
@@ -1026,7 +1225,7 @@ ${code}
1026
1225
  }
1027
1226
  return (
1028
1227
  (!this.bundle.hasAsset(resolved) && !this.externalAssets.has(resolved)) ||
1029
- (this.wrappedAssets.has(resolved.id) && resolved !== parentAsset)
1228
+ (this.wrappedAssets.has(resolved) && resolved !== parentAsset)
1030
1229
  );
1031
1230
  }
1032
1231
 
@@ -1076,14 +1275,14 @@ ${code}
1076
1275
  (!this.bundle.hasAsset(resolvedAsset) ||
1077
1276
  !this.shouldSkipAsset(resolvedAsset))
1078
1277
  ) {
1079
- let hoisted = this.hoistedRequires.get(dep.id);
1278
+ let hoisted = this.hoistedRequires.get(dep);
1080
1279
  if (!hoisted) {
1081
1280
  hoisted = new Map();
1082
- this.hoistedRequires.set(dep.id, hoisted);
1281
+ this.hoistedRequires.set(dep, hoisted);
1083
1282
  }
1084
1283
 
1085
1284
  hoisted.set(
1086
- resolvedAsset.id,
1285
+ resolvedAsset,
1087
1286
  `var $${publicId} = parcelRequire(${JSON.stringify(publicId)});`,
1088
1287
  );
1089
1288
  }
@@ -1117,6 +1316,7 @@ ${code}
1117
1316
  obj = `$${publicId}`;
1118
1317
  } else {
1119
1318
  obj = resolvedAsset.symbols.get('*')?.local || `$${assetId}$exports`;
1319
+
1120
1320
  obj = replacements?.get(obj) || obj;
1121
1321
  }
1122
1322
 
@@ -1124,7 +1324,7 @@ ${code}
1124
1324
  // Resolve to the namespace object if requested or this is a CJS default interop reqiure.
1125
1325
  if (
1126
1326
  parentAsset === resolvedAsset &&
1127
- this.wrappedAssets.has(resolvedAsset.id)
1327
+ this.wrappedAssets.has(resolvedAsset)
1128
1328
  ) {
1129
1329
  // Directly use module.exports for wrapped assets importing themselves.
1130
1330
  return 'module.exports';
@@ -1167,7 +1367,7 @@ ${code}
1167
1367
  return ['', 0];
1168
1368
  }
1169
1369
 
1170
- let hoisted = this.hoistedRequires.get(dep.id);
1370
+ let hoisted = this.hoistedRequires.get(dep);
1171
1371
  let res = '';
1172
1372
  let lineCount = 0;
1173
1373
  let isWrapped = this.isWrapped(resolved, parentAsset);
@@ -1179,7 +1379,7 @@ ${code}
1179
1379
  if (
1180
1380
  isWrapped &&
1181
1381
  !dep.meta.shouldWrap &&
1182
- (!hoisted || hoisted.keys().next().value !== resolved.id) &&
1382
+ (!hoisted || hoisted.keys().next().value !== resolved) &&
1183
1383
  !this.bundleGraph.isDependencySkipped(dep) &&
1184
1384
  !this.shouldSkipAsset(resolved)
1185
1385
  ) {
@@ -1191,8 +1391,22 @@ ${code}
1191
1391
 
1192
1392
  if (hoisted) {
1193
1393
  this.needsPrelude = true;
1194
- res += '\n' + [...hoisted.values()].join('\n');
1195
- lineCount += hoisted.size;
1394
+
1395
+ if (getFeatureFlag('applyScopeHoistingImprovementV2')) {
1396
+ let hoistedValues = [...hoisted.values()].filter(
1397
+ (val) => !this.seenHoistedRequires.has(val),
1398
+ );
1399
+
1400
+ for (let val of hoistedValues) {
1401
+ this.seenHoistedRequires.add(val);
1402
+ }
1403
+
1404
+ res += '\n' + hoistedValues.join('\n');
1405
+ lineCount += hoisted.size;
1406
+ } else {
1407
+ res += '\n' + [...hoisted.values()].join('\n');
1408
+ lineCount += hoisted.size;
1409
+ }
1196
1410
  }
1197
1411
 
1198
1412
  return [res, lineCount];
@@ -1207,7 +1421,7 @@ ${code}
1207
1421
  let prependLineCount = 0;
1208
1422
  let append = '';
1209
1423
 
1210
- let shouldWrap = this.wrappedAssets.has(asset.id);
1424
+ let shouldWrap = this.wrappedAssets.has(asset);
1211
1425
  let usedSymbols = nullthrows(this.bundleGraph.getUsedSymbols(asset));
1212
1426
  let assetId = asset.meta.id;
1213
1427
  invariant(typeof assetId === 'string');
@@ -1220,34 +1434,51 @@ ${code}
1220
1434
  usedSymbols.has('default') &&
1221
1435
  !asset.symbols.hasExportSymbol('__esModule');
1222
1436
 
1223
- let usedNamespace =
1224
- // If the asset has * in its used symbols, we might need the exports namespace.
1225
- // The one case where this isn't true is in ESM library entries, where the only
1226
- // dependency on * is the entry dependency. In this case, we will use ESM exports
1227
- // instead of the namespace object.
1228
- (usedSymbols.has('*') &&
1229
- (this.bundle.env.outputFormat !== 'esmodule' ||
1230
- !this.bundle.env.isLibrary ||
1231
- asset !== this.bundle.getMainEntry() ||
1232
- this.bundleGraph
1233
- .getIncomingDependencies(asset)
1234
- .some(
1235
- (dep) =>
1236
- !dep.isEntry &&
1237
- this.bundle.hasDependency(dep) &&
1238
- nullthrows(this.bundleGraph.getUsedSymbols(dep)).has('*'),
1239
- ))) ||
1240
- // If a symbol is imported (used) from a CJS asset but isn't listed in the symbols,
1241
- // we fallback on the namespace object.
1242
- (asset.symbols.hasExportSymbol('*') &&
1243
- [...usedSymbols].some((s) => !asset.symbols.hasExportSymbol(s))) ||
1244
- // If the exports has this asset's namespace (e.g. ESM output from CJS input),
1245
- // include the namespace object for the default export.
1246
- this.exportedSymbols.has(`$${assetId}$exports`) ||
1247
- // CommonJS library bundle entries always need a namespace.
1248
- (this.bundle.env.isLibrary &&
1249
- this.bundle.env.outputFormat === 'commonjs' &&
1250
- asset === this.bundle.getMainEntry());
1437
+ let usedNamespace;
1438
+ if (
1439
+ getFeatureFlag('inlineConstOptimisationFix') &&
1440
+ asset.meta.isConstantModule
1441
+ ) {
1442
+ // Only set usedNamespace if there is an incoming dependency in the current bundle that uses '*'
1443
+ usedNamespace = this.bundleGraph
1444
+ .getIncomingDependencies(asset)
1445
+ .some(
1446
+ (dep) =>
1447
+ this.bundle.hasDependency(dep) &&
1448
+ nullthrows(this.bundleGraph.getUsedSymbols(dep)).has('*'),
1449
+ );
1450
+ } else {
1451
+ usedNamespace =
1452
+ // If the asset has * in its used symbols, we might need the exports namespace.
1453
+ // The one case where this isn't true is in ESM library entries, where the only
1454
+ // dependency on * is the entry dependency. In this case, we will use ESM exports
1455
+ // instead of the namespace object.
1456
+
1457
+ (usedSymbols.has('*') &&
1458
+ (this.bundle.env.outputFormat !== 'esmodule' ||
1459
+ !this.bundle.env.isLibrary ||
1460
+ asset !== this.bundle.getMainEntry() ||
1461
+ this.bundleGraph
1462
+ .getIncomingDependencies(asset)
1463
+ .some(
1464
+ (dep) =>
1465
+ !dep.isEntry &&
1466
+ this.bundle.hasDependency(dep) &&
1467
+ nullthrows(this.bundleGraph.getUsedSymbols(dep)).has('*'),
1468
+ ))) ||
1469
+ // If a symbol is imported (used) from a CJS asset but isn't listed in the symbols,
1470
+ // we fallback on the namespace object.
1471
+
1472
+ (asset.symbols.hasExportSymbol('*') &&
1473
+ [...usedSymbols].some((s) => !asset.symbols.hasExportSymbol(s))) ||
1474
+ // If the exports has this asset's namespace (e.g. ESM output from CJS input),
1475
+ // include the namespace object for the default export.
1476
+ this.exportedSymbols.has(`$${assetId}$exports`) ||
1477
+ // CommonJS library bundle entries always need a namespace.
1478
+ (this.bundle.env.isLibrary &&
1479
+ this.bundle.env.outputFormat === 'commonjs' &&
1480
+ asset === this.bundle.getMainEntry());
1481
+ }
1251
1482
 
1252
1483
  // If the asset doesn't have static exports, should wrap, the namespace is used,
1253
1484
  // or we need default interop, then we need to synthesize a namespace object for
@@ -1274,6 +1505,7 @@ ${code}
1274
1505
  // Insert the __esModule interop flag for this module if it has a `default` export
1275
1506
  // and the namespace symbol is used.
1276
1507
  // TODO: only if required by CJS?
1508
+
1277
1509
  if (asset.symbols.hasExportSymbol('default') && usedSymbols.has('*')) {
1278
1510
  prepend += `\n$parcel$defineInteropFlag($${assetId}$exports);\n`;
1279
1511
  prependLineCount += 2;
@@ -1337,6 +1569,7 @@ ${code}
1337
1569
  let resolvedSymbol = this.getSymbolResolution(
1338
1570
  asset,
1339
1571
  resolved,
1572
+
1340
1573
  symbol,
1341
1574
  undefined,
1342
1575
  replacements,
@@ -1388,28 +1621,39 @@ ${code}
1388
1621
  // for the symbol so that when the value changes the object property also changes. This is
1389
1622
  // required to simulate ESM live bindings. It's easier to do it this way rather than inserting
1390
1623
  // additional assignments after each mutation of the original binding.
1391
- prepend += `\n${usedExports
1392
- .map((exp) => {
1393
- let resolved = this.getSymbolResolution(
1394
- asset,
1395
- asset,
1624
+ for (let exp of usedExports) {
1625
+ let resolved = this.getSymbolResolution(
1626
+ asset,
1627
+ asset,
1628
+ exp,
1629
+ undefined,
1630
+ replacements,
1631
+ );
1632
+ const meta = asset.symbols.get(exp)?.meta;
1633
+ if (
1634
+ getFeatureFlag('exportsRebindingOptimisation') &&
1635
+ (meta?.isStaticBindingSafe ||
1636
+ this.manualStaticBindingExports?.some((regex) =>
1637
+ regex.test(asset.filePath),
1638
+ ))
1639
+ ) {
1640
+ append += `$${assetId}$exports[${JSON.stringify(
1396
1641
  exp,
1397
- undefined,
1398
- replacements,
1399
- );
1642
+ )}] = ${resolved};\n`;
1643
+ } else {
1400
1644
  let get = this.buildFunctionExpression([], resolved);
1401
1645
  let isEsmExport = !!asset.symbols.get(exp)?.meta?.isEsm;
1402
1646
  let set =
1403
1647
  !isEsmExport && asset.meta.hasCJSExports
1404
1648
  ? ', ' + this.buildFunctionExpression(['v'], `${resolved} = v`)
1405
1649
  : '';
1406
- return `$parcel$export($${assetId}$exports, ${JSON.stringify(
1650
+ prepend += `$parcel$export($${assetId}$exports, ${JSON.stringify(
1407
1651
  exp,
1408
- )}, ${get}${set});`;
1409
- })
1410
- .join('\n')}\n`;
1411
- this.usedHelpers.add('$parcel$export');
1412
- prependLineCount += 1 + usedExports.length;
1652
+ )}, ${get}${set});\n`;
1653
+ this.usedHelpers.add('$parcel$export');
1654
+ prependLineCount += 1 + usedExports.length;
1655
+ }
1656
+ }
1413
1657
  }
1414
1658
  }
1415
1659
 
@@ -1448,9 +1692,11 @@ ${code}
1448
1692
  }
1449
1693
 
1450
1694
  for (let helper of this.usedHelpers) {
1451
- let currentHelper = helpers[helper];
1695
+ let currentHelper = (helpers as Record<string, any>)[helper];
1452
1696
  if (typeof currentHelper === 'function') {
1453
- currentHelper = helpers[helper](this.bundle.env);
1697
+ currentHelper = (helpers as Record<string, any>)[helper](
1698
+ this.bundle.env,
1699
+ );
1454
1700
  }
1455
1701
  res += currentHelper;
1456
1702
  if (enableSourceMaps) {
@@ -1470,11 +1716,14 @@ ${code}
1470
1716
  .some((g) => this.bundleGraph.isEntryBundleGroup(g)) ||
1471
1717
  this.bundle.env.isIsolated() ||
1472
1718
  this.bundle.bundleBehavior === 'isolated' ||
1719
+ this.bundle.bundleBehavior === 'inlineIsolated' ||
1473
1720
  // Conditional deps may be loaded before entrypoints on the server
1474
1721
  this.hasConditionalDependency();
1475
1722
 
1476
1723
  if (mightBeFirstJS) {
1477
- let preludeCode = prelude(this.parcelRequireName);
1724
+ let preludeCode = (
1725
+ getFeatureFlag('useNewPrelude') ? preludeNew : preludeOld
1726
+ )(this.parcelRequireName);
1478
1727
  res += preludeCode;
1479
1728
  if (enableSourceMaps) {
1480
1729
  lines += countLines(preludeCode) - 1;
@@ -1498,7 +1747,11 @@ ${code}
1498
1747
  }
1499
1748
 
1500
1749
  // Add importScripts for sibling bundles in workers.
1501
- if (this.bundle.env.isWorker() || this.bundle.env.isWorklet()) {
1750
+ if (
1751
+ this.bundle.env.isWorker() ||
1752
+ this.bundle.env.isTesseract() ||
1753
+ this.bundle.env.isWorklet()
1754
+ ) {
1502
1755
  let importScripts = '';
1503
1756
  let bundles = this.bundleGraph.getReferencedBundles(this.bundle);
1504
1757
  for (let b of bundles) {