add `minRemainingSize` option for splitChunks to ensure leftover chunks from splitting have a minimum size

defaults to `minSize` of cache group
defaults to global option when not specified
global option defaults to zero in development
This commit is contained in:
Tobias Koppers 2019-05-13 13:10:23 +02:00
parent 0c39719729
commit 92f8c36ca2
15 changed files with 183 additions and 33 deletions

View File

@ -975,6 +975,10 @@ export interface OptimizationSplitChunksOptions {
* Minimum number of times a module has to be duplicated until it's considered for splitting
*/
minChunks?: number;
/**
* Minimal size for the chunks the stay after moving the modules to a new chunk
*/
minRemainingSize?: OptimizationSplitChunksSizes;
/**
* Minimal size for the created chunks
*/
@ -1035,6 +1039,10 @@ export interface OptimizationSplitChunksCacheGroup {
* Minimum number of times a module has to be duplicated until it's considered for splitting
*/
minChunks?: number;
/**
* Minimal size for the chunks the stay after moving the modules to a new chunk
*/
minRemainingSize?: OptimizationSplitChunksSizes;
/**
* Minimal size for the created chunk
*/

View File

@ -283,6 +283,9 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
this.set("optimization.splitChunks.minSize", "make", options => {
return isProductionLikeMode(options) ? 30000 : 10000;
});
this.set("optimization.splitChunks.minRemainingSize", "make", options => {
return options.mode === "development" ? 0 : undefined;
});
this.set("optimization.splitChunks.maxAsyncRequests", "make", options => {
return isProductionLikeMode(options) ? 6 : Infinity;
});

View File

@ -54,6 +54,7 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
* @property {ChunkFilterFunction=} chunksFilter
* @property {boolean=} enforce
* @property {SplitChunksSizes} minSize
* @property {SplitChunksSizes} minRemainingSize
* @property {SplitChunksSizes} maxAsyncSize
* @property {SplitChunksSizes} maxInitialSize
* @property {number=} minChunks
@ -73,6 +74,7 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
* @property {ChunkFilterFunction=} chunksFilter
* @property {boolean=} enforce
* @property {SplitChunksSizes} minSize
* @property {SplitChunksSizes} minRemainingSize
* @property {SplitChunksSizes} minSizeForMaxSize
* @property {SplitChunksSizes} maxAsyncSize
* @property {SplitChunksSizes} maxInitialSize
@ -118,6 +120,7 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
* @typedef {Object} SplitChunksOptions
* @property {ChunkFilterFunction} chunksFilter
* @property {SplitChunksSizes} minSize
* @property {SplitChunksSizes} minRemainingSize
* @property {SplitChunksSizes} maxInitialSize
* @property {SplitChunksSizes} maxAsyncSize
* @property {number} minChunks
@ -250,6 +253,17 @@ const mergeSizes = (...sizes) => {
return Object.assign({}, ...sizes.map(normalizeSizes).reverse());
};
/**
* @param {SplitChunksSizes} sizes the sizes
* @returns {boolean} true, if there are sizes > 0
*/
const hasNonZeroSizes = sizes => {
for (const key of Object.keys(sizes)) {
if (sizes[key] > 0) return true;
}
return false;
};
/**
* @param {SplitChunksSizes} a first sizes
* @param {SplitChunksSizes} b second sizes
@ -279,12 +293,12 @@ const combineSizes = (a, b, combine) => {
/**
* @param {SplitChunksSizes} sizes the sizes
* @param {SplitChunksSizes} minSize the min sizes
* @returns {boolean} true if sizes are below `minSize`
* @returns {boolean} true if there are sizes and all existing sizes are at least `minSize`
*/
const checkMinSize = (sizes, minSize) => {
for (const key of Object.keys(minSize)) {
const size = sizes[key];
if (size === undefined) return false;
if (size === undefined || size === 0) continue;
if (size < minSize[key]) return false;
}
return true;
@ -453,6 +467,7 @@ const createCacheGroupSource = options => {
chunksFilter: normalizeChunksFilter(options.chunks),
enforce: options.enforce,
minSize: normalizeSizes(options.minSize),
minRemainingSize: mergeSizes(options.minRemainingSize, options.minSize),
maxAsyncSize: mergeSizes(options.maxAsyncSize, options.maxSize),
maxInitialSize: mergeSizes(options.maxInitialSize, options.maxSize),
minChunks: options.minChunks,
@ -476,8 +491,9 @@ module.exports = class SplitChunksPlugin {
this.options = {
chunksFilter: normalizeChunksFilter(options.chunks || "all"),
minSize: normalizeSizes(options.minSize),
maxAsyncSize: normalizeSizes(options.maxAsyncSize || options.maxSize),
maxInitialSize: normalizeSizes(options.maxInitialSize || options.maxSize),
minRemainingSize: mergeSizes(options.minRemainingSize, options.minSize),
maxAsyncSize: mergeSizes(options.maxAsyncSize, options.maxSize),
maxInitialSize: mergeSizes(options.maxInitialSize, options.maxSize),
minChunks: options.minChunks || 1,
maxAsyncRequests: options.maxAsyncRequests || 1,
maxInitialRequests: options.maxInitialRequests || 1,
@ -700,7 +716,9 @@ module.exports = class SplitChunksPlugin {
),
cacheGroup,
name,
validateSize: Object.keys(cacheGroup.minSize).length > 0,
validateSize:
hasNonZeroSizes(cacheGroup.minSize) ||
hasNonZeroSizes(cacheGroup.minRemainingSize),
sizes: {},
chunks: new Set(),
reuseableChunks: new Set(),
@ -756,6 +774,12 @@ module.exports = class SplitChunksPlugin {
cacheGroupSource.minSize,
cacheGroupSource.enforce ? undefined : this.options.minSize
),
minRemainingSize: mergeSizes(
cacheGroupSource.minRemainingSize,
cacheGroupSource.enforce
? undefined
: this.options.minRemainingSize
),
minSizeForMaxSize: mergeSizes(
cacheGroupSource.minSize,
this.options.minSize
@ -954,12 +978,38 @@ module.exports = class SplitChunksPlugin {
});
}
if (hasNonZeroSizes(item.cacheGroup.minRemainingSize)) {
validChunks = validChunks.filter(chunk => {
let chunkSizes = chunkGraph.getChunkModulesSizes(chunk);
let containsModule = false;
// early check for remaining size
if (!checkMinSize(chunkSizes, item.cacheGroup.minRemainingSize))
return false;
chunkSizes = Object.assign({}, chunkSizes);
for (const module of item.modules) {
if (chunkGraph.isModuleInChunk(module, chunk)) {
containsModule = true;
for (const type of module.getSourceTypes()) {
chunkSizes[type] -= module.size(type);
}
}
}
return (
containsModule &&
checkMinSize(chunkSizes, item.cacheGroup.minRemainingSize)
);
});
} else {
validChunks = validChunks.filter(chunk => {
for (const module of item.modules) {
if (chunk.containsModule(module)) return true;
if (chunkGraph.isModuleInChunk(module, chunk)) return true;
}
return false;
});
}
if (validChunks.length < usedChunks.length) {
if (isReused) validChunks.push(newChunk);

View File

@ -657,6 +657,14 @@
"type": "number",
"minimum": 1
},
"minRemainingSize": {
"description": "Minimal size for the chunks the stay after moving the modules to a new chunk",
"oneOf": [
{
"$ref": "#/definitions/OptimizationSplitChunksSizes"
}
]
},
"minSize": {
"description": "Minimal size for the created chunk",
"oneOf": [
@ -865,6 +873,14 @@
"type": "number",
"minimum": 1
},
"minRemainingSize": {
"description": "Minimal size for the chunks the stay after moving the modules to a new chunk",
"oneOf": [
{
"$ref": "#/definitions/OptimizationSplitChunksSizes"
}
]
},
"minSize": {
"description": "Minimal size for the created chunks",
"oneOf": [

View File

@ -297,9 +297,17 @@ Child vendors:
+ 3 hidden dependent modules
Child multiple-vendors:
Entrypoint main = multiple-vendors/main.js
Entrypoint a = multiple-vendors/282.js multiple-vendors/954.js multiple-vendors/767.js multiple-vendors/a.js
Entrypoint b = multiple-vendors/282.js multiple-vendors/954.js multiple-vendors/767.js multiple-vendors/b.js
Entrypoint c = multiple-vendors/282.js multiple-vendors/769.js multiple-vendors/767.js multiple-vendors/c.js
Entrypoint a = multiple-vendors/libs-x.js multiple-vendors/954.js multiple-vendors/767.js multiple-vendors/a.js
Entrypoint b = multiple-vendors/libs-x.js multiple-vendors/954.js multiple-vendors/767.js multiple-vendors/b.js
Entrypoint c = multiple-vendors/libs-x.js multiple-vendors/769.js multiple-vendors/767.js multiple-vendors/c.js
chunk {119} multiple-vendors/libs-x.js (libs-x) 20 bytes [initial] [rendered] split chunk (cache group: libs) (name: libs-x)
> ./a [10] ./index.js 1:0-47
> ./b [10] ./index.js 2:0-47
> ./c [10] ./index.js 3:0-47
> ./a a
> ./b b
> ./c c
[282] ./node_modules/x.js 20 bytes {119} [built]
chunk {128} multiple-vendors/b.js (b) 92 bytes (javascript) 2.55 KiB (runtime) [entry] [rendered]
> ./b b
[996] ./b.js 72 bytes {128} {334} [built]
@ -308,18 +316,10 @@ Child multiple-vendors:
chunk {137} multiple-vendors/async-g.js (async-g) 34 bytes [rendered]
> ./g ./a.js 6:0-47
[785] ./g.js 34 bytes {137} [built]
chunk {179} multiple-vendors/main.js (main) 147 bytes (javascript) 4.6 KiB (runtime) [entry] [rendered]
chunk {179} multiple-vendors/main.js (main) 147 bytes (javascript) 4.61 KiB (runtime) [entry] [rendered]
> ./ main
[10] ./index.js 147 bytes {179} [built]
+ 7 hidden root modules
chunk {282} multiple-vendors/282.js 20 bytes [initial] [rendered] split chunk (cache group: vendors)
> ./a [10] ./index.js 1:0-47
> ./b [10] ./index.js 2:0-47
> ./c [10] ./index.js 3:0-47
> ./a a
> ./b b
> ./c c
[282] ./node_modules/x.js 20 bytes {282} [built]
chunk {334} multiple-vendors/async-b.js (async-b) 72 bytes [rendered]
> ./b [10] ./index.js 2:0-47
[996] ./b.js 72 bytes {128} {334} [built]
@ -1539,15 +1539,15 @@ exports[`StatsTestCases should print correct stats for named-chunk-groups 1`] =
> ./c [10] ./index.js 3:0-47
[282] ./node_modules/x.js 20 bytes {216} [built]
[954] ./node_modules/y.js 20 bytes {216} [built]
chunk {334} async-b.js (async-b) 40 bytes [rendered]
chunk {334} async-b.js (async-b) 175 bytes [rendered]
> ./b [10] ./index.js 2:0-47
[996] ./b.js 40 bytes {334} [built]
[996] ./b.js 175 bytes {334} [built]
chunk {383} async-c.js (async-c) 45 bytes [rendered]
> ./c [10] ./index.js 3:0-47
[460] ./c.js 45 bytes {383} [built]
chunk {794} async-a.js (async-a) 40 bytes [rendered]
chunk {794} async-a.js (async-a) 175 bytes [rendered]
> ./a [10] ./index.js 1:0-47
[847] ./a.js 40 bytes {794} [built]
[847] ./a.js 175 bytes {794} [built]
Child
Entrypoint main = main.js
Chunk Group async-a = 52.js async-a.js
@ -1565,15 +1565,15 @@ Child
> ./c [10] ./index.js 3:0-47
[282] ./node_modules/x.js 20 bytes {216} [built]
[954] ./node_modules/y.js 20 bytes {216} [built]
chunk {334} async-b.js (async-b) 40 bytes [rendered]
chunk {334} async-b.js (async-b) 175 bytes [rendered]
> ./b [10] ./index.js 2:0-47
[996] ./b.js 40 bytes {334} [built]
[996] ./b.js 175 bytes {334} [built]
chunk {383} async-c.js (async-c) 45 bytes [rendered]
> ./c [10] ./index.js 3:0-47
[460] ./c.js 45 bytes {383} [built]
chunk {794} async-a.js (async-a) 40 bytes [rendered]
chunk {794} async-a.js (async-a) 175 bytes [rendered]
> ./a [10] ./index.js 1:0-47
[847] ./a.js 40 bytes {794} [built]"
[847] ./a.js 175 bytes {794} [built]"
`;
exports[`StatsTestCases should print correct stats for named-chunks-plugin 1`] = `
@ -3002,6 +3002,28 @@ chunk {786} a.js (a) 12 bytes (javascript) 2.53 KiB (runtime) ={282}= [entry] [r
+ 2 hidden root modules"
`;
exports[`StatsTestCases should print correct stats for split-chunks-keep-remaining-size 1`] = `
"Entrypoint main = default/main.js
chunk {52} default/52.js 102 bytes <{179}> ={383}= ={794}= [rendered] split chunk (cache group: default)
> ./a [10] ./index.js 1:0-47
> ./c [10] ./index.js 3:0-47
[52] ./shared.js 102 bytes {52} {334} [built]
chunk {179} default/main.js (main) 147 bytes (javascript) 4.57 KiB (runtime) >{52}< >{334}< >{383}< >{794}< [entry] [rendered]
> ./ main
[10] ./index.js 147 bytes {179} [built]
+ 7 hidden root modules
chunk {334} default/async-b.js (async-b) 141 bytes <{179}> [rendered]
> ./b [10] ./index.js 2:0-47
[996] ./b.js 39 bytes {334} [built]
+ 1 hidden dependent module
chunk {383} default/async-c.js (async-c) 142 bytes <{179}> ={52}= [rendered]
> ./c [10] ./index.js 3:0-47
[460] ./c.js 142 bytes {383} [built]
chunk {794} default/async-a.js (async-a) 142 bytes <{179}> ={52}= [rendered]
> ./a [10] ./index.js 1:0-47
[847] ./a.js 142 bytes {794} [built]"
`;
exports[`StatsTestCases should print correct stats for split-chunks-max-size 1`] = `
"Child production:
Entrypoint main = prod-869.js prod-410.js prod-main-10f51d07.js prod-main-3c98d7c3.js prod-main-6bb16544.js prod-main-1df31ce3.js prod-main-2f7dcf2e.js prod-main-e7c5ace7.js prod-main-5cfff2c6.js prod-main-1443e336.js prod-main-77a8c116.js prod-main-89a43a0f.js prod-main-12217e1d.js

View File

@ -1,3 +1,6 @@
import "./shared";
export default "a";
// content content content content content content content content
// content content content content content content content content

View File

@ -1,3 +1,6 @@
import "./shared";
export default "b";
// content content content content content content content content
// content content content content content content content content

View File

@ -19,7 +19,8 @@ module.exports = {
},
optimization: {
splitChunks: {
minSize: 100
minSize: 100,
minRemainingSize: 0
}
},
stats

View File

@ -0,0 +1,5 @@
import "./shared";
export default "a";
// content content content content content content
// content content content content content content

View File

@ -0,0 +1,2 @@
import "./shared";
export default "b";

View File

@ -0,0 +1,5 @@
import "./shared";
export default "a";
// content content content content content content
// content content content content content content

View File

@ -0,0 +1,3 @@
import(/* webpackChunkName: "async-a" */ "./a");
import(/* webpackChunkName: "async-b" */ "./b");
import(/* webpackChunkName: "async-c" */ "./c");

View File

@ -0,0 +1,2 @@
// content content content content content content
// content content content content content content

View File

@ -0,0 +1,26 @@
const stats = {
hash: false,
timings: false,
builtAt: false,
assets: false,
chunks: true,
chunkRelations: true,
chunkOrigins: true,
entrypoints: true,
modules: false
};
module.exports = {
mode: "production",
entry: {
main: "./"
},
output: {
filename: "default/[name].js"
},
optimization: {
splitChunks: {
minSize: 100
}
},
stats
};

View File

@ -19,7 +19,8 @@ module.exports = {
},
optimization: {
splitChunks: {
minSize: 80
minSize: 80,
minRemainingSize: 0
}
},
stats