diff --git a/lib/optimize/SplitChunksPlugin.js b/lib/optimize/SplitChunksPlugin.js index b17fcf211..86abd3502 100644 --- a/lib/optimize/SplitChunksPlugin.js +++ b/lib/optimize/SplitChunksPlugin.js @@ -330,6 +330,24 @@ const checkMinSize = (sizes, minSize) => { return true; }; +/** + * @param {SplitChunksSizes} sizes the sizes + * @param {SplitChunksSizes} minSize the min sizes + * @returns {undefined | string[]} list of size types that are below min size + */ +const getViolatingMinSizes = (sizes, minSize) => { + let list; + for (const key of Object.keys(minSize)) { + const size = sizes[key]; + if (size === undefined || size === 0) continue; + if (size < minSize[key]) { + if (list === undefined) list = [key]; + else list.push(key); + } + } + return list; +}; + /** * @param {SplitChunksSizes} sizes the sizes * @returns {number} the total size @@ -1130,12 +1148,40 @@ module.exports = class SplitChunksPlugin { logger.time("queue"); + /** + * @param {ChunksInfoItem} info entry + * @param {string[]} sourceTypes source types to be removed + */ + const removeModulesWithSourceType = (info, sourceTypes) => { + for (const module of info.modules) { + const types = module.getSourceTypes(); + if (sourceTypes.some(type => types.has(type))) { + info.modules.delete(module); + for (const type of types) { + info.sizes[type] -= module.size(type); + } + } + } + }; + + /** + * @param {ChunksInfoItem} info entry + * @returns {boolean} true, if entry become empty + */ + const removeMinSizeViolatingModules = info => { + if (!info.cacheGroup._validateSize) return false; + const violatingSizes = getViolatingMinSizes( + info.sizes, + info.cacheGroup.minSize + ); + if (violatingSizes === undefined) return false; + removeModulesWithSourceType(info, violatingSizes); + return info.modules.size === 0; + }; + // Filter items were size < minSize for (const [key, info] of chunksInfoMap) { - if ( - info.cacheGroup._validateSize && - !checkMinSize(info.sizes, info.cacheGroup.minSize) - ) { + if (removeMinSizeViolatingModules(info)) { chunksInfoMap.delete(key); } } @@ -1298,7 +1344,21 @@ module.exports = class SplitChunksPlugin { } } } - if (!checkMinSize(chunkSizes, item.cacheGroup.minRemainingSize)) { + const violatingSizes = getViolatingMinSizes( + chunkSizes, + item.cacheGroup.minRemainingSize + ); + if (violatingSizes !== undefined) { + const oldModulesSize = item.modules.size; + removeModulesWithSourceType(item, violatingSizes); + if ( + item.modules.size > 0 && + item.modules.size !== oldModulesSize + ) { + // queue this item again to be processed again + // without violating modules + chunksInfoMap.set(bestEntryKey, item); + } continue; } } @@ -1407,11 +1467,9 @@ module.exports = class SplitChunksPlugin { chunksInfoMap.delete(key); continue; } - if ( - info.cacheGroup._validateSize && - !checkMinSize(info.sizes, info.cacheGroup.minSize) - ) { + if (removeMinSizeViolatingModules(info)) { chunksInfoMap.delete(key); + continue; } } } diff --git a/test/configCases/split-chunks/issue-11513/big-module.js b/test/configCases/split-chunks/issue-11513/big-module.js new file mode 100644 index 000000000..4044b07f7 --- /dev/null +++ b/test/configCases/split-chunks/issue-11513/big-module.js @@ -0,0 +1,5 @@ +export default "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; diff --git a/test/configCases/split-chunks/issue-11513/chunk1.js b/test/configCases/split-chunks/issue-11513/chunk1.js new file mode 100644 index 000000000..b4a273e9e --- /dev/null +++ b/test/configCases/split-chunks/issue-11513/chunk1.js @@ -0,0 +1,2 @@ +import "./big-module"; +import "./wasm.wat"; diff --git a/test/configCases/split-chunks/issue-11513/chunk2.js b/test/configCases/split-chunks/issue-11513/chunk2.js new file mode 100644 index 000000000..b4a273e9e --- /dev/null +++ b/test/configCases/split-chunks/issue-11513/chunk2.js @@ -0,0 +1,2 @@ +import "./big-module"; +import "./wasm.wat"; diff --git a/test/configCases/split-chunks/issue-11513/index.js b/test/configCases/split-chunks/issue-11513/index.js new file mode 100644 index 000000000..0025ebdf4 --- /dev/null +++ b/test/configCases/split-chunks/issue-11513/index.js @@ -0,0 +1,4 @@ +it("should", async () => { + import("./chunk1"); + import("./chunk2"); +}); diff --git a/test/configCases/split-chunks/issue-11513/test.config.js b/test/configCases/split-chunks/issue-11513/test.config.js new file mode 100644 index 000000000..887746a67 --- /dev/null +++ b/test/configCases/split-chunks/issue-11513/test.config.js @@ -0,0 +1,5 @@ +module.exports = { + findBundle: function (i, options) { + return ["test.js", "main.js"]; + } +}; diff --git a/test/configCases/split-chunks/issue-11513/wasm.wat b/test/configCases/split-chunks/issue-11513/wasm.wat new file mode 100644 index 000000000..88f94a01e --- /dev/null +++ b/test/configCases/split-chunks/issue-11513/wasm.wat @@ -0,0 +1,9 @@ +(module + (func $add (export "add") (param $p0 i32) (param $p1 i32) (result i32) + (i32.add + (get_local $p0) + (get_local $p1) + ) + ) +) + diff --git a/test/configCases/split-chunks/issue-11513/webpack.config.js b/test/configCases/split-chunks/issue-11513/webpack.config.js new file mode 100644 index 000000000..3ed14c215 --- /dev/null +++ b/test/configCases/split-chunks/issue-11513/webpack.config.js @@ -0,0 +1,33 @@ +/** @type {import("../../../../").Configuration} */ +module.exports = { + entry: "./index", + module: { + rules: [ + { + test: /\.wat$/, + loader: "wast-loader", + type: "webassembly/async" + } + ] + }, + output: { + filename: "[name].js" + }, + optimization: { + splitChunks: { + cacheGroups: { + test: { + name: "test", + minChunks: 2, + minSize: { + javascript: 100, + webassembly: 100 + } + } + } + } + }, + experiments: { + asyncWebAssembly: true + } +};