Skip to content

Commit 7aa1efb

Browse files
authored
Merge pull request webpack#7651 from webpack/feature/split-chunks-max-size
add `splitChunks.maxSize` option
2 parents eaa5bc8 + fb2c24b commit 7aa1efb

File tree

24 files changed

+799
-40
lines changed

24 files changed

+799
-40
lines changed

lib/Chunk.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ class Chunk {
121121
this.entryModule = undefined;
122122
/** @private @type {SortableSet<Module>} */
123123
this._modules = new SortableSet(undefined, sortByIdentifier);
124+
/** @type {string?} */
125+
this.filenameTemplate = undefined;
124126

125127
/** @private */
126128
this._groups = new SortableSet(undefined, sortChunkGroupById);

lib/ContextModule.js

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
Author Tobias Koppers @sokra
44
*/
55
"use strict";
6-
const path = require("path");
76
const util = require("util");
87
const { OriginalSource, RawSource } = require("webpack-sources");
98
const Module = require("./Module");
109
const AsyncDependenciesBlock = require("./AsyncDependenciesBlock");
1110
const Template = require("./Template");
11+
const contextify = require("./util/identifier").contextify;
1212

1313
/** @typedef {import("./dependencies/ContextElementDependency")} ContextElementDependency */
1414

@@ -63,18 +63,6 @@ class ContextModule extends Module {
6363
return regexString.substring(1, regexString.length - 1);
6464
}
6565

66-
contextify(context, request) {
67-
return request
68-
.split("!")
69-
.map(subrequest => {
70-
let rp = path.relative(context, subrequest);
71-
if (path.sep === "\\") rp = rp.replace(/\\/g, "/");
72-
if (rp.indexOf("../") !== 0) rp = "./" + rp;
73-
return rp;
74-
})
75-
.join("!");
76-
}
77-
7866
_createIdentifier() {
7967
let identifier = this.context;
8068
if (this.options.resourceQuery) {
@@ -155,15 +143,15 @@ class ContextModule extends Module {
155143
}
156144

157145
libIdent(options) {
158-
let identifier = this.contextify(options.context, this.context);
146+
let identifier = contextify(options.context, this.context);
159147
if (this.options.mode) {
160148
identifier += ` ${this.options.mode}`;
161149
}
162150
if (this.options.recursive) {
163151
identifier += " recursive";
164152
}
165153
if (this.options.addon) {
166-
identifier += ` ${this.contextify(options.context, this.options.addon)}`;
154+
identifier += ` ${contextify(options.context, this.options.addon)}`;
167155
}
168156
if (this.options.regExp) {
169157
identifier += ` ${this.prettyRegExp(this.options.regExp + "")}`;

lib/Module.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,8 @@ Module.prototype.build = null;
384384
Module.prototype.source = null;
385385
Module.prototype.size = null;
386386
Module.prototype.nameForCondition = null;
387+
/** @type {null | function(Chunk): boolean} */
388+
Module.prototype.chunkCondition = null;
387389
Module.prototype.updateCacheModule = null;
388390

389391
module.exports = Module;

lib/NormalModule.js

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55
"use strict";
66

7-
const path = require("path");
87
const NativeModule = require("module");
98

109
const {
@@ -23,6 +22,7 @@ const ModuleBuildError = require("./ModuleBuildError");
2322
const ModuleError = require("./ModuleError");
2423
const ModuleWarning = require("./ModuleWarning");
2524
const createHash = require("./util/createHash");
25+
const contextify = require("./util/identifier").contextify;
2626

2727
const asString = buf => {
2828
if (Buffer.isBuffer(buf)) {
@@ -38,28 +38,6 @@ const asBuffer = str => {
3838
return str;
3939
};
4040

41-
const contextify = (context, request) => {
42-
return request
43-
.split("!")
44-
.map(r => {
45-
const splitPath = r.split("?");
46-
if (/^[a-zA-Z]:\\/.test(splitPath[0])) {
47-
splitPath[0] = path.win32.relative(context, splitPath[0]);
48-
if (!/^[a-zA-Z]:\\/.test(splitPath[0])) {
49-
splitPath[0] = splitPath[0].replace(/\\/g, "/");
50-
}
51-
}
52-
if (/^\//.test(splitPath[0])) {
53-
splitPath[0] = path.posix.relative(context, splitPath[0]);
54-
}
55-
if (!/^(\.\.\/|\/|[a-zA-Z]:\\)/.test(splitPath[0])) {
56-
splitPath[0] = "./" + splitPath[0];
57-
}
58-
return splitPath.join("?");
59-
})
60-
.join("!");
61-
};
62-
6341
class NonErrorEmittedError extends WebpackError {
6442
constructor(error) {
6543
super();

lib/WebpackOptionsDefaulter.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
215215
isProductionLikeMode(options)
216216
);
217217
this.set("optimization.splitChunks", {});
218+
this.set("optimization.splitChunks.hidePathInfo", "make", options => {
219+
return isProductionLikeMode(options);
220+
});
218221
this.set("optimization.splitChunks.chunks", "async");
219222
this.set("optimization.splitChunks.minSize", "make", options => {
220223
return isProductionLikeMode(options) ? 30000 : 10000;

lib/optimize/SplitChunksPlugin.js

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,16 @@ const crypto = require("crypto");
88
const SortableSet = require("../util/SortableSet");
99
const GraphHelpers = require("../GraphHelpers");
1010
const { isSubset } = require("../util/SetHelpers");
11+
const deterministicGrouping = require("../util/deterministicGrouping");
12+
const contextify = require("../util/identifier").contextify;
1113

14+
/** @typedef {import("../Compiler")} Compiler */
1215
/** @typedef {import("../Chunk")} Chunk */
1316
/** @typedef {import("../Module")} Module */
17+
/** @typedef {import("../util/deterministicGrouping").Options<Module>} DeterministicGroupingOptionsForModule */
18+
/** @typedef {import("../util/deterministicGrouping").GroupedItems<Module>} DeterministicGroupingGroupedItemsForModule */
19+
20+
const deterministicGroupingForModules = /** @type {function(DeterministicGroupingOptionsForModule): DeterministicGroupingGroupedItemsForModule[]} */ (deterministicGrouping);
1421

1522
const hashFilename = name => {
1623
return crypto
@@ -104,6 +111,7 @@ module.exports = class SplitChunksPlugin {
104111
options.chunks || "all"
105112
),
106113
minSize: options.minSize || 0,
114+
maxSize: options.maxSize || 0,
107115
minChunks: options.minChunks || 1,
108116
maxAsyncRequests: options.maxAsyncRequests || 1,
109117
maxInitialRequests: options.maxInitialRequests || 1,
@@ -112,11 +120,16 @@ module.exports = class SplitChunksPlugin {
112120
name: options.name,
113121
automaticNameDelimiter: options.automaticNameDelimiter
114122
}) || (() => {}),
123+
hidePathInfo: options.hidePathInfo || false,
115124
filename: options.filename || undefined,
116125
getCacheGroups: SplitChunksPlugin.normalizeCacheGroups({
117126
cacheGroups: options.cacheGroups,
118127
automaticNameDelimiter: options.automaticNameDelimiter
119-
})
128+
}),
129+
fallbackCacheGroup: SplitChunksPlugin.normalizeFallbackCacheGroup(
130+
options.fallbackCacheGroup || {},
131+
options
132+
)
120133
};
121134
}
122135

@@ -177,6 +190,26 @@ module.exports = class SplitChunksPlugin {
177190
if (typeof chunks === "function") return chunks;
178191
}
179192

193+
static normalizeFallbackCacheGroup(
194+
{
195+
minSize = undefined,
196+
maxSize = undefined,
197+
automaticNameDelimiter = undefined
198+
},
199+
{
200+
minSize: defaultMinSize = undefined,
201+
maxSize: defaultMaxSize = undefined,
202+
automaticNameDelimiter: defaultAutomaticNameDelimiter = undefined
203+
}
204+
) {
205+
return {
206+
minSize: typeof minSize === "number" ? minSize : defaultMinSize || 0,
207+
maxSize: typeof maxSize === "number" ? maxSize : defaultMaxSize || 0,
208+
automaticNameDelimiter:
209+
automaticNameDelimiter || defaultAutomaticNameDelimiter || "~"
210+
};
211+
}
212+
180213
static normalizeCacheGroups({ cacheGroups, automaticNameDelimiter }) {
181214
if (typeof cacheGroups === "function") {
182215
// TODO webpack 5 remove this
@@ -225,6 +258,7 @@ module.exports = class SplitChunksPlugin {
225258
),
226259
enforce: option.enforce,
227260
minSize: option.minSize,
261+
maxSize: option.maxSize,
228262
minChunks: option.minChunks,
229263
maxAsyncRequests: option.maxAsyncRequests,
230264
maxInitialRequests: option.maxInitialRequests,
@@ -278,6 +312,10 @@ module.exports = class SplitChunksPlugin {
278312
return false;
279313
}
280314

315+
/**
316+
* @param {Compiler} compiler webpack compiler
317+
* @returns {void}
318+
*/
281319
apply(compiler) {
282320
compiler.hooks.thisCompilation.tap("SplitChunksPlugin", compilation => {
283321
let alreadyOptimized = false;
@@ -486,6 +524,12 @@ module.exports = class SplitChunksPlugin {
486524
: cacheGroupSource.enforce
487525
? 0
488526
: this.options.minSize,
527+
maxSize:
528+
cacheGroupSource.maxSize !== undefined
529+
? cacheGroupSource.maxSize
530+
: cacheGroupSource.enforce
531+
? 0
532+
: this.options.maxSize,
489533
minChunks:
490534
cacheGroupSource.minChunks !== undefined
491535
? cacheGroupSource.minChunks
@@ -537,6 +581,9 @@ module.exports = class SplitChunksPlugin {
537581
}
538582
}
539583

584+
/** @type {Map<Chunk, {minSize: number, maxSize: number, automaticNameDelimiter: string}>} */
585+
const maxSizeQueueMap = new Map();
586+
540587
while (chunksInfoMap.size > 0) {
541588
// Find best matching entry
542589
let bestEntryKey;
@@ -563,6 +610,7 @@ module.exports = class SplitChunksPlugin {
563610

564611
let chunkName = item.name;
565612
// Variable for the new chunk (lazy created)
613+
/** @type {Chunk} */
566614
let newChunk;
567615
// When no chunk name, check if we can reuse a chunk instead of creating a new one
568616
let isReused = false;
@@ -689,6 +737,22 @@ module.exports = class SplitChunksPlugin {
689737
}
690738
}
691739
}
740+
741+
if (item.cacheGroup.maxSize > 0) {
742+
const oldMaxSizeSettings = maxSizeQueueMap.get(newChunk);
743+
maxSizeQueueMap.set(newChunk, {
744+
minSize: Math.max(
745+
oldMaxSizeSettings ? oldMaxSizeSettings.minSize : 0,
746+
item.cacheGroup.minSize
747+
),
748+
maxSize: Math.min(
749+
oldMaxSizeSettings ? oldMaxSizeSettings.maxSize : Infinity,
750+
item.cacheGroup.maxSize
751+
),
752+
automaticNameDelimiter: item.cacheGroup.automaticNameDelimiter
753+
});
754+
}
755+
692756
// remove all modules from other entries and update size
693757
for (const [key, info] of chunksInfoMap) {
694758
if (isOverlap(info.chunks, item.chunks)) {
@@ -709,6 +773,76 @@ module.exports = class SplitChunksPlugin {
709773
}
710774
}
711775
}
776+
777+
// Make sure that maxSize is fulfilled
778+
for (const chunk of compilation.chunks.slice()) {
779+
const { minSize, maxSize, automaticNameDelimiter } =
780+
maxSizeQueueMap.get(chunk) || this.options.fallbackCacheGroup;
781+
if (!maxSize) continue;
782+
const results = deterministicGroupingForModules({
783+
maxSize,
784+
minSize,
785+
items: chunk.modulesIterable,
786+
getKey(module) {
787+
const ident = contextify(
788+
compilation.options.context,
789+
module.identifier()
790+
);
791+
const name = module.nameForCondition
792+
? contextify(
793+
compilation.options.context,
794+
module.nameForCondition()
795+
)
796+
: ident.replace(/^.*!|\?[^?!]*$/g, "");
797+
const fullKey =
798+
name + automaticNameDelimiter + hashFilename(ident);
799+
return fullKey.replace(/[\\/?]/g, "_");
800+
},
801+
getSize(module) {
802+
return module.size();
803+
}
804+
});
805+
results.sort((a, b) => {
806+
if (a.key < b.key) return -1;
807+
if (a.key > b.key) return 1;
808+
return 0;
809+
});
810+
for (let i = 0; i < results.length; i++) {
811+
const group = results[i];
812+
const key = this.options.hidePathInfo
813+
? hashFilename(group.key)
814+
: group.key;
815+
let name = chunk.name
816+
? chunk.name + automaticNameDelimiter + key
817+
: null;
818+
if (name && name.length > 100) {
819+
name =
820+
name.slice(0, 100) +
821+
automaticNameDelimiter +
822+
hashFilename(name);
823+
}
824+
let newPart;
825+
if (i !== results.length - 1) {
826+
newPart = compilation.addChunk(name);
827+
chunk.split(newPart);
828+
// Add all modules to the new chunk
829+
for (const module of group.items) {
830+
if (typeof module.chunkCondition === "function") {
831+
if (!module.chunkCondition(newPart)) continue;
832+
}
833+
// Add module to new chunk
834+
GraphHelpers.connectChunkAndModule(newPart, module);
835+
// Remove module from used chunks
836+
chunk.removeModule(module);
837+
module.rewriteChunkInReasons(chunk, [newPart]);
838+
}
839+
} else {
840+
// change the chunk to be a part
841+
newPart = chunk;
842+
chunk.name = name;
843+
}
844+
}
845+
}
712846
}
713847
);
714848
});

0 commit comments

Comments
 (0)