diff --git a/lib/buildChunkGraph.js b/lib/buildChunkGraph.js index bbd6128733e..839f96e97ad 100644 --- a/lib/buildChunkGraph.js +++ b/lib/buildChunkGraph.js @@ -28,11 +28,13 @@ const GraphHelpers = require("./GraphHelpers"); /** * @typedef {Object} ChunkGroupInfo + * @property {ChunkGroup} chunkGroup the chunk group * @property {Set} minAvailableModules current minimal set of modules available at this point * @property {boolean} minAvailableModulesOwned true, if minAvailableModules is owned and can be modified * @property {Set[]} availableModulesToBeMerged enqueued updates to the minimal set of available modules * @property {QueueItem[]} skippedItems queue items that were skipped because module is already available in parent chunks (need to reconsider when minAvailableModules is shrinking) * @property {Set} resultingAvailableModules set of modules available including modules from this chunk group + * @property {Set} children set of children chunk groups, that will be revisited when availableModules shrink */ /** @@ -196,11 +198,13 @@ const visitModules = ( }); } chunkGroupInfoMap.set(chunkGroup, { + chunkGroup, minAvailableModules: new Set(), minAvailableModulesOwned: true, availableModulesToBeMerged: [], skippedItems: [], - resultingAvailableModules: undefined + resultingAvailableModules: undefined, + children: undefined }); return queue; }; @@ -418,7 +422,7 @@ const visitModules = ( } logger.timeEnd("visiting"); - if (queueConnect.size > 0) { + while (queueConnect.size > 0) { logger.time("calculating available modules"); // Figure out new parents for chunk groups @@ -435,17 +439,26 @@ const visitModules = ( } } info.resultingAvailableModules = resultingAvailableModules; + if (info.children === undefined) { + info.children = targets; + } else { + for (const target of targets) { + info.children.add(target); + } + } // 2. Update chunk group info for (const target of targets) { let chunkGroupInfo = chunkGroupInfoMap.get(target); if (chunkGroupInfo === undefined) { chunkGroupInfo = { + chunkGroup: target, minAvailableModules: undefined, minAvailableModulesOwned: undefined, availableModulesToBeMerged: [], skippedItems: [], - resultingAvailableModules: undefined + resultingAvailableModules: undefined, + children: undefined }; chunkGroupInfoMap.set(target, chunkGroupInfo); } @@ -463,7 +476,7 @@ const visitModules = ( // Execute the merge for (const info of outdatedChunkGroupInfo) { const availableModulesToBeMerged = info.availableModulesToBeMerged; - let minAvailableModules = info.minAvailableModules; + let cachedMinAvailableModules = info.minAvailableModules; // 1. Get minimal available modules // It doesn't make sense to traverse a chunk again with more available modules. @@ -474,29 +487,31 @@ const visitModules = ( } let changed = false; for (const availableModules of availableModulesToBeMerged) { - if (minAvailableModules === undefined) { - minAvailableModules = availableModules; - info.minAvailableModules = minAvailableModules; + if (cachedMinAvailableModules === undefined) { + cachedMinAvailableModules = availableModules; + info.minAvailableModules = cachedMinAvailableModules; info.minAvailableModulesOwned = false; changed = true; } else { if (info.minAvailableModulesOwned) { // We own it and can modify it - for (const m of minAvailableModules) { + for (const m of cachedMinAvailableModules) { if (!availableModules.has(m)) { - minAvailableModules.delete(m); + cachedMinAvailableModules.delete(m); changed = true; } } } else { - for (const m of minAvailableModules) { + for (const m of cachedMinAvailableModules) { if (!availableModules.has(m)) { - // minAvailableModules need to be modified + // cachedMinAvailableModules need to be modified // but we don't own it - // construct a new Set as intersection of minAvailableModules and availableModules + // construct a new Set as intersection of cachedMinAvailableModules and availableModules /** @type {Set} */ const newSet = new Set(); - const iterator = minAvailableModules[Symbol.iterator](); + const iterator = cachedMinAvailableModules[ + Symbol.iterator + ](); /** @type {IteratorResult} */ let it; while (!(it = iterator.next()).done) { @@ -510,9 +525,16 @@ const visitModules = ( newSet.add(module); } } - minAvailableModules = newSet; + cachedMinAvailableModules = newSet; info.minAvailableModulesOwned = true; info.minAvailableModules = newSet; + + // Update the cache from the first queue + // if the chunkGroup is currently cached + if (chunkGroup === info.chunkGroup) { + minAvailableModules = cachedMinAvailableModules; + } + changed = true; break; } @@ -528,6 +550,19 @@ const visitModules = ( queue.push(queueItem); } info.skippedItems.length = 0; + + // 3. Reconsider children chunk groups + if (info.children !== undefined) { + const chunkGroup = info.chunkGroup; + for (const c of info.children) { + let connectList = queueConnect.get(chunkGroup); + if (connectList === undefined) { + connectList = new Set(); + queueConnect.set(chunkGroup, connectList); + } + connectList.add(c); + } + } } outdatedChunkGroupInfo.clear(); logger.timeEnd("merging available modules"); diff --git a/test/configCases/chunk-graph/issue-9634/async-b.js b/test/configCases/chunk-graph/issue-9634/async-b.js new file mode 100644 index 00000000000..4aac7c43c3e --- /dev/null +++ b/test/configCases/chunk-graph/issue-9634/async-b.js @@ -0,0 +1 @@ +export default import(/* webpackChunkName: "shared" */ "./shared"); diff --git a/test/configCases/chunk-graph/issue-9634/async-b2.js b/test/configCases/chunk-graph/issue-9634/async-b2.js new file mode 100644 index 00000000000..c002386be38 --- /dev/null +++ b/test/configCases/chunk-graph/issue-9634/async-b2.js @@ -0,0 +1 @@ +export default import(/* webpackChunkName: "async-b" */ "./async-b"); diff --git a/test/configCases/chunk-graph/issue-9634/entry-a.js b/test/configCases/chunk-graph/issue-9634/entry-a.js new file mode 100644 index 00000000000..67559019b2d --- /dev/null +++ b/test/configCases/chunk-graph/issue-9634/entry-a.js @@ -0,0 +1,13 @@ +import leaf from "./leaf"; + +it("should include the leaf module", () => { + expect(leaf).toBe("ok"); +}); + +it("should load the leaf module from a", () => { + return import(/* webpackChunkName: "shared" */ "./shared").then(shared => { + return shared.default.then(module => { + expect(module.default).toBe("ok"); + }); + }); +}); diff --git a/test/configCases/chunk-graph/issue-9634/entry-b.js b/test/configCases/chunk-graph/issue-9634/entry-b.js new file mode 100644 index 00000000000..356285360db --- /dev/null +++ b/test/configCases/chunk-graph/issue-9634/entry-b.js @@ -0,0 +1,11 @@ +it("should load the leaf module from b", () => { + return import(/* webpackChunkName: "async-b2" */ "./async-b2").then(asy => { + return asy.default.then(asy => { + return asy.default.then(shared => { + return shared.default.then(module => { + expect(module.default).toBe("ok"); + }); + }); + }); + }); +}); diff --git a/test/configCases/chunk-graph/issue-9634/leaf.js b/test/configCases/chunk-graph/issue-9634/leaf.js new file mode 100644 index 00000000000..5c6b89abfc8 --- /dev/null +++ b/test/configCases/chunk-graph/issue-9634/leaf.js @@ -0,0 +1 @@ +export default "ok"; diff --git a/test/configCases/chunk-graph/issue-9634/module.js b/test/configCases/chunk-graph/issue-9634/module.js new file mode 100644 index 00000000000..6f1d5125a62 --- /dev/null +++ b/test/configCases/chunk-graph/issue-9634/module.js @@ -0,0 +1,3 @@ +import leaf from "./leaf"; + +export default leaf; diff --git a/test/configCases/chunk-graph/issue-9634/shared.js b/test/configCases/chunk-graph/issue-9634/shared.js new file mode 100644 index 00000000000..23ced5ed605 --- /dev/null +++ b/test/configCases/chunk-graph/issue-9634/shared.js @@ -0,0 +1 @@ +export default import(/* webpackChunkName: "module" */ "./module"); diff --git a/test/configCases/chunk-graph/issue-9634/test.config.js b/test/configCases/chunk-graph/issue-9634/test.config.js new file mode 100644 index 00000000000..4f87cbec712 --- /dev/null +++ b/test/configCases/chunk-graph/issue-9634/test.config.js @@ -0,0 +1,5 @@ +module.exports = { + findBundle: function(i, options) { + return ["a.js", "b.js"]; + } +}; diff --git a/test/configCases/chunk-graph/issue-9634/webpack.config.js b/test/configCases/chunk-graph/issue-9634/webpack.config.js new file mode 100644 index 00000000000..db3b667a050 --- /dev/null +++ b/test/configCases/chunk-graph/issue-9634/webpack.config.js @@ -0,0 +1,9 @@ +module.exports = { + entry: { + b: "./entry-b", + a: "./entry-a" + }, + output: { + filename: "[name].js" + } +};