@atlaspack/bundler-default 3.1.3-typescript-bc4459c37.0 → 3.2.1-typescript-5ad950d33.0

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 CHANGED
@@ -1,5 +1,30 @@
1
1
  # @atlaspack/bundler-default
2
2
 
3
+ ## 3.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#721](https://github.com/atlassian-labs/atlaspack/pull/721) [`069de47`](https://github.com/atlassian-labs/atlaspack/commit/069de478e64fb5889f6f2ce023eb510782767fbd) Thanks [@benjervis](https://github.com/benjervis)! - Add support for bundle merging based on `webpackChunkName` comments.
8
+
9
+ Adding a `webpackChunkName` comment to an import will allow the bundler to merge multiple imports into a single bundle.
10
+
11
+ e.g.:
12
+
13
+ ```ts
14
+ import(/* webpackChunkName: "my-chunk" */ './my-module');
15
+ import(/* webpackChunkName: "my-chunk" */ './another-module');
16
+ ```
17
+
18
+ This can be enabled with the feature flag `supportWebpackChunkName`.
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [[`069de47`](https://github.com/atlassian-labs/atlaspack/commit/069de478e64fb5889f6f2ce023eb510782767fbd)]:
23
+ - @atlaspack/feature-flags@2.20.0
24
+ - @atlaspack/graph@3.5.10
25
+ - @atlaspack/utils@2.17.3
26
+ - @atlaspack/plugin@2.14.21
27
+
3
28
  ## 3.1.2
4
29
 
5
30
  ### Patch Changes
@@ -42,11 +42,13 @@ function decorateLegacyGraph(idealGraph, bundleGraph) {
42
42
  bundleGroupBundleIds,
43
43
  manualAssetToBundle
44
44
  } = idealGraph;
45
+ // This line can be deleted once supportWebpackChunkName feature flag is removed.
45
46
  let entryBundleToBundleGroup = new Map();
46
47
  // Step Create Bundles: Create bundle groups, bundles, and shared bundles and add assets to them
47
48
  for (let [bundleNodeId, idealBundle] of idealBundleGraph.nodes.entries()) {
48
49
  if (!idealBundle || idealBundle === 'root') continue;
49
50
  let entryAsset = idealBundle.mainEntryAsset;
51
+ // This line can be deleted once supportWebpackChunkName feature flag is removed.
50
52
  let bundleGroup;
51
53
  let bundle;
52
54
  if (bundleGroupBundleIds.has(bundleNodeId)) {
@@ -57,19 +59,33 @@ function decorateLegacyGraph(idealGraph, bundleGraph) {
57
59
  return dependency.value;
58
60
  });
59
61
  (0, _assert().default)(entryAsset != null, 'Processing a bundleGroup with no entry asset');
62
+ let bundleGroups = new Map();
60
63
  for (let dependency of dependencies) {
61
64
  bundleGroup = bundleGraph.createBundleGroup(dependency, idealBundle.target);
65
+ bundleGroups.set(bundleGroup.entryAssetId, bundleGroup);
66
+ }
67
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
68
+ (0, _assert().default)(bundleGroups.size > 0, 'No bundle groups created');
69
+ } else {
70
+ (0, _assert().default)(bundleGroup);
71
+ entryBundleToBundleGroup.set(bundleNodeId, bundleGroup);
62
72
  }
63
- (0, _assert().default)(bundleGroup);
64
- entryBundleToBundleGroup.set(bundleNodeId, bundleGroup);
65
73
  bundle = (0, _nullthrows().default)(bundleGraph.createBundle({
66
74
  entryAsset: (0, _nullthrows().default)(entryAsset),
75
+ bundleRoots: Array.from(idealBundle.bundleRoots),
67
76
  needsStableName: idealBundle.needsStableName,
68
77
  bundleBehavior: idealBundle.bundleBehavior,
69
78
  target: idealBundle.target,
70
79
  manualSharedBundle: idealBundle.manualSharedBundle
71
80
  }));
72
- bundleGraph.addBundleToBundleGroup(bundle, bundleGroup);
81
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
82
+ for (let bundleGroup of bundleGroups.values()) {
83
+ bundleGraph.addBundleToBundleGroup(bundle, bundleGroup);
84
+ }
85
+ } else {
86
+ (0, _assert().default)(bundleGroup);
87
+ bundleGraph.addBundleToBundleGroup(bundle, bundleGroup);
88
+ }
73
89
  } else if (idealBundle.sourceBundles.size > 0 && !idealBundle.mainEntryAsset) {
74
90
  let uniqueKey = idealBundle.uniqueKey != null ? idealBundle.uniqueKey : [...idealBundle.assets].map(asset => asset.id).join(',');
75
91
  bundle = (0, _nullthrows().default)(bundleGraph.createBundle({
@@ -95,6 +111,7 @@ function decorateLegacyGraph(idealGraph, bundleGraph) {
95
111
  (0, _assert().default)(entryAsset != null);
96
112
  bundle = (0, _nullthrows().default)(bundleGraph.createBundle({
97
113
  entryAsset,
114
+ bundleRoots: Array.from(idealBundle.bundleRoots),
98
115
  needsStableName: idealBundle.needsStableName,
99
116
  bundleBehavior: idealBundle.bundleBehavior,
100
117
  target: idealBundle.target,
@@ -9,6 +9,7 @@ export type Bundle = {
9
9
  bundleBehavior?: BundleBehavior | null | undefined;
10
10
  needsStableName: boolean;
11
11
  mainEntryAsset: Asset | null | undefined;
12
+ bundleRoots: Set<Asset>;
12
13
  size: number;
13
14
  sourceBundles: Set<NodeId>;
14
15
  target: Target;
package/lib/idealGraph.js CHANGED
@@ -66,6 +66,11 @@ const idealBundleGraphEdges = exports.idealBundleGraphEdges = Object.freeze({
66
66
  // which mutates the assetGraph into the bundleGraph we would
67
67
  // expect from default bundler
68
68
 
69
+ function isNonRootBundle(bundle, message) {
70
+ let existingBundle = (0, _nullthrows().default)(bundle, message);
71
+ (0, _assert().default)(existingBundle !== 'root', "Bundle cannot be 'root'");
72
+ return existingBundle;
73
+ }
69
74
  function createIdealGraph(assetGraph, config, entries, logger) {
70
75
  var _config$sharedBundleM;
71
76
  // Asset to the bundle and group it's an entry of
@@ -810,6 +815,8 @@ function createIdealGraph(assetGraph, config, entries, logger) {
810
815
  }
811
816
  }
812
817
  let manualSharedBundleIds = new Set([...manualSharedMap.values()]);
818
+ let modifiedSourceBundles = new Set();
819
+
813
820
  // Step split manual shared bundles for those that have the "split" property set
814
821
  let remainderMap = new (_utils().DefaultMap)(() => []);
815
822
  for (let id of manualSharedMap.values()) {
@@ -881,6 +888,57 @@ function createIdealGraph(assetGraph, config, entries, logger) {
881
888
  }
882
889
  }
883
890
  }
891
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
892
+ // Merge webpack chunk name bundles
893
+ let chunkNameBundles = new (_utils().DefaultMap)(() => new Set());
894
+ for (let [nodeId, node] of dependencyBundleGraph.nodes.entries()) {
895
+ var _bundleNode$value$mai;
896
+ // meta.chunkName is set by the Rust transformer, so we just need to find
897
+ // bundles that have a chunkName set.
898
+ if (!node || node.type !== 'dependency' || node.value.meta.chunkName == null) {
899
+ continue;
900
+ }
901
+ let connectedBundles = dependencyBundleGraph.getNodeIdsConnectedFrom(nodeId, dependencyPriorityEdges[node.value.priority]);
902
+ if (connectedBundles.length === 0) {
903
+ continue;
904
+ }
905
+ (0, _assert().default)(connectedBundles.length === 1, 'Expected webpackChunkName dependency to be connected to no more than one bundle');
906
+ let bundleId = connectedBundles[0];
907
+ let bundleNode = dependencyBundleGraph.getNode(bundleId);
908
+ (0, _assert().default)(bundleNode != null && bundleNode.type === 'bundle');
909
+
910
+ // If a bundle does not have a main entry asset, it's somehow just a
911
+ // shared bundle, and will be merged/deleted by other means.
912
+ if (bundleNode.value.mainEntryAsset == null) {
913
+ continue;
914
+ }
915
+ let bundleNodeId = null;
916
+ let mainEntryAssetId = (_bundleNode$value$mai = bundleNode.value.mainEntryAsset) === null || _bundleNode$value$mai === void 0 ? void 0 : _bundleNode$value$mai.id;
917
+ if (mainEntryAssetId != null) {
918
+ bundleNodeId = bundles.get(mainEntryAssetId);
919
+ }
920
+ if (bundleNodeId == null) {
921
+ continue;
922
+ }
923
+ chunkNameBundles.get(node.value.meta.chunkName)
924
+ // DependencyBundleGraph uses content keys as node ids, so we can use that
925
+ // to get the bundle id.
926
+ .add(bundleNodeId);
927
+ }
928
+ for (let [chunkName, bundleIds] of chunkNameBundles.entries()) {
929
+ // The `[request]` placeholder is not yet supported
930
+ if (bundleIds.size <= 1 || typeof chunkName === 'string' && chunkName.includes('[request]')) {
931
+ continue; // Nothing to merge
932
+ }
933
+
934
+ // Merge all bundles with the same chunk name into the first one.
935
+ let [firstBundleId, ...rest] = Array.from(bundleIds);
936
+ for (let bundleId of rest) {
937
+ // @ts-expect-error TS2345
938
+ mergeBundles(firstBundleId, bundleId);
939
+ }
940
+ }
941
+ }
884
942
 
885
943
  // Step merge shared bundles that meet the overlap threshold
886
944
  // This step is skipped by default as the threshold defaults to 1
@@ -899,7 +957,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
899
957
  }
900
958
 
901
959
  // Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
902
- let modifiedSourceBundles = new Set();
960
+
903
961
  if (config.disableSharedBundles === false) {
904
962
  for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
905
963
  // Find shared bundles in this bundle group.
@@ -987,10 +1045,10 @@ function createIdealGraph(assetGraph, config, entries, logger) {
987
1045
  }
988
1046
  }
989
1047
  }
990
- function mergeBundles(bundleGraph, bundleToKeepId, bundleToRemoveId, assetReference) {
991
- let bundleToKeep = (0, _nullthrows().default)(bundleGraph.getNode(bundleToKeepId));
992
- let bundleToRemove = (0, _nullthrows().default)(bundleGraph.getNode(bundleToRemoveId));
993
- (0, _assert().default)(bundleToKeep !== 'root' && bundleToRemove !== 'root');
1048
+ function mergeBundles(bundleToKeepId, bundleToRemoveId) {
1049
+ let bundleToKeep = isNonRootBundle(bundleGraph.getNode(bundleToKeepId), `Bundle ${bundleToKeepId} not found`);
1050
+ let bundleToRemove = isNonRootBundle(bundleGraph.getNode(bundleToRemoveId), `Bundle ${bundleToRemoveId} not found`);
1051
+ modifiedSourceBundles.add(bundleToKeep);
994
1052
  for (let asset of bundleToRemove.assets) {
995
1053
  bundleToKeep.assets.add(asset);
996
1054
  bundleToKeep.size += asset.stats.size;
@@ -1001,8 +1059,20 @@ function createIdealGraph(assetGraph, config, entries, logger) {
1001
1059
  }
1002
1060
 
1003
1061
  // Merge any internalized assets
1004
- (0, _assert().default)(bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets, 'All shared bundles should have internalized assets');
1005
- bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
1062
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
1063
+ if (bundleToKeep.internalizedAssets != null) {
1064
+ if (bundleToRemove.internalizedAssets != null) {
1065
+ bundleToKeep.internalizedAssets.intersect(bundleToRemove.internalizedAssets);
1066
+ } else {
1067
+ bundleToKeep.internalizedAssets.clear();
1068
+ }
1069
+ }
1070
+ } else {
1071
+ (0, _assert().default)(bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets, 'All shared bundles should have internalized assets');
1072
+ bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
1073
+ }
1074
+
1075
+ // Merge and clean up source bundles
1006
1076
  for (let sourceBundleId of bundleToRemove.sourceBundles) {
1007
1077
  if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
1008
1078
  continue;
@@ -1010,6 +1080,66 @@ function createIdealGraph(assetGraph, config, entries, logger) {
1010
1080
  bundleToKeep.sourceBundles.add(sourceBundleId);
1011
1081
  bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
1012
1082
  }
1083
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
1084
+ bundleToKeep.sourceBundles.delete(bundleToRemoveId);
1085
+ for (let bundle of bundleGraph.getNodeIdsConnectedFrom(bundleToRemoveId)) {
1086
+ let bundleNode = (0, _nullthrows().default)(bundleGraph.getNode(bundle));
1087
+ if (bundleNode === 'root') {
1088
+ continue;
1089
+ }
1090
+
1091
+ // If the bundle is a source bundle, add it to the bundle to keep
1092
+ if (bundleNode.sourceBundles.has(bundleToRemoveId)) {
1093
+ bundleNode.sourceBundles.add(bundleToKeepId);
1094
+ bundleNode.sourceBundles.delete(bundleToRemoveId);
1095
+ bundleGraph.addEdge(bundleToKeepId, bundle);
1096
+ }
1097
+ }
1098
+
1099
+ // Merge bundle roots
1100
+ for (let bundleRoot of bundleToRemove.bundleRoots) {
1101
+ bundleToKeep.bundleRoots.add(bundleRoot);
1102
+ }
1103
+ if (bundleToRemove.mainEntryAsset != null) {
1104
+ (0, _assert().default)(bundleToKeep.mainEntryAsset != null);
1105
+
1106
+ // Merge the bundles in bundle group
1107
+ let bundlesInRemoveBundleGroup = getBundlesForBundleGroup(bundleToRemoveId);
1108
+ for (let bundleIdInGroup of bundlesInRemoveBundleGroup) {
1109
+ if (bundleIdInGroup === bundleToRemoveId) {
1110
+ continue;
1111
+ }
1112
+ bundleGraph.addEdge(bundleToKeepId, bundleIdInGroup);
1113
+ }
1114
+
1115
+ // Remove old bundle group
1116
+ bundleGroupBundleIds.delete(bundleToRemoveId);
1117
+
1118
+ // Clean up bundle roots
1119
+ let bundleRootToRemoveNodeId = (0, _nullthrows().default)(assetToBundleRootNodeId.get((0, _nullthrows().default)(bundleToRemove.mainEntryAsset)));
1120
+ let bundleRootToKeepNodeId = (0, _nullthrows().default)(assetToBundleRootNodeId.get((0, _nullthrows().default)(bundleToKeep.mainEntryAsset)));
1121
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedTo(bundleRootToRemoveNodeId)) {
1122
+ bundleRootGraph.addEdge(nodeId, bundleRootToKeepNodeId);
1123
+ bundleRootGraph.removeEdge(nodeId, bundleRootToRemoveNodeId);
1124
+ }
1125
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedFrom(bundleRootToRemoveNodeId)) {
1126
+ bundleRootGraph.addEdge(bundleRootToKeepNodeId, nodeId);
1127
+ bundleRootGraph.removeEdge(bundleRootToRemoveNodeId, nodeId);
1128
+ }
1129
+ bundleRoots.set((0, _nullthrows().default)(bundleToRemove.mainEntryAsset), [bundleToKeepId, bundleToKeepId]);
1130
+
1131
+ // Merge dependency bundle graph
1132
+ for (let dependencyNodeId of dependencyBundleGraph.getNodeIdsConnectedTo(dependencyBundleGraph.getNodeIdByContentKey(String(bundleToRemoveId)), _graph().ALL_EDGE_TYPES)) {
1133
+ let dependencyNode = (0, _nullthrows().default)(dependencyBundleGraph.getNode(dependencyNodeId));
1134
+ (0, _assert().default)(dependencyNode.type === 'dependency');
1135
+
1136
+ // Add dependency to the bundle to keep
1137
+ dependencyBundleGraph.addEdge(dependencyNodeId, dependencyBundleGraph.getNodeIdByContentKey(String(bundleToKeepId)), dependencyPriorityEdges[dependencyNode.value.priority]);
1138
+ // Remove dependency from the bundle to remove
1139
+ dependencyBundleGraph.removeEdge(dependencyNodeId, dependencyBundleGraph.getNodeIdByContentKey(String(bundleToRemoveId)), dependencyPriorityEdges[dependencyNode.value.priority]);
1140
+ }
1141
+ }
1142
+ }
1013
1143
  bundleGraph.removeNode(bundleToRemoveId);
1014
1144
  }
1015
1145
  function mergeOverlapBundles(mergeConfig) {
@@ -1044,11 +1174,16 @@ function createIdealGraph(assetGraph, config, entries, logger) {
1044
1174
  })
1045
1175
  };
1046
1176
  }));
1177
+ let mergedBundles = new Set();
1047
1178
  for (let cluster of clusters) {
1048
1179
  let [mergeTarget, ...rest] = cluster;
1049
1180
  for (let bundleIdToMerge of rest) {
1050
- mergeBundles(bundleGraph, mergeTarget, bundleIdToMerge, assetReference);
1181
+ mergeBundles(mergeTarget, bundleIdToMerge);
1051
1182
  }
1183
+ mergedBundles.add(mergeTarget);
1184
+ }
1185
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
1186
+ return mergedBundles;
1052
1187
  }
1053
1188
  }
