From a93af6a3d4f3de9d51f0f9644cfbb3d5d4431f7c Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Sat, 10 Feb 2024 14:07:37 -0500 Subject: [PATCH] Allow parallel type change bundles to be reused by async siblings (#9504) --- .../bundlers/default/src/DefaultBundler.js | 384 ++++-------------- packages/core/core/src/BundleGraph.js | 7 +- .../core/integration-tests/test/bundler.js | 47 ++- .../integration-tests/test/css-modules.js | 30 +- packages/core/integration-tests/test/css.js | 9 +- packages/core/integration-tests/test/html.js | 38 +- .../css-modules-import/package.json | 5 + .../package.json | 5 + .../shared-sibling-entries-multiple/yarn.lock | 0 .../shared-sibling-entries/package.json | 5 + .../shared-sibling-entries/yarn.lock | 0 .../core/integration-tests/test/monorepos.js | 28 +- packages/core/test-utils/src/utils.js | 67 ++- 13 files changed, 229 insertions(+), 396 deletions(-) create mode 100644 packages/core/integration-tests/test/integration/css-modules-import/package.json create mode 100644 packages/core/integration-tests/test/integration/shared-sibling-entries-multiple/package.json create mode 100644 packages/core/integration-tests/test/integration/shared-sibling-entries-multiple/yarn.lock create mode 100644 packages/core/integration-tests/test/integration/shared-sibling-entries/package.json create mode 100644 packages/core/integration-tests/test/integration/shared-sibling-entries/yarn.lock diff --git a/packages/bundlers/default/src/DefaultBundler.js b/packages/bundlers/default/src/DefaultBundler.js index 20af3564e1e..deebea75d38 100644 --- a/packages/bundlers/default/src/DefaultBundler.js +++ b/packages/bundlers/default/src/DefaultBundler.js @@ -1,4 +1,3 @@ -/* eslint-disable */ // @flow strict-local import type { Asset, @@ -12,6 +11,7 @@ import type { PluginOptions, Target, BuildMode, + PluginLogger, } from '@parcel/types'; import type {NodeId} from '@parcel/graph'; import type {SchemaEntity} from '@parcel/utils'; @@ -19,14 +19,7 @@ import {ContentGraph, Graph, BitSet, ALL_EDGE_TYPES} from '@parcel/graph'; import invariant from 'assert'; import {Bundler} from '@parcel/plugin'; -import { - setEqual, - setIntersect, - validateSchema, - DefaultMap, - globToRegex, -} from '@parcel/utils'; -import logger from '@parcel/logger'; +import {validateSchema, DefaultMap, globToRegex} from '@parcel/utils'; import nullthrows from 'nullthrows'; import path from 'path'; import {encodeJSONKeyComponent} from '@parcel/diagnostic'; @@ -143,8 +136,8 @@ type IdealGraph = {| * */ export default (new Bundler({ - loadConfig({config, options}) { - return loadBundlerConfig(config, options); + loadConfig({config, options, logger}) { + return loadBundlerConfig(config, options, logger); }, bundle({bundleGraph, config}) { @@ -431,7 +424,6 @@ function createIdealGraph( let assets = []; let assetToIndex = new Map(); - let typeChangeIds = new Set(); function makeManualAssetToConfigLookup() { let manualAssetToConfig = new Map(); let constantModuleToMSB = new DefaultMap(() => []); @@ -559,7 +551,6 @@ function createIdealGraph( let dependency = node.value; invariant(context?.type === 'asset'); - let parentAsset = context.value; let assets = assetGraph.getDependencyAssets(dependency); if (assets.length === 0) { @@ -651,7 +642,6 @@ function createIdealGraph( dependencyPriorityEdges[dependency.priority], ); } else if ( - parentAsset.type !== childAsset.type || dependency.priority === 'parallel' || childAsset.bundleBehavior === 'inline' ) { @@ -672,22 +662,6 @@ function createIdealGraph( ); invariant(referencingBundle !== 'root'); - /** - * If this is an entry bundlegroup, we only allow one bundle per type in those groups - * So attempt to add the asset to the entry bundle if it's of the same type. - * This asset will be created by other dependency if it's in another bundlegroup - * and bundles of other types should be merged in the next step - */ - let bundleGroupRootAsset = nullthrows(bundleGroup.mainEntryAsset); - if ( - parentAsset.type !== childAsset.type && - entries.has(bundleGroupRootAsset) && - canMerge(bundleGroupRootAsset, childAsset) && - dependency.bundleBehavior == null && - manualSharedBundleKey == null //exclude MSBs for merging - ) { - bundleId = bundleGroupNodeId; - } if (bundleId == null) { bundle = createBundle({ // Bundles created from type changes shouldn't have an entry asset. @@ -706,11 +680,6 @@ function createIdealGraph( : referencingBundle.needsStableName, }); bundleId = bundleGraph.addNode(bundle); - - // Store Type-Change bundles for later since we need to know ALL bundlegroups they are part of to reduce/combine them - if (parentAsset.type !== childAsset.type) { - typeChangeIds.add(bundleId); - } } else { bundle = bundleGraph.getNode(bundleId); invariant(bundle != null && bundle !== 'root'); @@ -816,128 +785,6 @@ function createIdealGraph( bundleGroupBundleIds.delete(nodeId); // manual bundles can now act as shared, non-bundle group, should they be non-bundleRoots as well? } - // Step Merge Type Change Bundles: Clean up type change bundles within the exact same bundlegroups - for (let [nodeIdA, a] of bundleGraph.nodes.entries()) { - //if bundle b bundlegroups ==== bundle a bundlegroups then combine type changes - if (!a || !typeChangeIds.has(nodeIdA) || a === 'root') continue; - let bundleABundleGroups = getBundleGroupsForBundle(nodeIdA); - for (let [nodeIdB, b] of bundleGraph.nodes.entries()) { - if ( - b && - a !== 'root' && - b !== 'root' && - a !== b && - typeChangeIds.has(nodeIdB) && - canMerge(a, b) - ) { - let bundleBbundleGroups = getBundleGroupsForBundle(nodeIdB); - if (setEqual(bundleBbundleGroups, bundleABundleGroups)) { - let shouldMerge = true; - for (let depId of dependencyBundleGraph.getNodeIdsConnectedTo( - dependencyBundleGraph.getNodeIdByContentKey(String(nodeIdB)), - ALL_EDGE_TYPES, - )) { - let depNode = dependencyBundleGraph.getNode(depId); - // Cannot merge Dependency URL specifier type - if ( - depNode && - depNode.type === 'dependency' && - depNode.value.specifierType === 'url' - ) { - shouldMerge = false; - continue; - } - } - if (!shouldMerge) continue; - mergeBundle(nodeIdA, nodeIdB); - } else if (a.needsStableName || b.needsStableName) { - let overlap = new Set(bundleBbundleGroups); - setIntersect(overlap, bundleABundleGroups); - if (overlap.size > 0) { - // Two bundles are the same type and exist in the same bundle group but cannot be (fully) merged - // We must duplicate or create a new bundle in this case - - let shouldCreateNewBundle = - overlap.size == bundleBbundleGroups.size || - overlap.size == bundleABundleGroups.size - ? false - : true; - if (shouldCreateNewBundle) { - // Neither bundle can be the a host since both have unique groups - // So we may need to generate a new bundle for the intersection instead - } else { - // If the overlap of bundleGroups is a subset, then we should duplicate the bundle - // that results in a correct graph - let hostBundle = - overlap.size == bundleBbundleGroups.size - ? [nodeIdB, b, bundleBbundleGroups] - : [nodeIdA, a, bundleABundleGroups]; - let duplicatedBundle = - overlap.size == bundleBbundleGroups.size - ? [nodeIdA, a, bundleABundleGroups] - : [nodeIdB, b, bundleBbundleGroups]; - for (let asset of duplicatedBundle[1].assets) { - hostBundle[1].assets.add(asset); - hostBundle[1].size += asset.stats.size; - } - for (let group of overlap) { - let bundleGroup = nullthrows(bundleGraph.getNode(group)); - invariant( - bundleGroup != null && bundleGroup !== 'root', - 'Something went wrong with accessing a bundleGroup', - ); - // Patch up dependency bundleGraph - for (let depId of dependencyBundleGraph.getNodeIdsConnectedTo( - dependencyBundleGraph.getNodeIdByContentKey( - String(duplicatedBundle[0]), - ), - ALL_EDGE_TYPES, - )) { - let depNode = dependencyBundleGraph.getNode(depId); - invariant(bundleGroup.mainEntryAsset != null); - if ( - depNode && - depNode.type === 'dependency' && - depNode.value.sourcePath == - bundleGroup.mainEntryAsset.filePath - ) { - dependencyBundleGraph.addEdge( - depId, - dependencyBundleGraph.getNodeIdByContentKey( - String(hostBundle[0]), - ), - dependencyPriorityEdges.parallel, - ); - - dependencyBundleGraph.removeEdge( - depId, - dependencyBundleGraph.getNodeIdByContentKey( - String(duplicatedBundle[0]), - ), - dependencyPriorityEdges.parallel, - ); - for (let asset of duplicatedBundle[1].assets) { - replaceAssetReference( - asset, - duplicatedBundle[1], - hostBundle[1], - depNode.value, - ); - } - } - } - // This might be a referencing bundle, not necessarily the group, so we - detachFromBundleGroup(group, duplicatedBundle[0]); - } - } - //We might've changed the bundleGroups of A, which should be recalculated; - bundleABundleGroups = getBundleGroupsForBundle(nodeIdA); - } - } - } - } - } - /** * Step Determine Reachability: Determine reachability for every asset from each bundleRoot. * This is later used to determine which bundles to place each asset in. We build up two @@ -1028,7 +875,7 @@ function createIdealGraph( } //asset node type let asset = node.value; - if (asset.bundleBehavior != null || root.type !== asset.type) { + if (asset.bundleBehavior != null) { actions.skipChildren(); return; } @@ -1245,13 +1092,7 @@ function createIdealGraph( getBundleFromBundleRoot(a).bundleBehavior === 'isolated')) ) { // Add asset to non-splittable bundles. - let entryBundleId = nullthrows(bundleRoots.get(a))[0]; - let entryBundle = nullthrows(bundleGraph.getNode(entryBundleId)); - invariant(entryBundle !== 'root'); - entryBundle.assets.add(asset); - entryBundle.size += asset.stats.size; - - assignInlineConstants(asset, entryBundle); + addAssetToBundleRoot(asset, a); } else if (!ancestorAssets[nodeId]?.has(i)) { // Filter out bundles from this asset's reachable array if // bundle does not contain the asset in its ancestry @@ -1278,9 +1119,9 @@ function createIdealGraph( invariant(firstSourceBundle !== 'root'); bundle = createBundle({ - uniqueKey: manualSharedObject.name + firstSourceBundle.type, + uniqueKey: manualSharedBundleKey, target: firstSourceBundle.target, - type: firstSourceBundle.type, + type: asset.type, env: firstSourceBundle.env, manualSharedBundle: manualSharedObject?.name, }); @@ -1388,7 +1229,7 @@ function createIdealGraph( let sourceBundles = reachableArray.map( a => nullthrows(bundleRoots.get(a))[0], ); - let key = reachableArray.map(a => a.id).join(','); + let key = reachableArray.map(a => a.id).join(',') + '.' + asset.type; let bundleId = bundles.get(key); let bundle; if (bundleId == null) { @@ -1398,7 +1239,7 @@ function createIdealGraph( invariant(firstSourceBundle !== 'root'); bundle = createBundle({ target: firstSourceBundle.target, - type: firstSourceBundle.type, + type: asset.type, env: firstSourceBundle.env, }); bundle.sourceBundles = new Set(sourceBundles); @@ -1444,14 +1285,7 @@ function createIdealGraph( reachableArray.length <= config.minBundles ) { for (let root of reachableArray) { - let bundle = nullthrows( - bundleGraph.getNode(nullthrows(bundleRoots.get(root))[0]), - ); - invariant(bundle !== 'root'); - bundle.assets.add(asset); - bundle.size += asset.stats.size; - - assignInlineConstants(asset, bundle); + addAssetToBundleRoot(asset, root); } } } @@ -1459,7 +1293,7 @@ function createIdealGraph( let manualSharedBundleIds = new Set([...manualSharedMap.values()]); // Step split manual shared bundles for those that have the "split" property set let remainderMap = new DefaultMap(() => []); - for (let [manualName, id] of manualSharedMap) { + for (let id of manualSharedMap.values()) { let manualBundle = bundleGraph.getNode(id); invariant(manualBundle !== 'root' && manualBundle != null); @@ -1610,8 +1444,10 @@ function createIdealGraph( modifiedSourceBundles.add(sourceBundle); bundleToRemove.sourceBundles.delete(sourceBundleId); for (let asset of bundleToRemove.assets) { - sourceBundle.assets.add(asset); - sourceBundle.size += asset.stats.size; + addAssetToBundleRoot( + asset, + nullthrows(sourceBundle.mainEntryAsset), + ); } //This case is specific to reused bundles, which can have shared bundles attached to it for (let childId of bundleGraph.getNodeIdsConnectedFrom( @@ -1669,24 +1505,6 @@ function createIdealGraph( ); } } - function detachFromBundleGroup(groupId: NodeId, bundleId: NodeId) { - // This removes a particular bundle from the specfied bundleGroup - if (bundleGraph.hasEdge(groupId, bundleId)) { - bundleGraph.removeEdge(groupId, bundleId); - } else { - let referencingBundleId; - bundleGraph.traverse(nodeId => { - if (bundleGraph.hasEdge(nodeId, bundleId)) { - referencingBundleId = nodeId; - } - }, groupId); - invariant( - referencingBundleId != null, - 'Expected to remove bundle from group but could not find it...', - ); - bundleGraph.removeEdge(referencingBundleId, bundleId); - } - } function deleteBundle(bundleRoot: BundleRoot) { bundleGraph.removeNode(nullthrows(bundles.get(bundleRoot.id))); bundleRoots.delete(bundleRoot); @@ -1696,19 +1514,6 @@ function createIdealGraph( bundleRootGraph.removeNode(bundleRootId); } } - function getBundleGroupsForBundle(nodeId: NodeId) { - let bundleGroupBundleIds = new Set(); - bundleGraph.traverseAncestors(nodeId, ancestorId => { - if ( - bundleGraph - .getNodeIdsConnectedTo(ancestorId) //if node is root, then dont add, otherwise do add. - .includes(bundleGraph.rootNodeId) - ) { - bundleGroupBundleIds.add(ancestorId); - } - }); - return bundleGroupBundleIds; - } function getBundlesForBundleGroup(bundleGroupId) { let bundlesInABundleGroup = []; bundleGraph.traverse(nodeId => { @@ -1717,56 +1522,6 @@ function createIdealGraph( return bundlesInABundleGroup; } - function mergeBundle(mainNodeId: NodeId, otherNodeId: NodeId) { - //merges assets of "otherRoot" into "mainBundleRoot" - let a = nullthrows(bundleGraph.getNode(mainNodeId)); - let b = nullthrows(bundleGraph.getNode(otherNodeId)); - invariant(a !== 'root' && b !== 'root'); - let bundleRootB = nullthrows(b.mainEntryAsset); - let mainBundleRoot = nullthrows(a.mainEntryAsset); - let bundleGroupOfMain = nullthrows(bundleRoots.get(mainBundleRoot))[1]; - // If our merging bundle is already a combination of bundles, all previous root assets must be updated as well - for (let movingAsset of b.assets) { - if (movingAsset === bundleRootB) continue; - if (bundleRoots.has(movingAsset)) { - bundleRoots.set(movingAsset, [mainNodeId, bundleGroupOfMain]); - bundles.set(movingAsset.id, mainNodeId); - } - replaceAssetReference(movingAsset, b, a); - } - - for (let asset of b.assets) { - a.assets.add(asset); - a.size += asset.stats.size; - } - for (let depId of dependencyBundleGraph.getNodeIdsConnectedTo( - dependencyBundleGraph.getNodeIdByContentKey(String(otherNodeId)), - ALL_EDGE_TYPES, - )) { - dependencyBundleGraph.replaceNodeIdsConnectedTo(depId, [ - dependencyBundleGraph.getNodeIdByContentKey(String(mainNodeId)), - ]); - } - - //clean up asset reference - for (let dependencyTuple of assetReference.get(bundleRootB)) { - dependencyTuple[1] = a; - } - //add in any lost edges, parent or child - for (let nodeId of bundleGraph.getNodeIdsConnectedTo(otherNodeId)) { - bundleGraph.addEdge(nodeId, mainNodeId); - } - for (let nodeId of bundleGraph.getNodeIdsConnectedFrom(otherNodeId)) { - bundleGraph.addEdge(mainNodeId, nodeId); - } - replaceAssetReference(bundleRootB, b, a); - deleteBundle(bundleRootB); - // We still need to key this bundle via each bundleRoot - bundleRoots.set(bundleRootB, [mainNodeId, bundleGroupOfMain]); - bundles.set(bundleRootB.id, mainNodeId); - - bundles.delete(bundleRootB.id); - } function getBundleFromBundleRoot(bundleRoot: BundleRoot): Bundle { let bundle = bundleGraph.getNode( nullthrows(bundleRoots.get(bundleRoot))[0], @@ -1774,25 +1529,61 @@ function createIdealGraph( invariant(bundle !== 'root' && bundle != null); return bundle; } - function replaceAssetReference( - bundleRoot: BundleRoot, - toReplace: Bundle, - replaceWith: Bundle, - dependency?: Dependency, - ): void { - let replaceAssetReference = assetReference.get(bundleRoot).map(entry => { - let bundle = entry[1]; - let dep = entry[0]; - if (bundle == toReplace) { - if (dependency && dependency == dep) { - return [entry[0], replaceWith]; - } else if (dependency == null) { - return [entry[0], replaceWith]; - } + + function addAssetToBundleRoot(asset: Asset, bundleRoot: Asset) { + let [bundleId, bundleGroupId] = nullthrows(bundleRoots.get(bundleRoot)); + let bundle = nullthrows(bundleGraph.getNode(bundleId)); + invariant(bundle !== 'root'); + + if (asset.type !== bundle.type) { + let bundleGroup = nullthrows(bundleGraph.getNode(bundleGroupId)); + invariant(bundleGroup !== 'root'); + let key = nullthrows(bundleGroup.mainEntryAsset).id + '.' + asset.type; + let typeChangeBundleId = bundles.get(key); + if (typeChangeBundleId == null) { + let typeChangeBundle = createBundle({ + uniqueKey: key, + needsStableName: bundle.needsStableName, + bundleBehavior: bundle.bundleBehavior, + type: asset.type, + target: bundle.target, + env: bundle.env, + }); + typeChangeBundleId = bundleGraph.addNode(typeChangeBundle); + bundleGraph.addEdge(bundleId, typeChangeBundleId); + bundles.set(key, typeChangeBundleId); + bundle = typeChangeBundle; + } else { + bundle = nullthrows(bundleGraph.getNode(typeChangeBundleId)); + invariant(bundle !== 'root'); } - return entry; - }); - assetReference.set(bundleRoot, replaceAssetReference); + } + + bundle.assets.add(asset); + bundle.size += asset.stats.size; + assignInlineConstants(asset, bundle); + } + + function removeBundle( + bundleGraph: Graph, + bundleId: NodeId, + assetReference: DefaultMap>, + ) { + let bundle = nullthrows(bundleGraph.getNode(bundleId)); + invariant(bundle !== 'root'); + for (let asset of bundle.assets) { + assetReference.set( + asset, + assetReference.get(asset).filter(t => !t.includes(bundle)), + ); + for (let sourceBundleId of bundle.sourceBundles) { + let sourceBundle = nullthrows(bundleGraph.getNode(sourceBundleId)); + invariant(sourceBundle !== 'root'); + addAssetToBundleRoot(asset, nullthrows(sourceBundle.mainEntryAsset)); + } + } + + bundleGraph.removeNode(bundleId); } return { @@ -1901,29 +1692,6 @@ function createBundle(opts: {| }; } -function removeBundle( - bundleGraph: Graph, - bundleId: NodeId, - assetReference: DefaultMap>, -) { - let bundle = nullthrows(bundleGraph.getNode(bundleId)); - invariant(bundle !== 'root'); - for (let asset of bundle.assets) { - assetReference.set( - asset, - assetReference.get(asset).filter(t => !t.includes(bundle)), - ); - for (let sourceBundleId of bundle.sourceBundles) { - let sourceBundle = nullthrows(bundleGraph.getNode(sourceBundleId)); - invariant(sourceBundle !== 'root'); - sourceBundle.assets.add(asset); - sourceBundle.size += asset.stats.size; - } - } - - bundleGraph.removeNode(bundleId); -} - function resolveModeConfig( config: BundlerConfig, mode: BuildMode, @@ -1951,6 +1719,7 @@ function resolveModeConfig( async function loadBundlerConfig( config: Config, options: PluginOptions, + logger: PluginLogger, ): Promise { let conf = await config.getConfig([], { packageKey: '@parcel/bundler-default', @@ -2063,14 +1832,3 @@ function getEntryByTarget( }); return targets; } - -function canMerge(a, b) { - // Bundles can be merged if they have the same type and environment, - // unless they are explicitly marked as isolated or inline. - return ( - a.type === b.type && - a.env.context === b.env.context && - a.bundleBehavior == null && - b.bundleBehavior == null - ); -} diff --git a/packages/core/core/src/BundleGraph.js b/packages/core/core/src/BundleGraph.js index 0b92930347c..4632586cd42 100644 --- a/packages/core/core/src/BundleGraph.js +++ b/packages/core/core/src/BundleGraph.js @@ -1137,6 +1137,7 @@ export default class BundleGraph { ? firstAsset : // Otherwise, find the first asset that belongs to this bundle. assets.find(asset => this.bundleHasAsset(bundle, asset)) || + assets.find(a => a.type === bundle.type) || firstAsset; // If a resolution still hasn't been found, return the first referenced asset. @@ -1735,7 +1736,7 @@ export default class BundleGraph { ); let depSymbol = symbolLookup.get(identifier); if (depSymbol != null) { - let resolved = this.getResolvedAsset(dep); + let resolved = this.getResolvedAsset(dep, boundary); if (!resolved || resolved.id === asset.id) { // External module or self-reference return { @@ -1785,7 +1786,7 @@ export default class BundleGraph { depSymbols.get('*')?.local === '*' && symbol !== 'default' ) { - let resolved = this.getResolvedAsset(dep); + let resolved = this.getResolvedAsset(dep, boundary); if (!resolved) { continue; } @@ -1918,7 +1919,7 @@ export default class BundleGraph { if (!depSymbols) continue; if (depSymbols.get('*')?.local === '*') { - let resolved = this.getResolvedAsset(dep); + let resolved = this.getResolvedAsset(dep, boundary); if (!resolved) continue; let exported = this.getExportedSymbols(resolved, boundary) .filter(s => s.exportSymbol !== 'default') diff --git a/packages/core/integration-tests/test/bundler.js b/packages/core/integration-tests/test/bundler.js index 9252b00e181..f5fc298e15e 100644 --- a/packages/core/integration-tests/test/bundler.js +++ b/packages/core/integration-tests/test/bundler.js @@ -1524,7 +1524,7 @@ describe('bundler', function () { ]); let targetDistDir = normalizePath(path.join(__dirname, '../dist')); - let hashedIdWithMSB = hashString('bundle:' + 'vendorjs' + targetDistDir); + let hashedIdWithMSB = hashString('bundle:' + 'vendor,js' + targetDistDir); assert( b.getBundles().find(b => b.id == hashedIdWithMSB), 'MSB id does not match expected', @@ -1760,4 +1760,49 @@ describe('bundler', function () { ]); }); }); + + it('should reuse type change bundles from parent bundle groups', async function () { + await fsFixture(overlayFS, __dirname)` + reuse-type-change-bundles + index.html: + + + + style.css: + @import "common.css"; + body { color: red } + + common.css: + .common { color: green } + + index.js: + import('./async'); + + async.js: + import './common.css'; + `; + + let b = await bundle( + path.join(__dirname, 'reuse-type-change-bundles', 'index.html'), + { + mode: 'production', + inputFS: overlayFS, + }, + ); + + assertBundles(b, [ + { + assets: ['index.html'], + }, + { + assets: ['style.css', 'common.css'], + }, + { + assets: ['index.js', 'bundle-manifest.js', 'esm-js-loader.js'], + }, + { + assets: ['async.js'], + }, + ]); + }); }); diff --git a/packages/core/integration-tests/test/css-modules.js b/packages/core/integration-tests/test/css-modules.js index 703af828eca..049d783148f 100644 --- a/packages/core/integration-tests/test/css-modules.js +++ b/packages/core/integration-tests/test/css-modules.js @@ -619,29 +619,19 @@ describe('css modules', () => { }, { type: 'js', - assets: [ - 'page1.js', - 'index.module.css', - 'a.module.css', - 'b.module.css', - ], + assets: ['page1.js'], }, { type: 'js', - assets: [ - 'page2.js', - 'index.module.css', - 'a.module.css', - 'b.module.css', - ], + assets: ['page2.js'], }, { type: 'css', - assets: ['a.module.css', 'b.module.css'], + assets: ['a.module.css', 'b.module.css', 'index.module.css'], }, { - type: 'css', - assets: ['index.module.css'], + type: 'js', + assets: ['a.module.css', 'b.module.css', 'index.module.css'], }, ]); }); @@ -745,7 +735,7 @@ describe('css modules', () => { assert(res[0][1].includes('container') && res[0][1].includes('expand')); }); - it('should allow css modules to be shared between targets', async function () { + it('should duplicate css modules between targets', async function () { let b = await bundle([ path.join(__dirname, '/integration/css-module-self-references/a'), path.join(__dirname, '/integration/css-module-self-references/b'), @@ -760,6 +750,14 @@ describe('css modules', () => { name: 'main.css', assets: ['bar.module.css'], }, + { + name: 'module.css', + assets: ['bar.module.css'], + }, + { + name: 'module.css', + assets: ['bar.module.css'], + }, { name: 'main.js', assets: ['index.js', 'bar.module.css'], diff --git a/packages/core/integration-tests/test/css.js b/packages/core/integration-tests/test/css.js index 80e630aeea0..af691645025 100644 --- a/packages/core/integration-tests/test/css.js +++ b/packages/core/integration-tests/test/css.js @@ -77,19 +77,12 @@ describe('css', () => { { name: 'entry.js', type: 'js', - assets: [ - 'bundle-url.js', - 'cacheLoader.js', - 'css-loader.js', - 'entry.js', - 'js-loader.js', - ], + assets: ['bundle-url.js', 'cacheLoader.js', 'entry.js', 'js-loader.js'], }, { type: 'js', assets: ['esmodule-helpers.js', 'index.js'], }, - {name: 'Foo.css', type: 'css', assets: ['foo.css']}, {name: 'entry.css', type: 'css', assets: ['foo.css', 'main.css']}, ]); }); diff --git a/packages/core/integration-tests/test/html.js b/packages/core/integration-tests/test/html.js index 07011f0efc3..9121106a27a 100644 --- a/packages/core/integration-tests/test/html.js +++ b/packages/core/integration-tests/test/html.js @@ -2242,39 +2242,39 @@ describe('html', function () { assertBundles(b, [ { - name: 'a.html', - type: 'html', + type: 'js', assets: ['a.html'], }, { - name: 'b.html', - type: 'html', + type: 'js', assets: ['b.html'], }, { - name: 'c.html', - type: 'html', + type: 'js', assets: ['c.html'], }, { - type: 'js', - assets: ['a.html', 'shared.js'], + name: 'a.html', + type: 'html', + assets: ['a.html'], }, { - type: 'js', - assets: ['b.html', 'shared.js'], + name: 'b.html', + type: 'html', + assets: ['b.html'], }, { - type: 'js', - assets: ['c.html', 'shared.js'], + name: 'c.html', + type: 'html', + assets: ['c.html'], }, { type: 'css', - assets: ['other.css'], + assets: ['other.css', 'shared.css'], }, { - type: 'css', - assets: ['shared.css'], + type: 'js', + assets: ['shared.js'], }, ]); @@ -2493,6 +2493,14 @@ describe('html', function () { type: 'css', assets: ['a.module.css'], }, + { + type: 'css', + assets: ['a.module.css'], + }, + { + type: 'css', + assets: ['a.module.css'], + }, { type: 'html', assets: ['searchfield.html'], diff --git a/packages/core/integration-tests/test/integration/css-modules-import/package.json b/packages/core/integration-tests/test/integration/css-modules-import/package.json new file mode 100644 index 00000000000..27fa31fc878 --- /dev/null +++ b/packages/core/integration-tests/test/integration/css-modules-import/package.json @@ -0,0 +1,5 @@ +{ + "@parcel/bundler-default": { + "minBundleSize": 0 + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/shared-sibling-entries-multiple/package.json b/packages/core/integration-tests/test/integration/shared-sibling-entries-multiple/package.json new file mode 100644 index 00000000000..27fa31fc878 --- /dev/null +++ b/packages/core/integration-tests/test/integration/shared-sibling-entries-multiple/package.json @@ -0,0 +1,5 @@ +{ + "@parcel/bundler-default": { + "minBundleSize": 0 + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/shared-sibling-entries-multiple/yarn.lock b/packages/core/integration-tests/test/integration/shared-sibling-entries-multiple/yarn.lock new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/integration/shared-sibling-entries/package.json b/packages/core/integration-tests/test/integration/shared-sibling-entries/package.json new file mode 100644 index 00000000000..27fa31fc878 --- /dev/null +++ b/packages/core/integration-tests/test/integration/shared-sibling-entries/package.json @@ -0,0 +1,5 @@ +{ + "@parcel/bundler-default": { + "minBundleSize": 0 + } +} \ No newline at end of file diff --git a/packages/core/integration-tests/test/integration/shared-sibling-entries/yarn.lock b/packages/core/integration-tests/test/integration/shared-sibling-entries/yarn.lock new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/core/integration-tests/test/monorepos.js b/packages/core/integration-tests/test/monorepos.js index 478c835572b..3335cc015f8 100644 --- a/packages/core/integration-tests/test/monorepos.js +++ b/packages/core/integration-tests/test/monorepos.js @@ -255,6 +255,10 @@ describe('monorepos', function () { name: 'pkg-b.cjs.css', assets: ['index.module.css'], }, + { + name: 'pkg-b.module.css', + assets: ['index.module.css'], + }, ]); let contents = await outputFS.readFile( @@ -300,7 +304,7 @@ describe('monorepos', function () { ), 'utf8', ); - assert(contents.includes('import "./pkg-b.cjs.css"')); + assert(contents.includes('import "./pkg-b.module.css"')); }); it('should build using root targets with a glob pointing at files inside packages and cwd at project root', async function () { @@ -531,6 +535,10 @@ describe('monorepos', function () { name: 'pkg-b.cjs.css', assets: ['index.module.css'], }, + { + name: 'pkg-b.module.css', + assets: ['index.module.css'], + }, ]); let contents = await outputFS.readFile( @@ -576,7 +584,7 @@ describe('monorepos', function () { ), 'utf8', ); - assert(contents.includes('import "./pkg-b.cjs.css"')); + assert(contents.includes('import "./pkg-b.module.css"')); }); it('should watch glob entries and build new packages that are added', async function () { @@ -634,6 +642,10 @@ describe('monorepos', function () { name: 'pkg-b.cjs.css', assets: ['index.module.css'], }, + { + name: 'pkg-b.module.css', + assets: ['index.module.css'], + }, ]); }); @@ -788,6 +800,10 @@ describe('monorepos', function () { name: 'pkg-a.cjs.css', assets: ['index.module.css'], }, + { + name: 'pkg-a.module.css', + assets: ['index.module.css'], + }, { name: 'pkg-b.cjs.js', assets: ['index.js', 'index.module.css'], @@ -800,6 +816,10 @@ describe('monorepos', function () { name: 'pkg-b.cjs.css', assets: ['index.module.css'], }, + { + name: 'pkg-b.module.css', + assets: ['index.module.css'], + }, ]); let contents = await outputFS.readFile( @@ -820,7 +840,7 @@ describe('monorepos', function () { 'utf8', ); assert(contents.includes('export {')); - assert(contents.includes('import "./pkg-a.cjs.css"')); + assert(contents.includes('import "./pkg-a.module.css"')); contents = await outputFS.readFile( path.join( @@ -856,7 +876,7 @@ describe('monorepos', function () { ), 'utf8', ); - assert(contents.includes('import "./pkg-b.cjs.css"')); + assert(contents.includes('import "./pkg-b.module.css"')); }); it('should search for .parcelrc at cwd in monorepos', async () => { diff --git a/packages/core/test-utils/src/utils.js b/packages/core/test-utils/src/utils.js index 11a697184bd..00acbee0bb4 100644 --- a/packages/core/test-utils/src/utils.js +++ b/packages/core/test-utils/src/utils.js @@ -538,53 +538,48 @@ export function assertBundles( bundle.assets.sort(byAlphabet); } - const byName = (a, b) => { - if (typeof a.name === 'string' && typeof b.name === 'string') { - return a.name.localeCompare(b.name); - } - - return 0; - }; - - const byAssets = (a, b) => - a.assets.join(',').localeCompare(b.assets.join(',')); - expectedBundles.sort(byName).sort(byAssets); - actualBundles.sort(byName).sort(byAssets); assert.equal( actualBundles.length, expectedBundles.length, 'expected number of bundles mismatched', ); - let i = 0; for (let bundle of expectedBundles) { - let actualBundle = actualBundles[i++]; let name = bundle.name; - let actualName = actualBundle.name; - if (name != null && actualName != null) { - if (typeof name === 'string') { - assert.equal( - actualName, - name, - `Bundle name "${actualName}", does not match expected name "${name}"`, - ); - } else if (name instanceof RegExp) { - assert( - actualName.match(name), - `${actualName} does not match regexp ${name.toString()}`, - ); - } else { - // $FlowFixMe[incompatible-call] - assert.fail('Expected bundle name has invalid type'); + let found = actualBundles.some(b => { + if (name != null && b.name != null) { + if (typeof name === 'string') { + if (name !== b.name) { + return false; + } + } else if (name instanceof RegExp) { + if (!name.test(b.name)) { + return false; + } + } else { + // $FlowFixMe[incompatible-call] + assert.fail('Expected bundle name has invalid type'); + } } - } - if (bundle.type != null) { - assert.equal(actualBundle.type, bundle.type); - } + if (bundle.type != null && bundle.type !== b.type) { + return false; + } - if (bundle.assets) { - assert.deepEqual(actualBundle.assets, bundle.assets); + return ( + bundle.assets && + bundle.assets.length === b.assets.length && + bundle.assets.every((a, i) => a === b.assets[i]) + ); + }); + + if (!found) { + // $FlowFixMe[incompatible-call] + assert.fail( + `Could not find expected bundle: \n\n${util.inspect( + bundle, + )} \n\nActual bundles: \n\n${util.inspect(actualBundles)}`, + ); } } }