Skip to content

Commit

Permalink
Merge pull request #7651 from webpack/feature/split-chunks-max-size
Browse files Browse the repository at this point in the history
add `splitChunks.maxSize` option
  • Loading branch information
sokra committed Jul 4, 2018
2 parents eaa5bc8 + fb2c24b commit 7aa1efb
Show file tree
Hide file tree
Showing 24 changed files with 799 additions and 40 deletions.
2 changes: 2 additions & 0 deletions lib/Chunk.js
Expand Up @@ -121,6 +121,8 @@ class Chunk {
this.entryModule = undefined;
/** @private @type {SortableSet<Module>} */
this._modules = new SortableSet(undefined, sortByIdentifier);
/** @type {string?} */
this.filenameTemplate = undefined;

/** @private */
this._groups = new SortableSet(undefined, sortChunkGroupById);
Expand Down
18 changes: 3 additions & 15 deletions lib/ContextModule.js
Expand Up @@ -3,12 +3,12 @@
Author Tobias Koppers @sokra
*/
"use strict";
const path = require("path");
const util = require("util");
const { OriginalSource, RawSource } = require("webpack-sources");
const Module = require("./Module");
const AsyncDependenciesBlock = require("./AsyncDependenciesBlock");
const Template = require("./Template");
const contextify = require("./util/identifier").contextify;

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

Expand Down Expand Up @@ -63,18 +63,6 @@ class ContextModule extends Module {
return regexString.substring(1, regexString.length - 1);
}

contextify(context, request) {
return request
.split("!")
.map(subrequest => {
let rp = path.relative(context, subrequest);
if (path.sep === "\\") rp = rp.replace(/\\/g, "/");
if (rp.indexOf("../") !== 0) rp = "./" + rp;
return rp;
})
.join("!");
}

_createIdentifier() {
let identifier = this.context;
if (this.options.resourceQuery) {
Expand Down Expand Up @@ -155,15 +143,15 @@ class ContextModule extends Module {
}

libIdent(options) {
let identifier = this.contextify(options.context, this.context);
let identifier = contextify(options.context, this.context);
if (this.options.mode) {
identifier += ` ${this.options.mode}`;
}
if (this.options.recursive) {
identifier += " recursive";
}
if (this.options.addon) {
identifier += ` ${this.contextify(options.context, this.options.addon)}`;
identifier += ` ${contextify(options.context, this.options.addon)}`;
}
if (this.options.regExp) {
identifier += ` ${this.prettyRegExp(this.options.regExp + "")}`;
Expand Down
2 changes: 2 additions & 0 deletions lib/Module.js
Expand Up @@ -384,6 +384,8 @@ Module.prototype.build = null;
Module.prototype.source = null;
Module.prototype.size = null;
Module.prototype.nameForCondition = null;
/** @type {null | function(Chunk): boolean} */
Module.prototype.chunkCondition = null;
Module.prototype.updateCacheModule = null;

module.exports = Module;
24 changes: 1 addition & 23 deletions lib/NormalModule.js
Expand Up @@ -4,7 +4,6 @@
*/
"use strict";

const path = require("path");
const NativeModule = require("module");

const {
Expand All @@ -23,6 +22,7 @@ const ModuleBuildError = require("./ModuleBuildError");
const ModuleError = require("./ModuleError");
const ModuleWarning = require("./ModuleWarning");
const createHash = require("./util/createHash");
const contextify = require("./util/identifier").contextify;

const asString = buf => {
if (Buffer.isBuffer(buf)) {
Expand All @@ -38,28 +38,6 @@ const asBuffer = str => {
return str;
};

const contextify = (context, request) => {
return request
.split("!")
.map(r => {
const splitPath = r.split("?");
if (/^[a-zA-Z]:\\/.test(splitPath[0])) {
splitPath[0] = path.win32.relative(context, splitPath[0]);
if (!/^[a-zA-Z]:\\/.test(splitPath[0])) {
splitPath[0] = splitPath[0].replace(/\\/g, "/");
}
}
if (/^\//.test(splitPath[0])) {
splitPath[0] = path.posix.relative(context, splitPath[0]);
}
if (!/^(\.\.\/|\/|[a-zA-Z]:\\)/.test(splitPath[0])) {
splitPath[0] = "./" + splitPath[0];
}
return splitPath.join("?");
})
.join("!");
};

class NonErrorEmittedError extends WebpackError {
constructor(error) {
super();
Expand Down
3 changes: 3 additions & 0 deletions lib/WebpackOptionsDefaulter.js
Expand Up @@ -215,6 +215,9 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
isProductionLikeMode(options)
);
this.set("optimization.splitChunks", {});
this.set("optimization.splitChunks.hidePathInfo", "make", options => {
return isProductionLikeMode(options);
});
this.set("optimization.splitChunks.chunks", "async");
this.set("optimization.splitChunks.minSize", "make", options => {
return isProductionLikeMode(options) ? 30000 : 10000;
Expand Down
136 changes: 135 additions & 1 deletion lib/optimize/SplitChunksPlugin.js
Expand Up @@ -8,9 +8,16 @@ const crypto = require("crypto");
const SortableSet = require("../util/SortableSet");
const GraphHelpers = require("../GraphHelpers");
const { isSubset } = require("../util/SetHelpers");
const deterministicGrouping = require("../util/deterministicGrouping");
const contextify = require("../util/identifier").contextify;

/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../util/deterministicGrouping").Options<Module>} DeterministicGroupingOptionsForModule */
/** @typedef {import("../util/deterministicGrouping").GroupedItems<Module>} DeterministicGroupingGroupedItemsForModule */

const deterministicGroupingForModules = /** @type {function(DeterministicGroupingOptionsForModule): DeterministicGroupingGroupedItemsForModule[]} */ (deterministicGrouping);

const hashFilename = name => {
return crypto
Expand Down Expand Up @@ -104,6 +111,7 @@ module.exports = class SplitChunksPlugin {
options.chunks || "all"
),
minSize: options.minSize || 0,
maxSize: options.maxSize || 0,
minChunks: options.minChunks || 1,
maxAsyncRequests: options.maxAsyncRequests || 1,
maxInitialRequests: options.maxInitialRequests || 1,
Expand All @@ -112,11 +120,16 @@ module.exports = class SplitChunksPlugin {
name: options.name,
automaticNameDelimiter: options.automaticNameDelimiter
}) || (() => {}),
hidePathInfo: options.hidePathInfo || false,
filename: options.filename || undefined,
getCacheGroups: SplitChunksPlugin.normalizeCacheGroups({
cacheGroups: options.cacheGroups,
automaticNameDelimiter: options.automaticNameDelimiter
})
}),
fallbackCacheGroup: SplitChunksPlugin.normalizeFallbackCacheGroup(
options.fallbackCacheGroup || {},
options
)
};
}

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

static normalizeFallbackCacheGroup(
{
minSize = undefined,
maxSize = undefined,
automaticNameDelimiter = undefined
},
{
minSize: defaultMinSize = undefined,
maxSize: defaultMaxSize = undefined,
automaticNameDelimiter: defaultAutomaticNameDelimiter = undefined
}
) {
return {
minSize: typeof minSize === "number" ? minSize : defaultMinSize || 0,
maxSize: typeof maxSize === "number" ? maxSize : defaultMaxSize || 0,
automaticNameDelimiter:
automaticNameDelimiter || defaultAutomaticNameDelimiter || "~"
};
}

static normalizeCacheGroups({ cacheGroups, automaticNameDelimiter }) {
if (typeof cacheGroups === "function") {
// TODO webpack 5 remove this
Expand Down Expand Up @@ -225,6 +258,7 @@ module.exports = class SplitChunksPlugin {
),
enforce: option.enforce,
minSize: option.minSize,
maxSize: option.maxSize,
minChunks: option.minChunks,
maxAsyncRequests: option.maxAsyncRequests,
maxInitialRequests: option.maxInitialRequests,
Expand Down Expand Up @@ -278,6 +312,10 @@ module.exports = class SplitChunksPlugin {
return false;
}

/**
* @param {Compiler} compiler webpack compiler
* @returns {void}
*/
apply(compiler) {
compiler.hooks.thisCompilation.tap("SplitChunksPlugin", compilation => {
let alreadyOptimized = false;
Expand Down Expand Up @@ -486,6 +524,12 @@ module.exports = class SplitChunksPlugin {
: cacheGroupSource.enforce
? 0
: this.options.minSize,
maxSize:
cacheGroupSource.maxSize !== undefined
? cacheGroupSource.maxSize
: cacheGroupSource.enforce
? 0
: this.options.maxSize,
minChunks:
cacheGroupSource.minChunks !== undefined
? cacheGroupSource.minChunks
Expand Down Expand Up @@ -537,6 +581,9 @@ module.exports = class SplitChunksPlugin {
}
}

/** @type {Map<Chunk, {minSize: number, maxSize: number, automaticNameDelimiter: string}>} */
const maxSizeQueueMap = new Map();

while (chunksInfoMap.size > 0) {
// Find best matching entry
let bestEntryKey;
Expand All @@ -563,6 +610,7 @@ module.exports = class SplitChunksPlugin {

let chunkName = item.name;
// Variable for the new chunk (lazy created)
/** @type {Chunk} */
let newChunk;
// When no chunk name, check if we can reuse a chunk instead of creating a new one
let isReused = false;
Expand Down Expand Up @@ -689,6 +737,22 @@ module.exports = class SplitChunksPlugin {
}
}
}

if (item.cacheGroup.maxSize > 0) {
const oldMaxSizeSettings = maxSizeQueueMap.get(newChunk);
maxSizeQueueMap.set(newChunk, {
minSize: Math.max(
oldMaxSizeSettings ? oldMaxSizeSettings.minSize : 0,
item.cacheGroup.minSize
),
maxSize: Math.min(
oldMaxSizeSettings ? oldMaxSizeSettings.maxSize : Infinity,
item.cacheGroup.maxSize
),
automaticNameDelimiter: item.cacheGroup.automaticNameDelimiter
});
}

// remove all modules from other entries and update size
for (const [key, info] of chunksInfoMap) {
if (isOverlap(info.chunks, item.chunks)) {
Expand All @@ -709,6 +773,76 @@ module.exports = class SplitChunksPlugin {
}
}
}

// Make sure that maxSize is fulfilled
for (const chunk of compilation.chunks.slice()) {
const { minSize, maxSize, automaticNameDelimiter } =
maxSizeQueueMap.get(chunk) || this.options.fallbackCacheGroup;
if (!maxSize) continue;
const results = deterministicGroupingForModules({
maxSize,
minSize,
items: chunk.modulesIterable,
getKey(module) {
const ident = contextify(
compilation.options.context,
module.identifier()
);
const name = module.nameForCondition
? contextify(
compilation.options.context,
module.nameForCondition()
)
: ident.replace(/^.*!|\?[^?!]*$/g, "");
const fullKey =
name + automaticNameDelimiter + hashFilename(ident);
return fullKey.replace(/[\\/?]/g, "_");
},
getSize(module) {
return module.size();
}
});
results.sort((a, b) => {
if (a.key < b.key) return -1;
if (a.key > b.key) return 1;
return 0;
});
for (let i = 0; i < results.length; i++) {
const group = results[i];
const key = this.options.hidePathInfo
? hashFilename(group.key)
: group.key;
let name = chunk.name
? chunk.name + automaticNameDelimiter + key
: null;
if (name && name.length > 100) {
name =
name.slice(0, 100) +
automaticNameDelimiter +
hashFilename(name);
}
let newPart;
if (i !== results.length - 1) {
newPart = compilation.addChunk(name);
chunk.split(newPart);
// Add all modules to the new chunk
for (const module of group.items) {
if (typeof module.chunkCondition === "function") {
if (!module.chunkCondition(newPart)) continue;
}
// Add module to new chunk
GraphHelpers.connectChunkAndModule(newPart, module);
// Remove module from used chunks
chunk.removeModule(module);
module.rewriteChunkInReasons(chunk, [newPart]);
}
} else {
// change the chunk to be a part
newPart = chunk;
chunk.name = name;
}
}
}
}
);
});
Expand Down

0 comments on commit 7aa1efb

Please sign in to comment.