1054
1189
  function getBigIntFromContentKey(contentKey) {
@@ -1060,10 +1195,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
1060
1195
  if (modifiedSourceBundles.size > 0) {
1061
1196
  let assetOrderMap = new Map(assets.map((a, index) => [a, index]));
1062
1197
  for (let bundle of modifiedSourceBundles) {
1063
- // @ts-expect-error TS18046
1064
- bundle.assets = new Set(
1065
- // @ts-expect-error TS18046
1066
- [...bundle.assets].sort((a, b) => {
1198
+ bundle.assets = new Set([...bundle.assets].sort((a, b) => {
1067
1199
  let aIndex = (0, _nullthrows().default)(assetOrderMap.get(a));
1068
1200
  let bIndex = (0, _nullthrows().default)(assetOrderMap.get(b));
1069
1201
  return aIndex - bIndex;
@@ -1151,6 +1283,7 @@ function createBundle(opts) {
1151
1283
  bundleBehavior: opts.bundleBehavior,
1152
1284
  env: (0, _nullthrows().default)(opts.env),
1153
1285
  mainEntryAsset: null,
1286
+ bundleRoots: new Set(),
1154
1287
  manualSharedBundle: opts.manualSharedBundle,
1155
1288
  needsStableName: Boolean(opts.needsStableName),
1156
1289
  size: 0,
@@ -1166,6 +1299,7 @@ function createBundle(opts) {
1166
1299
  bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior,
1167
1300
  env: opts.env ?? asset.env,
1168
1301
  mainEntryAsset: asset,
1302
+ bundleRoots: new Set([asset]),
1169
1303
  manualSharedBundle: opts.manualSharedBundle,
1170
1304
  needsStableName: Boolean(opts.needsStableName),
1171
1305
  size: asset.stats.size,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaspack/bundler-default",
3
- "version": "3.1.3-typescript-bc4459c37.0",
3
+ "version": "3.2.1-typescript-5ad950d33.0",
4
4
  "license": "(MIT OR Apache-2.0)",
5
5
  "type": "commonjs",
6
6
  "publishConfig": {
@@ -17,17 +17,17 @@
17
17
  "node": ">= 16.0.0"
18
18
  },
19
19
  "dependencies": {
20
- "@atlaspack/diagnostic": "2.14.2-typescript-bc4459c37.0",
21
- "@atlaspack/feature-flags": "2.19.3-typescript-bc4459c37.0",
22
- "@atlaspack/graph": "3.5.10-typescript-bc4459c37.0",
23
- "@atlaspack/plugin": "2.14.21-typescript-bc4459c37.0",
24
- "@atlaspack/rust": "3.4.2-typescript-bc4459c37.0",
25
- "@atlaspack/utils": "2.17.3-typescript-bc4459c37.0",
20
+ "@atlaspack/diagnostic": "2.14.2-typescript-5ad950d33.0",
21
+ "@atlaspack/feature-flags": "2.20.1-typescript-5ad950d33.0",
22
+ "@atlaspack/graph": "3.5.11-typescript-5ad950d33.0",
23
+ "@atlaspack/plugin": "2.14.22-typescript-5ad950d33.0",
24
+ "@atlaspack/rust": "3.4.2-typescript-5ad950d33.0",
25
+ "@atlaspack/utils": "2.17.4-typescript-5ad950d33.0",
26
26
  "many-keys-map": "^1.0.3",
27
27
  "nullthrows": "^1.1.1"
28
28
  },
29
29
  "scripts": {
30
30
  "check-ts": "tsc --emitDeclarationOnly --rootDir src"
31
31
  },
32
- "gitHead": "bc4459c37a38ef1f74772126637e1d8841d1fcb0"
32
+ "gitHead": "5ad950d33a5f2255ebeb10c04a2e84c346e2de85"
33
33
  }
@@ -23,11 +23,13 @@ export function decorateLegacyGraph(
23
23
  bundleGroupBundleIds,
24
24
  manualAssetToBundle,
25
25
  } = idealGraph;
26
+ // This line can be deleted once supportWebpackChunkName feature flag is removed.
26
27
  let entryBundleToBundleGroup: Map<NodeId, BundleGroup> = new Map();
27
28
  // Step Create Bundles: Create bundle groups, bundles, and shared bundles and add assets to them
28
29
  for (let [bundleNodeId, idealBundle] of idealBundleGraph.nodes.entries()) {
29
30
  if (!idealBundle || idealBundle === 'root') continue;
30
31
  let entryAsset = idealBundle.mainEntryAsset;
32
+ // This line can be deleted once supportWebpackChunkName feature flag is removed.
31
33
  let bundleGroup;
32
34
  let bundle;
33
35
 
@@ -50,18 +52,26 @@ export function decorateLegacyGraph(
50
52
  entryAsset != null,
51
53
  'Processing a bundleGroup with no entry asset',
52
54
  );
55
+
56
+ let bundleGroups = new Map();
53
57
  for (let dependency of dependencies) {
54
58
  bundleGroup = bundleGraph.createBundleGroup(
55
59
  dependency,
56
60
  idealBundle.target,
57
61
  );
62
+ bundleGroups.set(bundleGroup.entryAssetId, bundleGroup);
63
+ }
64
+ if (getFeatureFlag('supportWebpackChunkName')) {
65
+ invariant(bundleGroups.size > 0, 'No bundle groups created');
66
+ } else {
67
+ invariant(bundleGroup);
68
+ entryBundleToBundleGroup.set(bundleNodeId, bundleGroup);
58
69
  }
59
- invariant(bundleGroup);
60
- entryBundleToBundleGroup.set(bundleNodeId, bundleGroup);
61
70
 
62
71
  bundle = nullthrows(
63
72
  bundleGraph.createBundle({
64
73
  entryAsset: nullthrows(entryAsset),
74
+ bundleRoots: Array.from(idealBundle.bundleRoots),
65
75
  needsStableName: idealBundle.needsStableName,
66
76
  bundleBehavior: idealBundle.bundleBehavior,
67
77
  target: idealBundle.target,
@@ -69,7 +79,14 @@ export function decorateLegacyGraph(
69
79
  }),
70
80
  );
71
81
 
72
- bundleGraph.addBundleToBundleGroup(bundle, bundleGroup);
82
+ if (getFeatureFlag('supportWebpackChunkName')) {
83
+ for (let bundleGroup of bundleGroups.values()) {
84
+ bundleGraph.addBundleToBundleGroup(bundle, bundleGroup);
85
+ }
86
+ } else {
87
+ invariant(bundleGroup);
88
+ bundleGraph.addBundleToBundleGroup(bundle, bundleGroup);
89
+ }
73
90
  } else if (
74
91
  idealBundle.sourceBundles.size > 0 &&
75
92
  !idealBundle.mainEntryAsset
@@ -107,6 +124,7 @@ export function decorateLegacyGraph(
107
124
  bundle = nullthrows(
108
125
  bundleGraph.createBundle({
109
126
  entryAsset,
127
+ bundleRoots: Array.from(idealBundle.bundleRoots),
110
128
  needsStableName: idealBundle.needsStableName,
111
129
  bundleBehavior: idealBundle.bundleBehavior,
112
130
  target: idealBundle.target,
package/src/idealGraph.ts CHANGED
@@ -34,6 +34,7 @@ export type Bundle = {
34
34
  bundleBehavior?: BundleBehavior | null | undefined;
35
35
  needsStableName: boolean;
36
36
  mainEntryAsset: Asset | null | undefined;
37
+ bundleRoots: Set<Asset>;
37
38
  size: number;
38
39
  sourceBundles: Set<NodeId>;
39
40
  target: Target;
@@ -83,6 +84,16 @@ export type IdealGraph = {
83
84
  manualAssetToBundle: Map<Asset, NodeId>;
84
85
  };
85
86
 
87
+ function isNonRootBundle(
88
+ bundle?: Bundle | 'root' | null,
89
+ message?: string,
90
+ ): Bundle {
91
+ let existingBundle = nullthrows(bundle, message);
92
+ invariant(existingBundle !== 'root', "Bundle cannot be 'root'");
93
+
94
+ return existingBundle;
95
+ }
96
+
86
97
  export function createIdealGraph(
87
98
  assetGraph: MutableBundleGraph,
88
99
  config: ResolvedBundlerConfig,
@@ -1102,6 +1113,8 @@ export function createIdealGraph(
1102
1113
  }
1103
1114
 
1104
1115
  let manualSharedBundleIds = new Set([...manualSharedMap.values()]);
1116
+ let modifiedSourceBundles = new Set<Bundle>();
1117
+
1105
1118
  // Step split manual shared bundles for those that have the "split" property set
1106
1119
  let remainderMap = new DefaultMap(() => []);
1107
1120
  for (let id of manualSharedMap.values()) {
@@ -1180,6 +1193,80 @@ export function createIdealGraph(
1180
1193
  }
1181
1194
  }
1182
1195
 
1196
+ if (getFeatureFlag('supportWebpackChunkName')) {
1197
+ // Merge webpack chunk name bundles
1198
+ let chunkNameBundles = new DefaultMap(() => new Set());
1199
+ for (let [nodeId, node] of dependencyBundleGraph.nodes.entries()) {
1200
+ // meta.chunkName is set by the Rust transformer, so we just need to find
1201
+ // bundles that have a chunkName set.
1202
+ if (
1203
+ !node ||
1204
+ node.type !== 'dependency' ||
1205
+ node.value.meta.chunkName == null
1206
+ ) {
1207
+ continue;
1208
+ }
1209
+
1210
+ let connectedBundles = dependencyBundleGraph.getNodeIdsConnectedFrom(
1211
+ nodeId,
1212
+ dependencyPriorityEdges[node.value.priority],
1213
+ );
1214
+
1215
+ if (connectedBundles.length === 0) {
1216
+ continue;
1217
+ }
1218
+
1219
+ invariant(
1220
+ connectedBundles.length === 1,
1221
+ 'Expected webpackChunkName dependency to be connected to no more than one bundle',
1222
+ );
1223
+
1224
+ let bundleId = connectedBundles[0];
1225
+ let bundleNode = dependencyBundleGraph.getNode(bundleId);
1226
+ invariant(bundleNode != null && bundleNode.type === 'bundle');
1227
+
1228
+ // If a bundle does not have a main entry asset, it's somehow just a
1229
+ // shared bundle, and will be merged/deleted by other means.
1230
+ if (bundleNode.value.mainEntryAsset == null) {
1231
+ continue;
1232
+ }
1233
+
1234
+ let bundleNodeId = null;
1235
+ let mainEntryAssetId = bundleNode.value.mainEntryAsset?.id;
1236
+
1237
+ if (mainEntryAssetId != null) {
1238
+ bundleNodeId = bundles.get(mainEntryAssetId);
1239
+ }
1240
+
1241
+ if (bundleNodeId == null) {
1242
+ continue;
1243
+ }
1244
+
1245
+ chunkNameBundles
1246
+ .get(node.value.meta.chunkName)
1247
+ // DependencyBundleGraph uses content keys as node ids, so we can use that
1248
+ // to get the bundle id.
1249
+ .add(bundleNodeId);
1250
+ }
1251
+
1252
+ for (let [chunkName, bundleIds] of chunkNameBundles.entries()) {
1253
+ // The `[request]` placeholder is not yet supported
1254
+ if (
1255
+ bundleIds.size <= 1 ||
1256
+ (typeof chunkName === 'string' && chunkName.includes('[request]'))
1257
+ ) {
1258
+ continue; // Nothing to merge
1259
+ }
1260
+
1261
+ // Merge all bundles with the same chunk name into the first one.
1262
+ let [firstBundleId, ...rest] = Array.from(bundleIds);
1263
+ for (let bundleId of rest) {
1264
+ // @ts-expect-error TS2345
1265
+ mergeBundles(firstBundleId, bundleId);
1266
+ }
1267
+ }
1268
+ }
1269
+
1183
1270
  // Step merge shared bundles that meet the overlap threshold
1184
1271
  // This step is skipped by default as the threshold defaults to 1
1185
1272
  if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
@@ -1202,7 +1289,6 @@ export function createIdealGraph(
1202
1289
  }
1203
1290
 
1204
1291
  // Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
1205
- let modifiedSourceBundles = new Set();
1206
1292
 
1207
1293
  if (config.disableSharedBundles === false) {
1208
1294
  for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
@@ -1310,15 +1396,16 @@ export function createIdealGraph(
1310
1396
  }
1311
1397
  }
1312
1398
 
1313
- function mergeBundles(
1314
- bundleGraph: IdealBundleGraph,
1315
- bundleToKeepId: NodeId,
1316
- bundleToRemoveId: NodeId,
1317
- assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>,
1318
- ) {
1319
- let bundleToKeep = nullthrows(bundleGraph.getNode(bundleToKeepId));
1320
- let bundleToRemove = nullthrows(bundleGraph.getNode(bundleToRemoveId));
1321
- invariant(bundleToKeep !== 'root' && bundleToRemove !== 'root');
1399
+ function mergeBundles(bundleToKeepId: NodeId, bundleToRemoveId: NodeId) {
1400
+ let bundleToKeep = isNonRootBundle(
1401
+ bundleGraph.getNode(bundleToKeepId),
1402
+ `Bundle ${bundleToKeepId} not found`,
1403
+ );
1404
+ let bundleToRemove = isNonRootBundle(
1405
+ bundleGraph.getNode(bundleToRemoveId),
1406
+ `Bundle ${bundleToRemoveId} not found`,
1407
+ );
1408
+ modifiedSourceBundles.add(bundleToKeep);
1322
1409
  for (let asset of bundleToRemove.assets) {
1323
1410
  bundleToKeep.assets.add(asset);
1324
1411
  bundleToKeep.size += asset.stats.size;
@@ -1334,12 +1421,25 @@ export function createIdealGraph(
1334
1421
  }
1335
1422
 
1336
1423
  // Merge any internalized assets
1337
- invariant(
1338
- bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets,
1339
- 'All shared bundles should have internalized assets',
1340
- );
1341
- bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
1424
+ if (getFeatureFlag('supportWebpackChunkName')) {
1425
+ if (bundleToKeep.internalizedAssets != null) {
1426
+ if (bundleToRemove.internalizedAssets != null) {
1427
+ bundleToKeep.internalizedAssets.intersect(
1428
+ bundleToRemove.internalizedAssets,
1429
+ );
1430
+ } else {
1431
+ bundleToKeep.internalizedAssets.clear();
1432
+ }
1433
+ }
1434
+ } else {
1435
+ invariant(
1436
+ bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets,
1437
+ 'All shared bundles should have internalized assets',
1438
+ );
1439
+ bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
1440
+ }
1342
1441
 
1442
+ // Merge and clean up source bundles
1343
1443
  for (let sourceBundleId of bundleToRemove.sourceBundles) {
1344
1444
  if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
1345
1445
  continue;
@@ -1349,6 +1449,104 @@ export function createIdealGraph(
1349
1449
  bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
1350
1450
  }
1351
1451
 
1452
+ if (getFeatureFlag('supportWebpackChunkName')) {
1453
+ bundleToKeep.sourceBundles.delete(bundleToRemoveId);
1454
+
1455
+ for (let bundle of bundleGraph.getNodeIdsConnectedFrom(
1456
+ bundleToRemoveId,
1457
+ )) {
1458
+ let bundleNode = nullthrows(bundleGraph.getNode(bundle));
1459
+ if (bundleNode === 'root') {
1460
+ continue;
1461
+ }
1462
+
1463
+ // If the bundle is a source bundle, add it to the bundle to keep
1464
+ if (bundleNode.sourceBundles.has(bundleToRemoveId)) {
1465
+ bundleNode.sourceBundles.add(bundleToKeepId);
1466
+ bundleNode.sourceBundles.delete(bundleToRemoveId);
1467
+ bundleGraph.addEdge(bundleToKeepId, bundle);
1468
+ }
1469
+ }
1470
+
1471
+ // Merge bundle roots
1472
+ for (let bundleRoot of bundleToRemove.bundleRoots) {
1473
+ bundleToKeep.bundleRoots.add(bundleRoot);
1474
+ }
1475
+
1476
+ if (bundleToRemove.mainEntryAsset != null) {
1477
+ invariant(bundleToKeep.mainEntryAsset != null);
1478
+
1479
+ // Merge the bundles in bundle group
1480
+ let bundlesInRemoveBundleGroup =
1481
+ getBundlesForBundleGroup(bundleToRemoveId);
1482
+
1483
+ for (let bundleIdInGroup of bundlesInRemoveBundleGroup) {
1484
+ if (bundleIdInGroup === bundleToRemoveId) {
1485
+ continue;
1486
+ }
1487
+ bundleGraph.addEdge(bundleToKeepId, bundleIdInGroup);
1488
+ }
1489
+
1490
+ // Remove old bundle group
1491
+ bundleGroupBundleIds.delete(bundleToRemoveId);
1492
+
1493
+ // Clean up bundle roots
1494
+ let bundleRootToRemoveNodeId = nullthrows(
1495
+ assetToBundleRootNodeId.get(
1496
+ nullthrows(bundleToRemove.mainEntryAsset),
1497
+ ),
1498
+ );
1499
+ let bundleRootToKeepNodeId = nullthrows(
1500
+ assetToBundleRootNodeId.get(nullthrows(bundleToKeep.mainEntryAsset)),
1501
+ );
1502
+
1503
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedTo(
1504
+ bundleRootToRemoveNodeId,
1505
+ )) {
1506
+ bundleRootGraph.addEdge(nodeId, bundleRootToKeepNodeId);
1507
+ bundleRootGraph.removeEdge(nodeId, bundleRootToRemoveNodeId);
1508
+ }
1509
+
1510
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedFrom(
1511
+ bundleRootToRemoveNodeId,
1512
+ )) {
1513
+ bundleRootGraph.addEdge(bundleRootToKeepNodeId, nodeId);
1514
+ bundleRootGraph.removeEdge(bundleRootToRemoveNodeId, nodeId);
1515
+ }
1516
+
1517
+ bundleRoots.set(nullthrows(bundleToRemove.mainEntryAsset), [
1518
+ bundleToKeepId,
1519
+ bundleToKeepId,
1520
+ ]);
1521
+
1522
+ // Merge dependency bundle graph
1523
+ for (let dependencyNodeId of dependencyBundleGraph.getNodeIdsConnectedTo(
1524
+ dependencyBundleGraph.getNodeIdByContentKey(String(bundleToRemoveId)),
1525
+ ALL_EDGE_TYPES,
1526
+ )) {
1527
+ let dependencyNode = nullthrows(
1528
+ dependencyBundleGraph.getNode(dependencyNodeId),
1529
+ );
1530
+ invariant(dependencyNode.type === 'dependency');
1531
+
1532
+ // Add dependency to the bundle to keep
1533
+ dependencyBundleGraph.addEdge(
1534
+ dependencyNodeId,
1535
+ dependencyBundleGraph.getNodeIdByContentKey(String(bundleToKeepId)),
1536
+ dependencyPriorityEdges[dependencyNode.value.priority],
1537
+ );
1538
+ // Remove dependency from the bundle to remove
1539
+ dependencyBundleGraph.removeEdge(
1540
+ dependencyNodeId,
1541
+ dependencyBundleGraph.getNodeIdByContentKey(
1542
+ String(bundleToRemoveId),
1543
+ ),
1544
+ dependencyPriorityEdges[dependencyNode.value.priority],
1545
+ );
1546
+ }
1547
+ }
1548
+ }
1549
+
1352
1550
  bundleGraph.removeNode(bundleToRemoveId);
1353
1551
  }
1354
1552
 
@@ -1400,12 +1598,20 @@ export function createIdealGraph(
1400
1598
  ),
1401
1599
  );
1402
1600
 
1601
+ let mergedBundles = new Set();
1602
+
1403
1603
  for (let cluster of clusters) {
1404
1604
  let [mergeTarget, ...rest] = cluster;
1405
1605
 
1406
1606
  for (let bundleIdToMerge of rest) {
1407
- mergeBundles(bundleGraph, mergeTarget, bundleIdToMerge, assetReference);
1607
+ mergeBundles(mergeTarget, bundleIdToMerge);
1408
1608
  }
1609
+
1610
+ mergedBundles.add(mergeTarget);
1611
+ }
1612
+
1613
+ if (getFeatureFlag('supportWebpackChunkName')) {
1614
+ return mergedBundles;
1409
1615
  }
1410
1616
  }
1411
1617
 
@@ -1419,9 +1625,7 @@ export function createIdealGraph(
1419
1625
  let assetOrderMap = new Map(assets.map((a, index) => [a, index]));
1420
1626
 
1421
1627
  for (let bundle of modifiedSourceBundles) {
1422
- // @ts-expect-error TS18046
1423
1628
  bundle.assets = new Set(
1424
- // @ts-expect-error TS18046
1425
1629
  [...bundle.assets].sort((a, b) => {
1426
1630
  let aIndex = nullthrows(assetOrderMap.get(a));
1427
1631
  let bIndex = nullthrows(assetOrderMap.get(b));
@@ -1539,6 +1743,7 @@ function createBundle(opts: {
1539
1743
  bundleBehavior: opts.bundleBehavior,
1540
1744
  env: nullthrows(opts.env),
1541
1745
  mainEntryAsset: null,
1746
+ bundleRoots: new Set(),
1542
1747
  manualSharedBundle: opts.manualSharedBundle,
1543
1748
  needsStableName: Boolean(opts.needsStableName),
1544
1749
  size: 0,
@@ -1555,6 +1760,7 @@ function createBundle(opts: {
1555
1760
  bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior,
1556
1761
  env: opts.env ?? asset.env,
1557
1762
  mainEntryAsset: asset,
1763
+ bundleRoots: new Set([asset]),
1558
1764
  manualSharedBundle: opts.manualSharedBundle,
1559
1765
  needsStableName: Boolean(opts.needsStableName),
1560
1766
  size: asset.stats.size,