@atlaspack/packager-js 2.14.5-canary.22 → 2.14.5-canary.221
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.
- package/CHANGELOG.md +370 -0
- package/dist/CJSOutputFormat.js +34 -0
- package/dist/DevPackager.js +202 -0
- package/dist/ESMOutputFormat.js +102 -0
- package/dist/GlobalOutputFormat.js +18 -0
- package/dist/ScopeHoistingPackager.js +1365 -0
- package/dist/helpers.js +170 -0
- package/dist/index.js +105 -0
- package/dist/utils.js +60 -0
- package/lib/DevPackager.js +28 -3
- package/lib/ESMOutputFormat.js +1 -1
- package/lib/ScopeHoistingPackager.js +257 -106
- package/lib/dev-prelude.js +6 -6
- package/lib/helpers.js +38 -3
- package/lib/index.js +3 -3
- package/lib/types/CJSOutputFormat.d.ts +7 -0
- package/lib/types/DevPackager.d.ts +15 -0
- package/lib/types/ESMOutputFormat.d.ts +7 -0
- package/lib/types/GlobalOutputFormat.d.ts +7 -0
- package/lib/types/ScopeHoistingPackager.d.ts +66 -0
- package/lib/types/helpers.d.ts +12 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/utils.d.ts +6 -0
- package/package.json +17 -12
- package/src/{CJSOutputFormat.js → CJSOutputFormat.ts} +0 -1
- package/src/{DevPackager.js → DevPackager.ts} +34 -7
- package/src/{ESMOutputFormat.js → ESMOutputFormat.ts} +3 -4
- package/src/{GlobalOutputFormat.js → GlobalOutputFormat.ts} +0 -1
- package/src/{ScopeHoistingPackager.js → ScopeHoistingPackager.ts} +411 -176
- package/src/dev-prelude.js +6 -6
- package/src/{helpers.js → helpers.ts} +37 -3
- package/src/{index.js → index.ts} +21 -17
- package/src/{utils.js → utils.ts} +1 -2
- package/tsconfig.json +27 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -60,6 +60,13 @@ function _featureFlags() {
|
|
|
60
60
|
};
|
|
61
61
|
return data;
|
|
62
62
|
}
|
|
63
|
+
function _outdent() {
|
|
64
|
+
const data = require("outdent");
|
|
65
|
+
_outdent = function () {
|
|
66
|
+
return data;
|
|
67
|
+
};
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
63
70
|
var _ESMOutputFormat = require("./ESMOutputFormat");
|
|
64
71
|
var _CJSOutputFormat = require("./CJSOutputFormat");
|
|
65
72
|
var _GlobalOutputFormat = require("./GlobalOutputFormat");
|
|
@@ -70,7 +77,6 @@ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e;
|
|
|
70
77
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
71
78
|
// General regex used to replace imports with the resolved code, references with resolutions,
|
|
72
79
|
// and count the number of newlines in the file for source maps.
|
|
73
|
-
//
|
|
74
80
|
// For conditional bundling the only difference in this regex is adding `importCond` where we have `importAsync` etc..
|
|
75
81
|
const REPLACEMENT_RE_CONDITIONAL = /\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;
|
|
76
82
|
const REPLACEMENT_RE = /\n|import\s+"([0-9a-f]{16,20}:.+?)";|(?:\$[0-9a-f]{16,20}\$exports)|(?:\$[0-9a-f]{16,20}\$(?:import|importAsync|require)\$[0-9a-f]+(?:\$[0-9a-f]+)?)/g;
|
|
@@ -80,6 +86,7 @@ const GLOBALS_BY_CONTEXT = {
|
|
|
80
86
|
'web-worker': new Set([...BUILTINS, ...Object.keys(_globals().default.worker)]),
|
|
81
87
|
'service-worker': new Set([...BUILTINS, ...Object.keys(_globals().default.serviceworker)]),
|
|
82
88
|
worklet: new Set([...BUILTINS]),
|
|
89
|
+
tesseract: new Set([...BUILTINS, ...Object.keys(_globals().default.worker)]),
|
|
83
90
|
node: new Set([...BUILTINS, ...Object.keys(_globals().default.node)]),
|
|
84
91
|
'electron-main': new Set([...BUILTINS, ...Object.keys(_globals().default.node)]),
|
|
85
92
|
'electron-renderer': new Set([...BUILTINS, ...Object.keys(_globals().default.node), ...Object.keys(_globals().default.browser)])
|
|
@@ -90,32 +97,35 @@ const OUTPUT_FORMATS = {
|
|
|
90
97
|
global: _GlobalOutputFormat.GlobalOutputFormat
|
|
91
98
|
};
|
|
92
99
|
class ScopeHoistingPackager {
|
|
100
|
+
assetOutputs = new Map();
|
|
93
101
|
exportedSymbols = new Map();
|
|
94
102
|
externals = new Map();
|
|
95
103
|
topLevelNames = new Map();
|
|
96
104
|
seenAssets = new Set();
|
|
97
105
|
wrappedAssets = new Set();
|
|
106
|
+
constantAssets = new Set();
|
|
98
107
|
hoistedRequires = new Map();
|
|
108
|
+
seenHoistedRequires = new Set();
|
|
99
109
|
needsPrelude = false;
|
|
100
110
|
usedHelpers = new Set();
|
|
101
111
|
externalAssets = new Set();
|
|
102
|
-
|
|
103
|
-
constructor(options, bundleGraph, bundle, parcelRequireName, useAsyncBundleRuntime,
|
|
112
|
+
useBothScopeHoistingImprovements = (0, _featureFlags().getFeatureFlag)('applyScopeHoistingImprovementV2') || (0, _featureFlags().getFeatureFlag)('applyScopeHoistingImprovement');
|
|
113
|
+
constructor(options, bundleGraph, bundle, parcelRequireName, useAsyncBundleRuntime, manualStaticBindingExports, logger) {
|
|
104
114
|
this.options = options;
|
|
105
115
|
this.bundleGraph = bundleGraph;
|
|
106
116
|
this.bundle = bundle;
|
|
107
117
|
this.parcelRequireName = parcelRequireName;
|
|
108
118
|
this.useAsyncBundleRuntime = useAsyncBundleRuntime;
|
|
109
|
-
this.
|
|
119
|
+
this.manualStaticBindingExports = (manualStaticBindingExports === null || manualStaticBindingExports === void 0 ? void 0 : manualStaticBindingExports.map(glob => (0, _utils().globToRegex)(glob))) ?? null;
|
|
110
120
|
this.logger = logger;
|
|
111
121
|
let OutputFormat = OUTPUT_FORMATS[this.bundle.env.outputFormat];
|
|
112
122
|
this.outputFormat = new OutputFormat(this);
|
|
113
|
-
this.isAsyncBundle = this.bundleGraph.hasParentBundleOfType(this.bundle, 'js') && !this.bundle.env.isIsolated() && this.bundle.bundleBehavior !== 'isolated';
|
|
123
|
+
this.isAsyncBundle = this.bundleGraph.hasParentBundleOfType(this.bundle, 'js') && !this.bundle.env.isIsolated() && this.bundle.bundleBehavior !== 'isolated' && this.bundle.bundleBehavior !== 'inlineIsolated';
|
|
114
124
|
this.globalNames = GLOBALS_BY_CONTEXT[bundle.env.context];
|
|
115
125
|
}
|
|
116
126
|
async package() {
|
|
117
127
|
var _sourceMap;
|
|
118
|
-
|
|
128
|
+
await this.loadAssets();
|
|
119
129
|
this.buildExportedSymbols();
|
|
120
130
|
|
|
121
131
|
// If building a library, the target is actually another bundler rather
|
|
@@ -134,8 +144,10 @@ class ScopeHoistingPackager {
|
|
|
134
144
|
let lineCount = 0;
|
|
135
145
|
let sourceMap = null;
|
|
136
146
|
let processAsset = asset => {
|
|
147
|
+
this.seenHoistedRequires.clear();
|
|
137
148
|
let [content, map, lines] = this.visitAsset(asset);
|
|
138
149
|
if (sourceMap && map) {
|
|
150
|
+
// @ts-expect-error TS2551 - addSourceMap method exists but missing from @parcel/source-map type definitions
|
|
139
151
|
sourceMap.addSourceMap(map, lineCount);
|
|
140
152
|
} else if (this.bundle.env.sourceMap) {
|
|
141
153
|
sourceMap = map;
|
|
@@ -143,11 +155,19 @@ class ScopeHoistingPackager {
|
|
|
143
155
|
res += content + '\n';
|
|
144
156
|
lineCount += lines + 1;
|
|
145
157
|
};
|
|
158
|
+
if ((0, _featureFlags().getFeatureFlag)('inlineConstOptimisationFix') || this.useBothScopeHoistingImprovements) {
|
|
159
|
+
// Write out all constant modules used by this bundle
|
|
160
|
+
for (let asset of this.constantAssets) {
|
|
161
|
+
if (!this.seenAssets.has(asset)) {
|
|
162
|
+
processAsset(asset);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
146
166
|
|
|
147
167
|
// Hoist wrapped asset to the top of the bundle to ensure that they are registered
|
|
148
168
|
// before they are used.
|
|
149
|
-
for (let asset of wrappedAssets) {
|
|
150
|
-
if (!this.seenAssets.has(asset
|
|
169
|
+
for (let asset of this.wrappedAssets) {
|
|
170
|
+
if (!this.seenAssets.has(asset)) {
|
|
151
171
|
processAsset(asset);
|
|
152
172
|
}
|
|
153
173
|
}
|
|
@@ -155,7 +175,7 @@ class ScopeHoistingPackager {
|
|
|
155
175
|
// Add each asset that is directly connected to the bundle. Dependencies will be handled
|
|
156
176
|
// by replacing `import` statements in the code.
|
|
157
177
|
this.bundle.traverseAssets((asset, _, actions) => {
|
|
158
|
-
if (this.seenAssets.has(asset
|
|
178
|
+
if (this.seenAssets.has(asset)) {
|
|
159
179
|
actions.skipChildren();
|
|
160
180
|
return;
|
|
161
181
|
}
|
|
@@ -165,23 +185,35 @@ class ScopeHoistingPackager {
|
|
|
165
185
|
let [prelude, preludeLines] = this.buildBundlePrelude();
|
|
166
186
|
res = prelude + res;
|
|
167
187
|
lineCount += preludeLines;
|
|
188
|
+
// @ts-expect-error TS2339 - offsetLines method exists but missing from @parcel/source-map type definitions
|
|
168
189
|
(_sourceMap = sourceMap) === null || _sourceMap === void 0 || _sourceMap.offsetLines(1, preludeLines);
|
|
169
190
|
let entries = this.bundle.getEntryAssets();
|
|
170
191
|
let mainEntry = this.bundle.getMainEntry();
|
|
171
192
|
if (this.isAsyncBundle) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
193
|
+
if (this.useBothScopeHoistingImprovements || (0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
|
|
194
|
+
// Generally speaking, async bundles should not be executed on load, as
|
|
195
|
+
// they're just collections of assets that other assets require.
|
|
196
|
+
// However, there are some special cases where a runtime asset needs to be
|
|
197
|
+
// injected, but no other asset will require it (mostly the bundle
|
|
198
|
+
// manifest).
|
|
199
|
+
// In this case, those assets need to be required on load.
|
|
200
|
+
entries = entries.filter(a => {
|
|
201
|
+
var _a$meta;
|
|
202
|
+
return (_a$meta = a.meta) === null || _a$meta === void 0 ? void 0 : _a$meta.runtimeAssetRequiringExecutionOnLoad;
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
entries = entries.filter(a => {
|
|
206
|
+
var _mainEntry;
|
|
207
|
+
return a.id !== ((_mainEntry = mainEntry) === null || _mainEntry === void 0 ? void 0 : _mainEntry.id);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
178
210
|
mainEntry = null;
|
|
179
211
|
}
|
|
180
212
|
let needsBundleQueue = this.shouldBundleQueue(this.bundle);
|
|
181
213
|
|
|
182
214
|
// If any of the entry assets are wrapped, call parcelRequire so they are executed.
|
|
183
215
|
for (let entry of entries) {
|
|
184
|
-
if (this.wrappedAssets.has(entry
|
|
216
|
+
if (this.wrappedAssets.has(entry) && !this.isScriptEntry(entry)) {
|
|
185
217
|
var _entry$symbols$get;
|
|
186
218
|
let parcelRequire = `parcelRequire(${JSON.stringify(this.bundleGraph.getAssetPublicId(entry))});\n`;
|
|
187
219
|
let entryExports = (_entry$symbols$get = entry.symbols.get('*')) === null || _entry$symbols$get === void 0 ? void 0 : _entry$symbols$get.local;
|
|
@@ -211,13 +243,14 @@ class ScopeHoistingPackager {
|
|
|
211
243
|
let {
|
|
212
244
|
code,
|
|
213
245
|
map: mapBuffer
|
|
214
|
-
} = (0, _nullthrows().default)(this.assetOutputs.get(mainEntry
|
|
246
|
+
} = (0, _nullthrows().default)(this.assetOutputs.get(mainEntry));
|
|
215
247
|
let map;
|
|
216
248
|
if (mapBuffer) {
|
|
217
249
|
map = new (_sourceMap2().default)(this.options.projectRoot, mapBuffer);
|
|
218
250
|
}
|
|
219
251
|
res += (0, _utils2.replaceScriptDependencies)(this.bundleGraph, this.bundle, code, map, this.parcelRequireName);
|
|
220
252
|
if (sourceMap && map) {
|
|
253
|
+
// @ts-expect-error TS2339 - addSourceMap method exists but missing from @parcel/source-map type definitions
|
|
221
254
|
sourceMap.addSourceMap(map, lineCount);
|
|
222
255
|
}
|
|
223
256
|
}
|
|
@@ -231,18 +264,18 @@ class ScopeHoistingPackager {
|
|
|
231
264
|
let hasHtmlReference = referencingBundles.some(b => b.type === 'html');
|
|
232
265
|
let hasConditionalReference = false;
|
|
233
266
|
let isConditionalBundle = false;
|
|
234
|
-
if ((0, _featureFlags().getFeatureFlag)('conditionalBundlingApi')
|
|
267
|
+
if ((0, _featureFlags().getFeatureFlag)('conditionalBundlingApi')) {
|
|
235
268
|
// If the bundle has a conditional bundle reference (has an importCond)
|
|
236
269
|
hasConditionalReference = this.bundleGraph.getReferencedConditionalBundles(bundle).length > 0;
|
|
237
270
|
// If the bundle is a conditional bundle
|
|
238
271
|
isConditionalBundle = this.hasConditionalDependency();
|
|
239
272
|
}
|
|
240
|
-
return this.useAsyncBundleRuntime && bundle.type === 'js' && bundle.bundleBehavior !== 'inline' && bundle.env.outputFormat === 'esmodule' && !bundle.env.isIsolated() && bundle.bundleBehavior !== 'isolated' && (hasHtmlReference || hasConditionalReference || isConditionalBundle);
|
|
273
|
+
return this.useAsyncBundleRuntime && bundle.type === 'js' && bundle.bundleBehavior !== 'inline' && bundle.bundleBehavior !== 'inlineIsolated' && bundle.env.outputFormat === 'esmodule' && !bundle.env.isIsolated() && bundle.bundleBehavior !== 'isolated' && (hasHtmlReference || hasConditionalReference || isConditionalBundle);
|
|
241
274
|
}
|
|
242
275
|
runWhenReady(bundle, codeToRun) {
|
|
243
276
|
let deps = this.bundleGraph.getReferencedBundles(bundle).filter(b => this.shouldBundleQueue(b)).map(b => b.publicId);
|
|
244
277
|
const conditions = [];
|
|
245
|
-
if ((0, _featureFlags().getFeatureFlag)('conditionalBundlingApi')
|
|
278
|
+
if ((0, _featureFlags().getFeatureFlag)('conditionalBundlingApi')) {
|
|
246
279
|
const conditionSet = this.bundleGraph.getConditionalBundleMapping().get(bundle.id);
|
|
247
280
|
for (const [key, {
|
|
248
281
|
ifTrueBundles,
|
|
@@ -264,11 +297,10 @@ class ScopeHoistingPackager {
|
|
|
264
297
|
let queue = new (_utils().PromiseQueue)({
|
|
265
298
|
maxConcurrent: 32
|
|
266
299
|
});
|
|
267
|
-
let wrapped = [];
|
|
268
300
|
this.bundle.traverseAssets(asset => {
|
|
269
301
|
queue.add(async () => {
|
|
270
302
|
let [code, map] = await Promise.all([asset.getCode(), this.bundle.env.sourceMap ? asset.getMapBuffer() : null]);
|
|
271
|
-
return [asset
|
|
303
|
+
return [asset, {
|
|
272
304
|
code,
|
|
273
305
|
map
|
|
274
306
|
}];
|
|
@@ -276,40 +308,86 @@ class ScopeHoistingPackager {
|
|
|
276
308
|
if (asset.meta.shouldWrap || this.bundle.env.sourceType === 'script' || this.bundleGraph.isAssetReferenced(this.bundle, asset) || this.bundleGraph.getIncomingDependencies(asset).some(dep => dep.meta.shouldWrap && dep.specifierType !== 'url')) {
|
|
277
309
|
// Don't wrap constant "entry" modules _except_ if they are referenced by any lazy dependency
|
|
278
310
|
if (!asset.meta.isConstantModule || this.bundleGraph.getIncomingDependencies(asset).some(dep => dep.priority === 'lazy')) {
|
|
279
|
-
this.wrappedAssets.add(asset
|
|
280
|
-
|
|
311
|
+
this.wrappedAssets.add(asset);
|
|
312
|
+
} else if (((0, _featureFlags().getFeatureFlag)('inlineConstOptimisationFix') || this.useBothScopeHoistingImprovements) && asset.meta.isConstantModule) {
|
|
313
|
+
this.constantAssets.add(asset);
|
|
281
314
|
}
|
|
282
315
|
}
|
|
283
316
|
});
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// In particular this can force an async bundle to be scope hoisted where it previously would not be
|
|
297
|
-
// due to the entry asset being wrapped.
|
|
298
|
-
if (this.forceSkipWrapAssets.length > 0 && this.forceSkipWrapAssets.some(p => p === _path().default.relative(this.options.projectRoot, asset.filePath))) {
|
|
299
|
-
this.logger.verbose({
|
|
300
|
-
message: `Force skipping wrapping of ${_path().default.relative(this.options.projectRoot, asset.filePath)}`
|
|
301
|
-
});
|
|
302
|
-
actions.skipChildren();
|
|
303
|
-
return;
|
|
317
|
+
if (this.useBothScopeHoistingImprovements) {
|
|
318
|
+
// Tracks which assets have been assigned to a wrap group
|
|
319
|
+
let assignedAssets = new Set();
|
|
320
|
+
|
|
321
|
+
// In V2 scope hoisting, we iterate from the main entry, rather than
|
|
322
|
+
// wrapping the entry assets
|
|
323
|
+
if (!(0, _featureFlags().getFeatureFlag)('applyScopeHoistingImprovementV2')) {
|
|
324
|
+
// Make all entry assets wrapped, to avoid any top level hoisting
|
|
325
|
+
for (let entryAsset of this.bundle.getEntryAssets()) {
|
|
326
|
+
if (!this.wrappedAssets.has(entryAsset)) {
|
|
327
|
+
this.wrappedAssets.add(entryAsset);
|
|
328
|
+
}
|
|
304
329
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// We need to make a new copy here so that we can add to the list and
|
|
333
|
+
// iterate the newly added items, without mutating the wrappedAssets set
|
|
334
|
+
let moduleGroupParents = [...this.wrappedAssets.values()];
|
|
335
|
+
if ((0, _featureFlags().getFeatureFlag)('applyScopeHoistingImprovementV2')) {
|
|
336
|
+
// The main entry needs to be check to find assets that would have gone in
|
|
337
|
+
// the top level scope
|
|
338
|
+
let mainEntry = this.bundle.getMainEntry();
|
|
339
|
+
if (mainEntry && !this.wrappedAssets.has(mainEntry)) {
|
|
340
|
+
moduleGroupParents.unshift(mainEntry);
|
|
308
341
|
}
|
|
309
|
-
}
|
|
342
|
+
}
|
|
343
|
+
for (let moduleGroupParentAsset of moduleGroupParents) {
|
|
344
|
+
this.bundle.traverseAssets((asset, _, actions) => {
|
|
345
|
+
if (asset === moduleGroupParentAsset) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (this.wrappedAssets.has(asset)) {
|
|
349
|
+
actions.skipChildren();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (!asset.meta.isConstantModule && (assignedAssets.has(asset) || this.isReExported(asset))) {
|
|
353
|
+
this.wrappedAssets.add(asset);
|
|
354
|
+
|
|
355
|
+
// This also needs to be added to the traversal so that we iterate
|
|
356
|
+
// it during this check.
|
|
357
|
+
moduleGroupParents.push(asset);
|
|
358
|
+
actions.skipChildren();
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
assignedAssets.add(asset);
|
|
362
|
+
}, moduleGroupParentAsset);
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
for (let wrappedAssetRoot of this.wrappedAssets) {
|
|
366
|
+
this.bundle.traverseAssets((asset, _, actions) => {
|
|
367
|
+
if (asset === wrappedAssetRoot) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (this.wrappedAssets.has(asset)) {
|
|
371
|
+
actions.skipChildren();
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (!asset.meta.isConstantModule) {
|
|
375
|
+
this.wrappedAssets.add(asset);
|
|
376
|
+
}
|
|
377
|
+
}, wrappedAssetRoot);
|
|
378
|
+
}
|
|
310
379
|
}
|
|
311
380
|
this.assetOutputs = new Map(await queue.run());
|
|
312
|
-
|
|
381
|
+
}
|
|
382
|
+
isReExported(asset) {
|
|
383
|
+
let parentSymbols = this.bundleGraph.getIncomingDependencies(asset).map(dep => this.bundleGraph.getAssetWithDependency(dep)).flatMap(parent => {
|
|
384
|
+
if (parent == null) {
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
387
|
+
return this.bundleGraph.getExportedSymbols(parent, this.bundle);
|
|
388
|
+
});
|
|
389
|
+
let assetSymbols = this.bundleGraph.getExportedSymbols(asset, this.bundle);
|
|
390
|
+
return assetSymbols.some(assetSymbol => parentSymbols.some(parentSymbol => parentSymbol.symbol === assetSymbol.symbol));
|
|
313
391
|
}
|
|
314
392
|
buildExportedSymbols() {
|
|
315
393
|
if (!this.bundle.env.isLibrary || this.bundle.env.outputFormat !== 'esmodule') {
|
|
@@ -318,7 +396,7 @@ class ScopeHoistingPackager {
|
|
|
318
396
|
|
|
319
397
|
// TODO: handle ESM exports of wrapped entry assets...
|
|
320
398
|
let entry = this.bundle.getMainEntry();
|
|
321
|
-
if (entry && !this.wrappedAssets.has(entry
|
|
399
|
+
if (entry && !this.wrappedAssets.has(entry)) {
|
|
322
400
|
let hasNamespace = entry.symbols.hasExportSymbol('*');
|
|
323
401
|
for (let {
|
|
324
402
|
asset,
|
|
@@ -387,16 +465,19 @@ class ScopeHoistingPackager {
|
|
|
387
465
|
return `${obj}[${JSON.stringify(property)}]`;
|
|
388
466
|
}
|
|
389
467
|
visitAsset(asset) {
|
|
390
|
-
(0, _assert().default)(!this.seenAssets.has(asset
|
|
391
|
-
this.seenAssets.add(asset
|
|
468
|
+
(0, _assert().default)(!this.seenAssets.has(asset), 'Already visited asset');
|
|
469
|
+
this.seenAssets.add(asset);
|
|
392
470
|
let {
|
|
393
471
|
code,
|
|
394
472
|
map
|
|
395
|
-
} = (0, _nullthrows().default)(this.assetOutputs.get(asset
|
|
473
|
+
} = (0, _nullthrows().default)(this.assetOutputs.get(asset));
|
|
396
474
|
return this.buildAsset(asset, code, map);
|
|
397
475
|
}
|
|
476
|
+
getAssetFilePath(asset) {
|
|
477
|
+
return _path().default.relative(this.options.projectRoot, asset.filePath);
|
|
478
|
+
}
|
|
398
479
|
buildAsset(asset, code, map) {
|
|
399
|
-
let shouldWrap = this.wrappedAssets.has(asset
|
|
480
|
+
let shouldWrap = this.wrappedAssets.has(asset);
|
|
400
481
|
let deps = this.bundleGraph.getDependencies(asset);
|
|
401
482
|
let sourceMap = this.bundle.env.sourceMap && map ? new (_sourceMap2().default)(this.options.projectRoot, map) : null;
|
|
402
483
|
|
|
@@ -416,13 +497,21 @@ class ScopeHoistingPackager {
|
|
|
416
497
|
}
|
|
417
498
|
continue;
|
|
418
499
|
}
|
|
419
|
-
if (this.bundle.hasAsset(resolved) && !this.seenAssets.has(resolved
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
500
|
+
if (this.bundle.hasAsset(resolved) && !this.seenAssets.has(resolved)) {
|
|
501
|
+
if (this.useBothScopeHoistingImprovements && this.wrappedAssets.has(resolved)) {
|
|
502
|
+
// When the dep is wrapped then we just need to drop a side effect
|
|
503
|
+
// require instead of inlining
|
|
504
|
+
depCode += `parcelRequire("${this.bundleGraph.getAssetPublicId(resolved)}");\n`;
|
|
505
|
+
lineCount += 1;
|
|
506
|
+
} else {
|
|
507
|
+
let [code, map, lines] = this.visitAsset(resolved);
|
|
508
|
+
depCode += code + '\n';
|
|
509
|
+
if (sourceMap && map) {
|
|
510
|
+
// @ts-expect-error TS2551 - addSourceMap method exists but missing from @parcel/source-map type definitions
|
|
511
|
+
sourceMap.addSourceMap(map, lineCount);
|
|
512
|
+
}
|
|
513
|
+
lineCount += lines + 1;
|
|
424
514
|
}
|
|
425
|
-
lineCount += lines + 1;
|
|
426
515
|
}
|
|
427
516
|
}
|
|
428
517
|
return [depCode, sourceMap, lineCount];
|
|
@@ -482,20 +571,51 @@ class ScopeHoistingPackager {
|
|
|
482
571
|
// after the dependency is declared. This handles the case where the resulting asset
|
|
483
572
|
// is wrapped, but the dependency in this asset is not marked as wrapped. This means
|
|
484
573
|
// that it was imported/required at the top-level, so its side effects should run immediately.
|
|
485
|
-
let
|
|
574
|
+
let res = '';
|
|
575
|
+
let lines = 0;
|
|
486
576
|
let map;
|
|
487
|
-
if (
|
|
577
|
+
if (!(0, _featureFlags().getFeatureFlag)('applyScopeHoistingImprovementV2')) {
|
|
578
|
+
[res, lines] = this.getHoistedParcelRequires(asset, dep, resolved);
|
|
579
|
+
}
|
|
580
|
+
if (this.bundle.hasAsset(resolved) && !this.seenAssets.has(resolved)) {
|
|
488
581
|
// If this asset is wrapped, we need to hoist the code for the dependency
|
|
489
582
|
// outside our parcelRequire.register wrapper. This is safe because all
|
|
490
583
|
// assets referenced by this asset will also be wrapped. Otherwise, inline the
|
|
491
584
|
// asset content where the import statement was.
|
|
492
|
-
if (
|
|
493
|
-
|
|
585
|
+
if (this.useBothScopeHoistingImprovements) {
|
|
586
|
+
if (!resolved.meta.isConstantModule && !this.wrappedAssets.has(resolved)) {
|
|
587
|
+
let [depCode, depMap, depLines] = this.visitAsset(resolved);
|
|
588
|
+
if (_utils().debugTools['asset-file-names-in-output']) {
|
|
589
|
+
let resolvedPath = this.getAssetFilePath(resolved);
|
|
590
|
+
res = (0, _outdent().outdent)`
|
|
591
|
+
/* Scope hoisted asset: ${resolvedPath} */
|
|
592
|
+
${depCode}
|
|
593
|
+
/* End: ${resolvedPath} */
|
|
594
|
+
${res}
|
|
595
|
+
`;
|
|
596
|
+
lines += 3 + depLines;
|
|
597
|
+
} else {
|
|
598
|
+
res = depCode + '\n' + res;
|
|
599
|
+
lines += 1 + depLines;
|
|
600
|
+
}
|
|
601
|
+
map = depMap;
|
|
602
|
+
}
|
|
494
603
|
} else {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
604
|
+
if (shouldWrap) {
|
|
605
|
+
depContent.push(this.visitAsset(resolved));
|
|
606
|
+
} else {
|
|
607
|
+
let [depCode, depMap, depLines] = this.visitAsset(resolved);
|
|
608
|
+
res = depCode + '\n' + res;
|
|
609
|
+
lines += 1 + depLines;
|
|
610
|
+
map = depMap;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if ((0, _featureFlags().getFeatureFlag)('applyScopeHoistingImprovementV2')) {
|
|
615
|
+
let [requiresCode, requiresLines] = this.getHoistedParcelRequires(asset, dep, resolved);
|
|
616
|
+
if (requiresCode) {
|
|
617
|
+
res = requiresCode + '\n' + res;
|
|
618
|
+
lines += requiresLines + 1;
|
|
499
619
|
}
|
|
500
620
|
}
|
|
501
621
|
|
|
@@ -506,6 +626,7 @@ class ScopeHoistingPackager {
|
|
|
506
626
|
sourceMap.offsetLines(lineCount + 1, lines);
|
|
507
627
|
}
|
|
508
628
|
if (map) {
|
|
629
|
+
// @ts-expect-error TS2551 - addSourceMap method exists but missing from @parcel/source-map type definitions
|
|
509
630
|
sourceMap.addSourceMap(map, lineCount);
|
|
510
631
|
}
|
|
511
632
|
}
|
|
@@ -542,10 +663,15 @@ ${code}
|
|
|
542
663
|
});
|
|
543
664
|
`;
|
|
544
665
|
lineCount += 2;
|
|
666
|
+
if (_utils().debugTools['asset-file-names-in-output']) {
|
|
667
|
+
code = `/* ${this.getAssetFilePath(asset)} */\n` + code;
|
|
668
|
+
lineCount += 1;
|
|
669
|
+
}
|
|
545
670
|
for (let [depCode, map, lines] of depContent) {
|
|
546
671
|
if (!depCode) continue;
|
|
547
672
|
code += depCode + '\n';
|
|
548
673
|
if (sourceMap && map) {
|
|
674
|
+
// @ts-expect-error TS2551 - addSourceMap method exists but missing from @parcel/source-map type definitions
|
|
549
675
|
sourceMap.addSourceMap(map, lineCount);
|
|
550
676
|
}
|
|
551
677
|
lineCount += lines + 1;
|
|
@@ -614,7 +740,7 @@ ${code}
|
|
|
614
740
|
|
|
615
741
|
// If this asset is wrapped, we need to replace the exports namespace with `module.exports`,
|
|
616
742
|
// which will be provided to us by the wrapper.
|
|
617
|
-
if (this.wrappedAssets.has(asset
|
|
743
|
+
if (this.wrappedAssets.has(asset) || this.bundle.env.outputFormat === 'commonjs' && asset === this.bundle.getMainEntry()) {
|
|
618
744
|
var _asset$symbols$get;
|
|
619
745
|
let exportsName = ((_asset$symbols$get = asset.symbols.get('*')) === null || _asset$symbols$get === void 0 ? void 0 : _asset$symbols$get.local) || `$${assetId}$exports`;
|
|
620
746
|
replacements.set(exportsName, 'module.exports');
|
|
@@ -648,6 +774,7 @@ ${code}
|
|
|
648
774
|
local
|
|
649
775
|
}] of dep.symbols) {
|
|
650
776
|
// If already imported, just add the already renamed variable to the mapping.
|
|
777
|
+
|
|
651
778
|
let renamed = external.get(imported);
|
|
652
779
|
if (renamed && local !== '*' && replacements) {
|
|
653
780
|
replacements.set(local, renamed);
|
|
@@ -711,7 +838,7 @@ ${code}
|
|
|
711
838
|
continue;
|
|
712
839
|
}
|
|
713
840
|
}
|
|
714
|
-
renamed = this.bundleGraph.getSymbolResolution(entry, imported, this.bundle).symbol;
|
|
841
|
+
renamed = this.bundleGraph.getSymbolResolution(entry, imported, this.bundle).symbol || undefined;
|
|
715
842
|
}
|
|
716
843
|
}
|
|
717
844
|
|
|
@@ -752,7 +879,7 @@ ${code}
|
|
|
752
879
|
}
|
|
753
880
|
return false;
|
|
754
881
|
}
|
|
755
|
-
return !this.bundle.hasAsset(resolved) && !this.externalAssets.has(resolved) || this.wrappedAssets.has(resolved
|
|
882
|
+
return !this.bundle.hasAsset(resolved) && !this.externalAssets.has(resolved) || this.wrappedAssets.has(resolved) && resolved !== parentAsset;
|
|
756
883
|
}
|
|
757
884
|
getSymbolResolution(parentAsset, resolved, imported, dep, replacements) {
|
|
758
885
|
let {
|
|
@@ -779,12 +906,12 @@ ${code}
|
|
|
779
906
|
// Only do this if the asset is part of a different bundle (so it was definitely
|
|
780
907
|
// parcelRequire.register'ed there), or if it is indeed registered in this bundle.
|
|
781
908
|
!this.bundle.hasAsset(resolvedAsset) || !this.shouldSkipAsset(resolvedAsset))) {
|
|
782
|
-
let hoisted = this.hoistedRequires.get(dep
|
|
909
|
+
let hoisted = this.hoistedRequires.get(dep);
|
|
783
910
|
if (!hoisted) {
|
|
784
911
|
hoisted = new Map();
|
|
785
|
-
this.hoistedRequires.set(dep
|
|
912
|
+
this.hoistedRequires.set(dep, hoisted);
|
|
786
913
|
}
|
|
787
|
-
hoisted.set(resolvedAsset
|
|
914
|
+
hoisted.set(resolvedAsset, `var $${publicId} = parcelRequire(${JSON.stringify(publicId)});`);
|
|
788
915
|
}
|
|
789
916
|
if (isWrapped) {
|
|
790
917
|
this.needsPrelude = true;
|
|
@@ -813,7 +940,7 @@ ${code}
|
|
|
813
940
|
}
|
|
814
941
|
if (imported === '*' || exportSymbol === '*' || isDefaultInterop) {
|
|
815
942
|
// Resolve to the namespace object if requested or this is a CJS default interop reqiure.
|
|
816
|
-
if (parentAsset === resolvedAsset && this.wrappedAssets.has(resolvedAsset
|
|
943
|
+
if (parentAsset === resolvedAsset && this.wrappedAssets.has(resolvedAsset)) {
|
|
817
944
|
// Directly use module.exports for wrapped assets importing themselves.
|
|
818
945
|
return 'module.exports';
|
|
819
946
|
} else {
|
|
@@ -841,7 +968,7 @@ ${code}
|
|
|
841
968
|
if (resolved.type !== 'js') {
|
|
842
969
|
return ['', 0];
|
|
843
970
|
}
|
|
844
|
-
let hoisted = this.hoistedRequires.get(dep
|
|
971
|
+
let hoisted = this.hoistedRequires.get(dep);
|
|
845
972
|
let res = '';
|
|
846
973
|
let lineCount = 0;
|
|
847
974
|
let isWrapped = this.isWrapped(resolved, parentAsset);
|
|
@@ -850,14 +977,23 @@ ${code}
|
|
|
850
977
|
// we need to run side effects when this asset runs. If the resolved asset is not
|
|
851
978
|
// the first one in the hoisted requires, we need to insert a parcelRequire here
|
|
852
979
|
// so it runs first.
|
|
853
|
-
if (isWrapped && !dep.meta.shouldWrap && (!hoisted || hoisted.keys().next().value !== resolved
|
|
980
|
+
if (isWrapped && !dep.meta.shouldWrap && (!hoisted || hoisted.keys().next().value !== resolved) && !this.bundleGraph.isDependencySkipped(dep) && !this.shouldSkipAsset(resolved)) {
|
|
854
981
|
this.needsPrelude = true;
|
|
855
982
|
res += `parcelRequire(${JSON.stringify(this.bundleGraph.getAssetPublicId(resolved))});`;
|
|
856
983
|
}
|
|
857
984
|
if (hoisted) {
|
|
858
985
|
this.needsPrelude = true;
|
|
859
|
-
|
|
860
|
-
|
|
986
|
+
if ((0, _featureFlags().getFeatureFlag)('applyScopeHoistingImprovementV2')) {
|
|
987
|
+
let hoistedValues = [...hoisted.values()].filter(val => !this.seenHoistedRequires.has(val));
|
|
988
|
+
for (let val of hoistedValues) {
|
|
989
|
+
this.seenHoistedRequires.add(val);
|
|
990
|
+
}
|
|
991
|
+
res += '\n' + hoistedValues.join('\n');
|
|
992
|
+
lineCount += hoisted.size;
|
|
993
|
+
} else {
|
|
994
|
+
res += '\n' + [...hoisted.values()].join('\n');
|
|
995
|
+
lineCount += hoisted.size;
|
|
996
|
+
}
|
|
861
997
|
}
|
|
862
998
|
return [res, lineCount];
|
|
863
999
|
}
|
|
@@ -865,7 +1001,7 @@ ${code}
|
|
|
865
1001
|
let prepend = '';
|
|
866
1002
|
let prependLineCount = 0;
|
|
867
1003
|
let append = '';
|
|
868
|
-
let shouldWrap = this.wrappedAssets.has(asset
|
|
1004
|
+
let shouldWrap = this.wrappedAssets.has(asset);
|
|
869
1005
|
let usedSymbols = (0, _nullthrows().default)(this.bundleGraph.getUsedSymbols(asset));
|
|
870
1006
|
let assetId = asset.meta.id;
|
|
871
1007
|
(0, _assert().default)(typeof assetId === 'string');
|
|
@@ -874,20 +1010,28 @@ ${code}
|
|
|
874
1010
|
// If there's no __esModule flag, and default is a used symbol, we need
|
|
875
1011
|
// to insert an interop helper.
|
|
876
1012
|
let defaultInterop = asset.symbols.hasExportSymbol('*') && usedSymbols.has('default') && !asset.symbols.hasExportSymbol('__esModule');
|
|
877
|
-
let usedNamespace
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1013
|
+
let usedNamespace;
|
|
1014
|
+
if ((0, _featureFlags().getFeatureFlag)('inlineConstOptimisationFix') && asset.meta.isConstantModule) {
|
|
1015
|
+
// Only set usedNamespace if there is an incoming dependency in the current bundle that uses '*'
|
|
1016
|
+
usedNamespace = this.bundleGraph.getIncomingDependencies(asset).some(dep => this.bundle.hasDependency(dep) && (0, _nullthrows().default)(this.bundleGraph.getUsedSymbols(dep)).has('*'));
|
|
1017
|
+
} else {
|
|
1018
|
+
usedNamespace =
|
|
1019
|
+
// If the asset has * in its used symbols, we might need the exports namespace.
|
|
1020
|
+
// The one case where this isn't true is in ESM library entries, where the only
|
|
1021
|
+
// dependency on * is the entry dependency. In this case, we will use ESM exports
|
|
1022
|
+
// instead of the namespace object.
|
|
1023
|
+
|
|
1024
|
+
usedSymbols.has('*') && (this.bundle.env.outputFormat !== 'esmodule' || !this.bundle.env.isLibrary || asset !== this.bundle.getMainEntry() || this.bundleGraph.getIncomingDependencies(asset).some(dep => !dep.isEntry && this.bundle.hasDependency(dep) && (0, _nullthrows().default)(this.bundleGraph.getUsedSymbols(dep)).has('*'))) ||
|
|
1025
|
+
// If a symbol is imported (used) from a CJS asset but isn't listed in the symbols,
|
|
1026
|
+
// we fallback on the namespace object.
|
|
1027
|
+
|
|
1028
|
+
asset.symbols.hasExportSymbol('*') && [...usedSymbols].some(s => !asset.symbols.hasExportSymbol(s)) ||
|
|
1029
|
+
// If the exports has this asset's namespace (e.g. ESM output from CJS input),
|
|
1030
|
+
// include the namespace object for the default export.
|
|
1031
|
+
this.exportedSymbols.has(`$${assetId}$exports`) ||
|
|
1032
|
+
// CommonJS library bundle entries always need a namespace.
|
|
1033
|
+
this.bundle.env.isLibrary && this.bundle.env.outputFormat === 'commonjs' && asset === this.bundle.getMainEntry();
|
|
1034
|
+
}
|
|
891
1035
|
|
|
892
1036
|
// If the asset doesn't have static exports, should wrap, the namespace is used,
|
|
893
1037
|
// or we need default interop, then we need to synthesize a namespace object for
|
|
@@ -905,6 +1049,7 @@ ${code}
|
|
|
905
1049
|
// Insert the __esModule interop flag for this module if it has a `default` export
|
|
906
1050
|
// and the namespace symbol is used.
|
|
907
1051
|
// TODO: only if required by CJS?
|
|
1052
|
+
|
|
908
1053
|
if (asset.symbols.hasExportSymbol('default') && usedSymbols.has('*')) {
|
|
909
1054
|
prepend += `\n$parcel$defineInteropFlag($${assetId}$exports);\n`;
|
|
910
1055
|
prependLineCount += 2;
|
|
@@ -988,16 +1133,22 @@ ${code}
|
|
|
988
1133
|
// for the symbol so that when the value changes the object property also changes. This is
|
|
989
1134
|
// required to simulate ESM live bindings. It's easier to do it this way rather than inserting
|
|
990
1135
|
// additional assignments after each mutation of the original binding.
|
|
991
|
-
|
|
992
|
-
var _asset$symbols$get2;
|
|
1136
|
+
for (let exp of usedExports) {
|
|
1137
|
+
var _asset$symbols$get2, _this$manualStaticBin;
|
|
993
1138
|
let resolved = this.getSymbolResolution(asset, asset, exp, undefined, replacements);
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1139
|
+
const meta = (_asset$symbols$get2 = asset.symbols.get(exp)) === null || _asset$symbols$get2 === void 0 ? void 0 : _asset$symbols$get2.meta;
|
|
1140
|
+
if ((0, _featureFlags().getFeatureFlag)('exportsRebindingOptimisation') && (meta !== null && meta !== void 0 && meta.isStaticBindingSafe || (_this$manualStaticBin = this.manualStaticBindingExports) !== null && _this$manualStaticBin !== void 0 && _this$manualStaticBin.some(regex => regex.test(asset.filePath)))) {
|
|
1141
|
+
append += `$${assetId}$exports[${JSON.stringify(exp)}] = ${resolved};\n`;
|
|
1142
|
+
} else {
|
|
1143
|
+
var _asset$symbols$get3;
|
|
1144
|
+
let get = this.buildFunctionExpression([], resolved);
|
|
1145
|
+
let isEsmExport = !!((_asset$symbols$get3 = asset.symbols.get(exp)) !== null && _asset$symbols$get3 !== void 0 && (_asset$symbols$get3 = _asset$symbols$get3.meta) !== null && _asset$symbols$get3 !== void 0 && _asset$symbols$get3.isEsm);
|
|
1146
|
+
let set = !isEsmExport && asset.meta.hasCJSExports ? ', ' + this.buildFunctionExpression(['v'], `${resolved} = v`) : '';
|
|
1147
|
+
prepend += `$parcel$export($${assetId}$exports, ${JSON.stringify(exp)}, ${get}${set});\n`;
|
|
1148
|
+
this.usedHelpers.add('$parcel$export');
|
|
1149
|
+
prependLineCount += 1 + usedExports.length;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1001
1152
|
}
|
|
1002
1153
|
}
|
|
1003
1154
|
return [prepend, prependLineCount, append];
|
|
@@ -1041,11 +1192,11 @@ ${code}
|
|
|
1041
1192
|
// Add the prelude if this is potentially the first JS bundle to load in a
|
|
1042
1193
|
// particular context (e.g. entry scripts in HTML, workers, etc.).
|
|
1043
1194
|
let parentBundles = this.bundleGraph.getParentBundles(this.bundle);
|
|
1044
|
-
let mightBeFirstJS = parentBundles.length === 0 || parentBundles.some(b => b.type !== 'js') || this.bundleGraph.getBundleGroupsContainingBundle(this.bundle).some(g => this.bundleGraph.isEntryBundleGroup(g)) || this.bundle.env.isIsolated() || this.bundle.bundleBehavior === 'isolated' ||
|
|
1195
|
+
let mightBeFirstJS = parentBundles.length === 0 || parentBundles.some(b => b.type !== 'js') || this.bundleGraph.getBundleGroupsContainingBundle(this.bundle).some(g => this.bundleGraph.isEntryBundleGroup(g)) || this.bundle.env.isIsolated() || this.bundle.bundleBehavior === 'isolated' || this.bundle.bundleBehavior === 'inlineIsolated' ||
|
|
1045
1196
|
// Conditional deps may be loaded before entrypoints on the server
|
|
1046
1197
|
this.hasConditionalDependency();
|
|
1047
1198
|
if (mightBeFirstJS) {
|
|
1048
|
-
let preludeCode = (0, _helpers.
|
|
1199
|
+
let preludeCode = ((0, _featureFlags().getFeatureFlag)('useNewPrelude') ? _helpers.preludeNew : _helpers.preludeOld)(this.parcelRequireName);
|
|
1049
1200
|
res += preludeCode;
|
|
1050
1201
|
if (enableSourceMaps) {
|
|
1051
1202
|
lines += (0, _utils().countLines)(preludeCode) - 1;
|
|
@@ -1068,7 +1219,7 @@ ${code}
|
|
|
1068
1219
|
}
|
|
1069
1220
|
|
|
1070
1221
|
// Add importScripts for sibling bundles in workers.
|
|
1071
|
-
if (this.bundle.env.isWorker() || this.bundle.env.isWorklet()) {
|
|
1222
|
+
if (this.bundle.env.isWorker() || this.bundle.env.isTesseract() || this.bundle.env.isWorklet()) {
|
|
1072
1223
|
let importScripts = '';
|
|
1073
1224
|
let bundles = this.bundleGraph.getReferencedBundles(this.bundle);
|
|
1074
1225
|
for (let b of bundles) {
|