From 38bb2eefbe74b469afb6d427a3c4cb85a6d6c9ed Mon Sep 17 00:00:00 2001 From: Brian Do Date: Thu, 12 May 2022 16:01:29 -0700 Subject: [PATCH] More experimental bundler fixes (#7783) * internalization bug * Cleanup * Always add assets to reachable bundles * cleanup * fixed test regressions * add ref edges whenever there are bundle edges * add edge from bundlegroups to bundles wip * * Get inline bundles in bundle group in HTML packager * Traverse each bundle instead of iterating each outbound node * Add edge between root and bundle * use and follow reference edges again * set env whenever we create bundles * Check to add parallel edges from all paths to an asset from a bundle * Always register referenced bundles before pruning when building bundle manifest * Revert "set env whenever we create bundles" This reverts commit 73ad8286f58bdd1e8a036665b439fb65697df67b. * Add test for referenced roots in bundle manifest * Add reused sibling bundles to asyncBundleRootGraph * Add test case for asset that has both an async and sync import * ExperimentalBundler: stop at isolated bundles * ExperimentalBundler: fix step 7 comment * ExperimentalBundler: initialize entry bundles with no ancestors * ExperimentalBundler: accept shared bundles extracted from workers * Remove unused async bundles if needed * Scope-hositing with new bundler: allow less duplication * Uncomment line in getSymbolResolution * Consider sibling availability before removing from ancestorAssets * Uncomment line in getSymbolResolution * Upgrade flow to 0.173.0 (#7809) * Remove reachableBundles * Bump lmdb (#7797) * Replace typeof before DCE (#7788) * Consider sibling availability before removing from ancestorAssets * Consider assets in siblings before duplicating * Remove unrelated change * Don't consider any of parent's async bundles as sibling * Remove unused structure * remove eager bundle reuse and related lending code * Alter tests with mode production and correct assets with logic for splittable bundles * Skip unused dependencies in experimental bundler * Implement getBundleFromBundleRoot * Only add dependencies to CSS module JS, not CSS * Handle multiple assets on dependencies in reachability * ScopeHoistingPackager: Handle different wrapped ancestries in wrapping dfs * skip assets for reachable if isolated or inline fix invariant * Use bundleGroup instead of bundle root for determining needsStableName * Add bundle.mainEntryAsset * fixup! Add bundle.mainEntryAsset Co-authored-by: Gora Kong Co-authored-by: Will Binns-Smith Co-authored-by: Eric Eldredge Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Co-authored-by: Agnieszka Gawrys Co-authored-by: Devon Govett --- .../experimental/src/ExperimentalBundler.js | 363 ++++++------------ packages/core/integration-tests/test/html.js | 30 ++ .../html-shared-referenced/async.js | 3 + .../html-shared-referenced/async2.js | 0 .../html-shared-referenced/index1.html | 1 + .../html-shared-referenced/index1.js | 1 + .../html-shared-referenced/index2.html | 1 + .../html-shared-referenced/index2.js | 3 + .../html-shared-referenced/package.json | 5 + .../html-shared-referenced/shared.js | 1 + .../html-shared-referenced/yarn.lock | 0 .../html-sync-async-asset/index.html | 1 + .../html-sync-async-asset/index.js | 4 + .../html-sync-async-asset/other.js | 1 + .../integration/html-sync-async-asset/test.js | 1 + .../index.html | 6 + .../core/integration-tests/test/javascript.js | 49 ++- .../integration-tests/test/scope-hoisting.js | 6 +- packages/runtimes/js/src/JSRuntime.js | 5 + packages/transformers/js/native.js | 2 +- .../utils/fs-write-stream-atomic/index.js | 18 +- packages/utils/hash/browser.js | 10 +- 22 files changed, 230 insertions(+), 281 deletions(-) create mode 100644 packages/core/integration-tests/test/integration/html-shared-referenced/async.js create mode 100644 packages/core/integration-tests/test/integration/html-shared-referenced/async2.js create mode 100644 packages/core/integration-tests/test/integration/html-shared-referenced/index1.html create mode 100644 packages/core/integration-tests/test/integration/html-shared-referenced/index1.js create mode 100644 packages/core/integration-tests/test/integration/html-shared-referenced/index2.html create mode 100644 packages/core/integration-tests/test/integration/html-shared-referenced/index2.js create mode 100644 packages/core/integration-tests/test/integration/html-shared-referenced/package.json create mode 100644 packages/core/integration-tests/test/integration/html-shared-referenced/shared.js create mode 100644 packages/core/integration-tests/test/integration/html-shared-referenced/yarn.lock create mode 100644 packages/core/integration-tests/test/integration/html-sync-async-asset/index.html create mode 100644 packages/core/integration-tests/test/integration/html-sync-async-asset/index.js create mode 100644 packages/core/integration-tests/test/integration/html-sync-async-asset/other.js create mode 100644 packages/core/integration-tests/test/integration/html-sync-async-asset/test.js create mode 100644 packages/core/integration-tests/test/integration/scope-hoisting/es6/export-intermediate-wrapped-reexports/index.html diff --git a/packages/bundlers/experimental/src/ExperimentalBundler.js b/packages/bundlers/experimental/src/ExperimentalBundler.js index c4ed607eac7..12d85953d9a 100644 --- a/packages/bundlers/experimental/src/ExperimentalBundler.js +++ b/packages/bundlers/experimental/src/ExperimentalBundler.js @@ -19,7 +19,7 @@ import {ContentGraph, Graph} from '@parcel/graph'; import invariant from 'assert'; import {ALL_EDGE_TYPES} from '@parcel/graph'; import {Bundler} from '@parcel/plugin'; -import {validateSchema, DefaultMap} from '@parcel/utils'; +import {setIntersect, validateSchema, DefaultMap} from '@parcel/utils'; import nullthrows from 'nullthrows'; import {encodeJSONKeyComponent} from '@parcel/diagnostic'; @@ -58,6 +58,7 @@ export type Bundle = {| internalizedAssetIds: Array, bundleBehavior?: ?BundleBehavior, needsStableName: boolean, + mainEntryAsset: ?Asset, size: number, sourceBundles: Array, target: Target, @@ -118,11 +119,12 @@ function decorateLegacyGraph( // Step 1: Create bundle groups, bundles, and shared bundles and add assets to them for (let [bundleNodeId, idealBundle] of idealBundleGraph.nodes) { if (idealBundle === 'root') continue; - let [entryAsset] = [...idealBundle.assets]; + let entryAsset = idealBundle.mainEntryAsset; let bundleGroup; let bundle; if (bundleGroupBundleIds.includes(bundleNodeId)) { + invariant(entryAsset != null); let dependencies = dependencyBundleGraph .getNodeIdsConnectedTo( dependencyBundleGraph.getNodeIdByContentKey(String(bundleNodeId)), @@ -178,6 +180,7 @@ function decorateLegacyGraph( }), ); } else { + invariant(entryAsset != null); bundle = nullthrows( bundleGraph.createBundle({ entryAsset, @@ -194,6 +197,7 @@ function decorateLegacyGraph( bundleGraph.addAssetToBundle(asset, bundle); } } + // Step 2: Internalize dependencies for bundles for (let [, idealBundle] of idealBundleGraph.nodes) { if (idealBundle === 'root') continue; @@ -212,18 +216,31 @@ function decorateLegacyGraph( } } } + // Step 3: Add bundles to their bundle groups - for (let [bundleId, bundleGroup] of entryBundleToBundleGroup) { - let outboundNodeIds = idealBundleGraph.getNodeIdsConnectedFrom(bundleId); + idealBundleGraph.traverse((nodeId, _, actions) => { + let node = idealBundleGraph.getNode(nodeId); + if (node === 'root') { + return; + } + actions.skipChildren(); + + let outboundNodeIds = idealBundleGraph.getNodeIdsConnectedFrom(nodeId); + let entryBundle = nullthrows(idealBundleGraph.getNode(nodeId)); + invariant(entryBundle !== 'root'); + let legacyEntryBundle = nullthrows( + idealBundleToLegacyBundle.get(entryBundle), + ); + for (let id of outboundNodeIds) { let siblingBundle = nullthrows(idealBundleGraph.getNode(id)); invariant(siblingBundle !== 'root'); let legacySiblingBundle = nullthrows( idealBundleToLegacyBundle.get(siblingBundle), ); - bundleGraph.addBundleToBundleGroup(legacySiblingBundle, bundleGroup); + bundleGraph.createBundleReference(legacyEntryBundle, legacySiblingBundle); } - } + }); // Step 4: Add references to all bundles for (let [asset, references] of idealGraph.assetReference) { @@ -263,12 +280,6 @@ function createIdealGraph( Array<[Dependency, Bundle]>, > = new DefaultMap(() => []); - // bundleRoot to all bundleRoot descendants - let reachableBundles: DefaultMap< - BundleRoot, - Set, - > = new DefaultMap(() => new Set()); - let bundleGraph: Graph = new Graph(); let stack: Array<[BundleRoot, NodeId]> = []; @@ -411,20 +422,6 @@ function createIdealGraph( ), dependencyPriorityEdges[dependency.priority], ); - - // Walk up the stack until we hit a different asset type - // and mark each bundle as reachable from every parent bundle - for (let i = stack.length - 1; i >= 0; i--) { - let [stackAsset] = stack[i]; - if ( - stackAsset.type !== childAsset.type || - stackAsset.env.context !== childAsset.env.context || - stackAsset.env.isIsolated() - ) { - break; - } - reachableBundles.get(stackAsset).add(childAsset); - } continue; } if ( @@ -432,9 +429,7 @@ function createIdealGraph( dependency.priority === 'parallel' || childAsset.bundleBehavior === 'inline' ) { - let [parentBundleRoot, bundleGroupNodeId] = nullthrows( - stack[stack.length - 1], - ); + let [, bundleGroupNodeId] = nullthrows(stack[stack.length - 1]); let bundleGroup = nullthrows( bundleGraph.getNode(bundleGroupNodeId), ); @@ -467,12 +462,6 @@ function createIdealGraph( let bundle; if (bundleId == null) { - let parentBundleId = nullthrows(bundles.get(parentBundleRoot.id)); - let parentBundle = nullthrows( - bundleGraph.getNode(parentBundleId), - ); - invariant(parentBundle !== 'root'); - // Create a new bundle if none of the same type exists already. bundle = createBundle({ // We either have an entry asset or a unique key. @@ -489,7 +478,7 @@ function createIdealGraph( (dependency.priority === 'parallel' && !dependency.needsStableName) ? false - : parentBundle.needsStableName, + : bundleGroup.needsStableName, }); bundleId = bundleGraph.addNode(bundle); } else { @@ -566,22 +555,18 @@ function createIdealGraph( let reachableRoots: ContentGraph = new ContentGraph(); for (let [root] of bundleRoots) { let rootNodeId = reachableRoots.addNodeByContentKeyIfNeeded(root.id, root); - assetGraph.traverse((node, isAsync, actions) => { + assetGraph.traverse((node, _, actions) => { if (node.value === root) { return; } - if (node.type === 'dependency') { let dependency = node.value; if (dependencyBundleGraph.hasContentKey(dependency.id)) { - if ( - dependency.priority === 'lazy' || - dependency.priority === 'parallel' - ) { + if (dependency.priority !== 'sync') { let assets = assetGraph.getDependencyAssets(dependency); if (assets.length === 0) { - return node; + return; } invariant(assets.length === 1); @@ -601,16 +586,23 @@ function createIdealGraph( ); } } - return; + } + + if (dependency.priority !== 'sync') { + actions.skipChildren(); } return; } - - if (bundleRoots.has(node.value)) { + //asset node type + let asset = node.value; + if ( + asset.bundleBehavior === 'isolated' || + asset.bundleBehavior === 'inline' || + root.type !== asset.type + ) { actions.skipChildren(); return; } - let nodeId = reachableRoots.addNodeByContentKeyIfNeeded( node.value.id, node.value, @@ -621,99 +613,75 @@ function createIdealGraph( // Maps a given bundleRoot to the assets reachable from it, // and the bundleRoots reachable from each of these assets - let ancestorAssets: Map< - BundleRoot, - Map | null>, - > = new Map(); - - // Reference count of each asset available within a given bundleRoot's bundle group - let assetRefsInBundleGroup: DefaultMap< - BundleRoot, - DefaultMap, - > = new DefaultMap(() => new DefaultMap(() => 0)); + let asyncAncestorAssets: Map> = new Map(); // Step 4: Determine assets that should be duplicated by computing asset availability in each bundle group + for (let entry of entries.keys()) { + // Initialize an empty set of ancestors available to entries + asyncAncestorAssets.set(entry, new Set()); + } + + // Visit nodes in a topological order, visiting parent nodes before child nodes. + // This allows us to construct an understanding of which assets will already be + // loaded and available when a bundle runs, by pushing available assets downwards and + // computing the intersection of assets available through all possible paths to a bundle. for (let nodeId of asyncBundleRootGraph.topoSort()) { const bundleRoot = asyncBundleRootGraph.getNode(nodeId); if (bundleRoot === 'root') continue; invariant(bundleRoot != null); - let ancestors = ancestorAssets.get(bundleRoot); - - // First consider bundle group asset availability, processing only - // non-isolated bundles within that bundle group let bundleGroupId = nullthrows(bundleRoots.get(bundleRoot))[1]; - // Map of assets in the bundle group to their refcounts - let assetRefs = assetRefsInBundleGroup.get(bundleRoot); - - for (let bundleIdInGroup of [ - bundleGroupId, - ...bundleGraph.getNodeIdsConnectedFrom(bundleGroupId), - ]) { - let bundleInGroup = nullthrows(bundleGraph.getNode(bundleIdInGroup)); - invariant(bundleInGroup !== 'root'); - if ( - bundleInGroup.bundleBehavior === 'isolated' || - bundleInGroup.bundleBehavior === 'inline' - ) { - continue; - } - let [bundleRoot] = [...bundleInGroup.assets]; - // Assets directly connected to current bundleRoot - let assetsFromBundleRoot = reachableRoots - .getNodeIdsConnectedFrom( - reachableRoots.getNodeIdByContentKey(bundleRoot.id), - ) - .map(id => nullthrows(reachableRoots.getNode(id))); - for (let asset of assetsFromBundleRoot) { - assetRefs.set(asset, assetRefs.get(asset) + 1); - } - } + let available; + if (bundleRoot.bundleBehavior === 'isolated') { + available = new Set(); + } else { + available = new Set(asyncAncestorAssets.get(bundleRoot)); + for (let bundleIdInGroup of [ + bundleGroupId, + ...bundleGraph.getNodeIdsConnectedFrom(bundleGroupId), + ]) { + let bundleInGroup = nullthrows(bundleGraph.getNode(bundleIdInGroup)); + invariant(bundleInGroup !== 'root'); + if ( + bundleInGroup.bundleBehavior === 'isolated' || + bundleInGroup.bundleBehavior === 'inline' + ) { + continue; + } - // Enumerate bundleRoots connected to the node (parent), taking the intersection - // between the assets synchronously loaded by the parent, and those loaded by the child. - let bundleGroupAssets = new Set(assetRefs.keys()); + for (let bundleRoot of bundleInGroup.assets) { + // Assets directly connected to current bundleRoot + let assetsFromBundleRoot = reachableRoots + .getNodeIdsConnectedFrom( + reachableRoots.getNodeIdByContentKey(bundleRoot.id), + ) + .map(id => nullthrows(reachableRoots.getNode(id))); - let combined = ancestors - ? new Map([...bundleGroupAssets, ...ancestors.keys()].map(a => [a, null])) - : new Map([...bundleGroupAssets].map(a => [a, null])); + for (let asset of [bundleRoot, ...assetsFromBundleRoot]) { + available.add(asset); + } + } + } + } let children = asyncBundleRootGraph.getNodeIdsConnectedFrom(nodeId); - + // Group assets available across our children by the child. This will be used + // to determine borrowers if needed below. for (let childId of children) { let child = asyncBundleRootGraph.getNode(childId); invariant(child !== 'root' && child != null); - const availableAssets = ancestorAssets.get(child); - - if (availableAssets != null) { - ancestryIntersect(availableAssets, combined); - } else { - ancestorAssets.set(child, combined); + if ( + child.bundleBehavior === 'isolated' || + child.bundleBehavior === 'inline' + ) { + continue; } - } - - let siblingAncestors = ancestors - ? ancestryUnion(new Set(ancestors.keys()), assetRefs, bundleRoot) - : new Map([...bundleGroupAssets].map(a => [a, [bundleRoot]])); - - for (let bundleIdInGroup of bundleGraph.getNodeIdsConnectedFrom( - bundleGroupId, - )) { - let bundleInGroup = bundleGraph.getNode(bundleIdInGroup); - invariant( - bundleInGroup != null && - bundleInGroup !== 'root' && - bundleInGroup.assets != null, - ); - let [bundleRoot] = [...bundleInGroup.assets]; - - const availableAssets = ancestorAssets.get(bundleRoot); - - if (availableAssets != null) { - ancestryIntersect(availableAssets, siblingAncestors); + const childAvailableAssets = asyncAncestorAssets.get(child); + if (childAvailableAssets != null) { + setIntersect(childAvailableAssets, available); } else { - ancestorAssets.set(bundleRoot, siblingAncestors); + asyncAncestorAssets.set(child, new Set(available)); } } } @@ -730,96 +698,24 @@ function createIdealGraph( // Filter out bundles from this asset's reachable array if // bundle does not contain the asset in its ancestry - // or if any of the bundles in the ancestry have a refcount of <= 1 for that asset, - // meaning it may not be deduplicated. Otherwise, decrement all references in - // the ancestry and keep it - reachable = reachable.filter(b => { - let ancestry = ancestorAssets.get(b)?.get(asset); - if (ancestry === undefined) { - // No reachable bundles from this asset - return true; - } else if (ancestry === null) { - // Asset is reachable from this bundle - return false; - } else { - // If every bundle in its ancestry has more than 1 reference to the asset - if ( - ancestry.every( - bundleId => assetRefsInBundleGroup.get(bundleId).get(asset) > 1, - ) - ) { - for (let bundleRoot of ancestry) { - assetRefsInBundleGroup - .get(bundleRoot) - .set( - asset, - assetRefsInBundleGroup.get(bundleRoot).get(asset) - 1, - ); - } - return false; - } - return true; - } - }); - - let rootBundleTuple = bundleRoots.get(asset); - if (rootBundleTuple != null) { - let rootBundle = nullthrows(bundleGraph.getNode(rootBundleTuple[0])); - invariant(rootBundle !== 'root'); - - if (!rootBundle.env.isIsolated()) { - if (!bundles.has(asset.id)) { - bundles.set(asset.id, rootBundleTuple[0]); - } - - for (let reachableAsset of reachable) { - if (reachableAsset !== asset) { - bundleGraph.addEdge( - nullthrows(bundleRoots.get(reachableAsset))[1], - rootBundleTuple[0], - ); - } - } - - let willInternalizeRoots = asyncBundleRootGraph - .getNodeIdsConnectedTo( - asyncBundleRootGraph.getNodeIdByContentKey(asset.id), - ) - .map(id => nullthrows(asyncBundleRootGraph.getNode(id))) - .filter(bundleRoot => { - if (bundleRoot === 'root') { - return false; - } + reachable = reachable.filter(b => !asyncAncestorAssets.get(b)?.has(asset)); - return ( - reachableRoots.hasEdge( - reachableRoots.getNodeIdByContentKey(bundleRoot.id), - reachableRoots.getNodeIdByContentKey(asset.id), - ) || ancestorAssets.get(bundleRoot)?.has(asset) - ); - }) - .map(bundleRoot => { - // For Flow - invariant(bundleRoot !== 'root'); - return bundleRoot; - }); - - for (let bundleRoot of willInternalizeRoots) { - if (bundleRoot !== asset) { - let bundle = nullthrows( - bundleGraph.getNode(nullthrows(bundles.get(bundleRoot.id))), - ); - invariant(bundle !== 'root'); - bundle.internalizedAssetIds.push(asset.id); - } - } - } - } else if (reachable.length > 0) { + if (reachable.length > 0) { let reachableEntries = reachable.filter( - a => entries.has(a) || !a.isBundleSplittable, + a => + entries.has(a) || + !a.isBundleSplittable || + getBundleFromBundleRoot(a).needsStableName || + getBundleFromBundleRoot(a).bundleBehavior === 'inline' || + getBundleFromBundleRoot(a).bundleBehavior === 'isolated', ); reachable = reachable.filter( - a => !entries.has(a) && a.isBundleSplittable, + a => + !entries.has(a) && + a.isBundleSplittable && + !getBundleFromBundleRoot(a).needsStableName && + getBundleFromBundleRoot(a).bundleBehavior !== 'inline' && + getBundleFromBundleRoot(a).bundleBehavior !== 'isolated', ); // Add assets to non-splittable bundles. @@ -872,8 +768,8 @@ function createIdealGraph( } } - // Step 7: Merge any sibling bundles required by entry bundles back into the entry bundle. - // Entry bundles must be predictable, so cannot have unpredictable siblings. + // Step 7: Merge any shared bundles under the minimum bundle size back into + // their source bundles, and remove the bundle. for (let [bundleNodeId, bundle] of bundleGraph.nodes) { if (bundle === 'root') continue; if (bundle.sourceBundles.length > 0 && bundle.size < config.minBundleSize) { @@ -882,6 +778,14 @@ function createIdealGraph( } } + function getBundleFromBundleRoot(bundleRoot: BundleRoot): Bundle { + let bundle = bundleGraph.getNode( + nullthrows(bundleRoots.get(bundleRoot))[0], + ); + invariant(bundle !== 'root' && bundle != null); + return bundle; + } + return { bundleGraph, dependencyBundleGraph, @@ -925,6 +829,7 @@ function createBundle(opts: {| uniqueKey: opts.uniqueKey, assets: new Set(), internalizedAssetIds: [], + mainEntryAsset: null, size: 0, sourceBundles: [], target: opts.target, @@ -940,6 +845,7 @@ function createBundle(opts: {| uniqueKey: opts.uniqueKey, assets: new Set([asset]), internalizedAssetIds: [], + mainEntryAsset: asset, size: asset.stats.size, sourceBundles: [], target: opts.target, @@ -1002,43 +908,6 @@ async function loadBundlerConfig( }; } -function ancestryUnion( - ancestors: Set, - assetRefs: Map, - bundleRoot: BundleRoot, -): Map | null> { - let map = new Map(); - for (let a of ancestors) { - map.set(a, null); - } - for (let [asset, refCount] of assetRefs) { - if (!ancestors.has(asset) && refCount > 1) { - map.set(asset, [bundleRoot]); - } - } - return map; -} - -function ancestryIntersect( - currentMap: Map | null>, - map: Map | null>, -): void { - for (let [bundleRoot, currentAssets] of currentMap) { - if (map.has(bundleRoot)) { - let assets = map.get(bundleRoot); - if (assets) { - if (currentAssets) { - currentAssets.push(...assets); - } else { - currentMap.set(bundleRoot, [...assets]); - } - } - } else { - currentMap.delete(bundleRoot); - } - } -} - function getReachableBundleRoots(asset, graph): Array { return graph .getNodeIdsConnectedTo(graph.getNodeIdByContentKey(asset.id)) diff --git a/packages/core/integration-tests/test/html.js b/packages/core/integration-tests/test/html.js index a215957a03c..b8b20d51191 100644 --- a/packages/core/integration-tests/test/html.js +++ b/packages/core/integration-tests/test/html.js @@ -2841,4 +2841,34 @@ describe('html', function () { }, ); }); + + it('extracts shared bundles that load referenced bundle roots across entries', async () => { + let b = await bundle( + ['index1.html', 'index2.html'].map(entry => + path.join(__dirname, 'integration/html-shared-referenced', entry), + ), + { + mode: 'production', + defaultTargetOptions: { + shouldOptimize: false, + }, + }, + ); + + await run(b); + }); + + it('should not skip bundleRoots if an asset is both async required and static required', async function () { + let b = await bundle( + path.join(__dirname, 'integration/html-sync-async-asset/index.html'), + { + mode: 'production', + defaultTargetOptions: { + shouldOptimize: false, + }, + }, + ); + + await run(b, {output: null}, {require: false}); + }); }); diff --git a/packages/core/integration-tests/test/integration/html-shared-referenced/async.js b/packages/core/integration-tests/test/integration/html-shared-referenced/async.js new file mode 100644 index 00000000000..ad07c1e9102 --- /dev/null +++ b/packages/core/integration-tests/test/integration/html-shared-referenced/async.js @@ -0,0 +1,3 @@ +import './async2.js'; + +import('./async2.js'); diff --git a/packages/core/integration-tests/test/integration/html-shared-referenced/async2.js b/packages/core/integration-tests/test/integration/html-shared-referenced/async2.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/integration/html-shared-referenced/index1.html b/packages/core/integration-tests/test/integration/html-shared-referenced/index1.html new file mode 100644 index 00000000000..befa6f91239 --- /dev/null +++ b/packages/core/integration-tests/test/integration/html-shared-referenced/index1.html @@ -0,0 +1 @@ + diff --git a/packages/core/integration-tests/test/integration/html-shared-referenced/index1.js b/packages/core/integration-tests/test/integration/html-shared-referenced/index1.js new file mode 100644 index 00000000000..c21c06aca8a --- /dev/null +++ b/packages/core/integration-tests/test/integration/html-shared-referenced/index1.js @@ -0,0 +1 @@ +import './shared'; diff --git a/packages/core/integration-tests/test/integration/html-shared-referenced/index2.html b/packages/core/integration-tests/test/integration/html-shared-referenced/index2.html new file mode 100644 index 00000000000..edbd81997e9 --- /dev/null +++ b/packages/core/integration-tests/test/integration/html-shared-referenced/index2.html @@ -0,0 +1 @@ + diff --git a/packages/core/integration-tests/test/integration/html-shared-referenced/index2.js b/packages/core/integration-tests/test/integration/html-shared-referenced/index2.js new file mode 100644 index 00000000000..04c9991d158 --- /dev/null +++ b/packages/core/integration-tests/test/integration/html-shared-referenced/index2.js @@ -0,0 +1,3 @@ +import './async.js'; +import './shared.js'; + diff --git a/packages/core/integration-tests/test/integration/html-shared-referenced/package.json b/packages/core/integration-tests/test/integration/html-shared-referenced/package.json new file mode 100644 index 00000000000..c5f216e03fd --- /dev/null +++ b/packages/core/integration-tests/test/integration/html-shared-referenced/package.json @@ -0,0 +1,5 @@ +{ + "@parcel/bundler-default": { + "minBundleSize": 0 + } +} diff --git a/packages/core/integration-tests/test/integration/html-shared-referenced/shared.js b/packages/core/integration-tests/test/integration/html-shared-referenced/shared.js new file mode 100644 index 00000000000..6c111b7bca1 --- /dev/null +++ b/packages/core/integration-tests/test/integration/html-shared-referenced/shared.js @@ -0,0 +1 @@ +import('./async.js'); diff --git a/packages/core/integration-tests/test/integration/html-shared-referenced/yarn.lock b/packages/core/integration-tests/test/integration/html-shared-referenced/yarn.lock new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/integration/html-sync-async-asset/index.html b/packages/core/integration-tests/test/integration/html-sync-async-asset/index.html new file mode 100644 index 00000000000..8ef9a70b216 --- /dev/null +++ b/packages/core/integration-tests/test/integration/html-sync-async-asset/index.html @@ -0,0 +1 @@ + diff --git a/packages/core/integration-tests/test/integration/html-sync-async-asset/index.js b/packages/core/integration-tests/test/integration/html-sync-async-asset/index.js new file mode 100644 index 00000000000..afd241a43f8 --- /dev/null +++ b/packages/core/integration-tests/test/integration/html-sync-async-asset/index.js @@ -0,0 +1,4 @@ +import t from "./test.js"; + +import("./other.js") + .then((v) => v.default) diff --git a/packages/core/integration-tests/test/integration/html-sync-async-asset/other.js b/packages/core/integration-tests/test/integration/html-sync-async-asset/other.js new file mode 100644 index 00000000000..6be91ea6cc7 --- /dev/null +++ b/packages/core/integration-tests/test/integration/html-sync-async-asset/other.js @@ -0,0 +1 @@ +export default import("./test.js"); diff --git a/packages/core/integration-tests/test/integration/html-sync-async-asset/test.js b/packages/core/integration-tests/test/integration/html-sync-async-asset/test.js new file mode 100644 index 00000000000..58c57157d36 --- /dev/null +++ b/packages/core/integration-tests/test/integration/html-sync-async-asset/test.js @@ -0,0 +1 @@ +export default "test"; diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/export-intermediate-wrapped-reexports/index.html b/packages/core/integration-tests/test/integration/scope-hoisting/es6/export-intermediate-wrapped-reexports/index.html new file mode 100644 index 00000000000..233f9c37633 --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/export-intermediate-wrapped-reexports/index.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/core/integration-tests/test/javascript.js b/packages/core/integration-tests/test/javascript.js index 2ca75a87198..592a4d4d008 100644 --- a/packages/core/integration-tests/test/javascript.js +++ b/packages/core/integration-tests/test/javascript.js @@ -1036,31 +1036,42 @@ describe('javascript', function () { let b = await bundle( path.join(__dirname, '/integration/workers-module/index.js'), { + mode: 'production', defaultTargetOptions: { + shouldOptimize: false, shouldScopeHoist: true, }, }, ); - assertBundles(b, [ { - assets: ['dedicated-worker.js', 'index.js'], + assets: ['dedicated-worker.js'], }, { name: 'index.js', - assets: ['index.js', 'bundle-url.js', 'get-worker-url.js'], + assets: [ + 'index.js', + 'bundle-url.js', + 'get-worker-url.js', + 'bundle-manifest.js', + ], }, { - assets: ['shared-worker.js', 'index.js'], + assets: ['shared-worker.js'], + }, + { + assets: ['index.js'], }, ]); let dedicated, shared; b.traverseBundles((bundle, ctx, traversal) => { - if (bundle.getMainEntry().filePath.endsWith('shared-worker.js')) { + let mainEntry = bundle.getMainEntry(); + if (mainEntry && mainEntry.filePath.endsWith('shared-worker.js')) { shared = bundle; } else if ( - bundle.getMainEntry().filePath.endsWith('dedicated-worker.js') + mainEntry && + mainEntry.filePath.endsWith('dedicated-worker.js') ) { dedicated = bundle; } @@ -1086,7 +1097,9 @@ describe('javascript', function () { let b = await bundle( path.join(__dirname, '/integration/workers-module/index.js'), { + mode: 'production', defaultTargetOptions: { + shouldOptimize: false, shouldScopeHoist, engines: { browsers: '>= 0.25%', @@ -1097,31 +1110,36 @@ describe('javascript', function () { assertBundles(b, [ { - assets: [ - 'dedicated-worker.js', - !shouldScopeHoist && 'esmodule-helpers.js', - 'index.js', - ].filter(Boolean), + assets: ['dedicated-worker.js'], }, { name: 'index.js', - assets: ['index.js', 'bundle-url.js', 'get-worker-url.js'], + assets: [ + 'index.js', + 'bundle-url.js', + 'get-worker-url.js', + 'bundle-manifest.js', + ], }, { assets: [ !shouldScopeHoist && 'esmodule-helpers.js', - 'shared-worker.js', 'index.js', ].filter(Boolean), }, + { + assets: ['shared-worker.js'], + }, ]); let dedicated, shared; b.traverseBundles((bundle, ctx, traversal) => { - if (bundle.getMainEntry().filePath.endsWith('shared-worker.js')) { + let mainEntry = bundle.getMainEntry(); + if (mainEntry && mainEntry.filePath.endsWith('shared-worker.js')) { shared = bundle; } else if ( - bundle.getMainEntry().filePath.endsWith('dedicated-worker.js') + mainEntry && + mainEntry.filePath.endsWith('dedicated-worker.js') ) { dedicated = bundle; } @@ -1977,6 +1995,7 @@ describe('javascript', function () { }); it('should contain duplicate assets in workers when in development', async () => { + if (process.env.PARCEL_TEST_EXPERIMENTAL_BUNDLER) return; let b = await bundle( path.join(__dirname, '/integration/worker-shared/index.js'), {mode: 'development'}, diff --git a/packages/core/integration-tests/test/scope-hoisting.js b/packages/core/integration-tests/test/scope-hoisting.js index 2c9508fe405..5ff9f13fb24 100644 --- a/packages/core/integration-tests/test/scope-hoisting.js +++ b/packages/core/integration-tests/test/scope-hoisting.js @@ -5063,7 +5063,11 @@ describe('scope hoisting', function () { ], }, {assets: ['dep.js']}, - {assets: ['async-has-dep.js', 'dep.js', 'get-dep.js']}, + { + assets: process.env.PARCEL_TEST_EXPERIMENTAL_BUNDLER + ? ['async-has-dep.js'] + : ['async-has-dep.js', 'dep.js', 'get-dep.js'], + }, {assets: ['get-dep.js']}, ]); diff --git a/packages/runtimes/js/src/JSRuntime.js b/packages/runtimes/js/src/JSRuntime.js index f7751dbe971..a05f1cf7a13 100644 --- a/packages/runtimes/js/src/JSRuntime.js +++ b/packages/runtimes/js/src/JSRuntime.js @@ -552,6 +552,11 @@ function getRegisterCode( idToName[bundle.publicId] = path.basename(nullthrows(bundle.name)); if (bundle !== entryBundle && isNewContext(bundle, bundleGraph)) { + for (let referenced of bundleGraph.getReferencedBundles(bundle)) { + idToName[referenced.publicId] = path.basename( + nullthrows(referenced.name), + ); + } // New contexts have their own manifests, so there's no need to continue. actions.skipChildren(); } diff --git a/packages/transformers/js/native.js b/packages/transformers/js/native.js index ad423318745..ce1d82238c9 100644 --- a/packages/transformers/js/native.js +++ b/packages/transformers/js/native.js @@ -18,7 +18,7 @@ if (process.env.PARCEL_BUILD_ENV === 'production') { } else if (process.env.PARCEL_SWC_WASM) { const {transform} = require('./wasm/dist-node/parcel_js_swc_wasm.js'); - module.exports.transform = function(config) { + module.exports.transform = function (config) { let result = transform(config); return { ...result, diff --git a/packages/utils/fs-write-stream-atomic/index.js b/packages/utils/fs-write-stream-atomic/index.js index eec056fb25f..849ea300260 100644 --- a/packages/utils/fs-write-stream-atomic/index.js +++ b/packages/utils/fs-write-stream-atomic/index.js @@ -64,25 +64,25 @@ function WriteStreamAtomic(path, options) { // data has been written to our target stream. So we suppress // finish from being emitted here, and only emit it after our // target stream is closed and we've moved everything around. -WriteStreamAtomic.prototype.emit = function(event) { +WriteStreamAtomic.prototype.emit = function (event) { if (event === 'finish') return this.__atomicStream.end(); return Writable.prototype.emit.apply(this, arguments); }; -WriteStreamAtomic.prototype._write = function(buffer, encoding, cb) { +WriteStreamAtomic.prototype._write = function (buffer, encoding, cb) { var flushed = this.__atomicStream.write(buffer, encoding); if (flushed) return cb(); this.__atomicStream.once('drain', cb); }; function handleOpen(writeStream) { - return function(fd) { + return function (fd) { writeStream.emit('open', fd); }; } function handleClose(writeStream) { - return function() { + return function () { if (writeStream.__atomicClosed) return; writeStream.__atomicClosed = true; if (writeStream.__atomicChown) { @@ -127,13 +127,13 @@ function handleClose(writeStream) { var targetFileHash = crypto.createHash('sha512'); fs.createReadStream(writeStream.__atomicTmp) - .on('data', function(data, enc) { + .on('data', function (data, enc) { tmpFileHash.update(data, enc); }) .on('error', fileHashError) .on('end', fileHashComplete); fs.createReadStream(writeStream.__atomicTarget) - .on('data', function(data, enc) { + .on('data', function (data, enc) { targetFileHash.update(data, enc); }) .on('error', fileHashError) @@ -157,7 +157,7 @@ function handleClose(writeStream) { } function cleanup(err) { - fs.unlink(writeStream.__atomicTmp, function() { + fs.unlink(writeStream.__atomicTmp, function () { if (err) { writeStream.emit('error', err); writeStream.emit('close'); @@ -175,14 +175,14 @@ function handleClose(writeStream) { // Delay the close to provide the same temporal separation a physical // file operation would have– that is, the close event is emitted only // after the async close operation completes. - setImmediate(function() { + setImmediate(function () { writeStream.emit('close'); }); } } function handleError(writeStream) { - return function(er) { + return function (er) { cleanupSync(); writeStream.emit('error', er); writeStream.__atomicClosed = true; diff --git a/packages/utils/hash/browser.js b/packages/utils/hash/browser.js index d5f4dbc8d27..7f75a142370 100644 --- a/packages/utils/hash/browser.js +++ b/packages/utils/hash/browser.js @@ -49,13 +49,7 @@ function concatUint8Arrays(arrays) { function toHex(arr) { let dataView = new DataView(arr.buffer); return ( - dataView - .getUint32(0, true) - .toString(16) - .padStart(8, '0') + - dataView - .getUint32(4, true) - .toString(16) - .padStart(8, '0') + dataView.getUint32(0, true).toString(16).padStart(8, '0') + + dataView.getUint32(4, true).toString(16).padStart(8, '0') ); }