Merge pull request #11529 from webpack/bugfix/move-to-entrypoint

allow to move modules to parent chunks
This commit is contained in:
Tobias Koppers 2020-09-25 12:01:04 +02:00 committed by GitHub
commit 2604b5279a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 117 additions and 25 deletions

View File

@ -28,6 +28,7 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
/** @typedef {import("../../declarations/WebpackOptions").Output} OutputOptions */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../ChunkGroup")} ChunkGroup */
/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
/** @typedef {import("../Compilation").PathData} PathData */
/** @typedef {import("../Compiler")} Compiler */
@ -926,8 +927,10 @@ module.exports = class SplitChunksPlugin {
return entry2;
};
/** @type {Map<string, boolean>} */
const alreadyValidatedParents = new Map();
/** @type {Set<string>} */
const alreadyValidatedNames = new Set();
const alreadyReportedErrors = new Set();
// Map a list of chunks to a list of modules
// For the key the chunk "index" is used, the value is a SortableSet of modules
@ -958,23 +961,55 @@ module.exports = class SplitChunksPlugin {
cacheGroup.key
);
// Check if the name is ok
if (!alreadyValidatedNames.has(name)) {
alreadyValidatedNames.add(name);
if (compilation.namedChunks.has(name)) {
compilation.errors.push(
new WebpackError(
"SplitChunksPlugin\n" +
`Cache group "${cacheGroup.key}" conflicts with existing chunk.\n` +
`Both have the same name "${name}".\n` +
"Use a different name for the cache group.\n" +
'HINT: You can omit "name" to automatically create a name.\n' +
"BREAKING CHANGE: webpack < 5 used to allow to use the " +
"entrypoint as splitChunk. This is no longer allowed. " +
"Remove this entrypoint and add modules to cache group's 'test' instead. " +
"If you need modules to be evaluated on startup, add them to the existing entrypoints (make them arrays). " +
"See migration guide of more info."
)
);
const existingChunk = compilation.namedChunks.get(name);
if (existingChunk) {
const parentValidationKey = `${name}|${selectedChunksKey}`;
const valid = alreadyValidatedParents.get(parentValidationKey);
if (valid === false) return;
if (valid === undefined) {
// Module can only be moved into the existing chunk if the existing chunk
// is a parent of all selected chunks
let isInAllParents = true;
/** @type {Set<ChunkGroup>} */
const queue = new Set();
for (const chunk of selectedChunks) {
for (const group of chunk.groupsIterable) {
queue.add(group);
}
}
for (const group of queue) {
if (existingChunk.isInGroup(group)) continue;
let hasParent = false;
for (const parent of group.parentsIterable) {
hasParent = true;
queue.add(parent);
}
if (!hasParent) {
isInAllParents = false;
}
}
const valid = isInAllParents;
alreadyValidatedParents.set(parentValidationKey, valid);
if (!valid) {
if (!alreadyReportedErrors.has(name)) {
alreadyReportedErrors.add(name);
compilation.errors.push(
new WebpackError(
"SplitChunksPlugin\n" +
`Cache group "${cacheGroup.key}" conflicts with existing chunk.\n` +
`Both have the same name "${name}" and existing chunk is not a parent of the selected modules.\n` +
"Use a different name for the cache group or make sure that the existing chunk is a parent (e. g. via dependsOn).\n" +
'HINT: You can omit "name" to automatically create a name.\n' +
"BREAKING CHANGE: webpack < 5 used to allow to use an entrypoint as splitChunk. " +
"This is no longer allowed when the entrypoint is not a parent of the selected modules.\n" +
"Remove this entrypoint and add modules to cache group's 'test' instead. " +
"If you need modules to be evaluated on startup, add them to the existing entrypoints (make them arrays). " +
"See migration guide of more info."
)
);
}
return;
}
}
}
// Create key for maps
@ -1289,13 +1324,6 @@ module.exports = class SplitChunksPlugin {
}
if (chunkName) {
newChunk.chunkReason += ` (name: ${chunkName})`;
// If the chosen name is already an entry point we remove the entry point
const entrypoint = compilation.entrypoints.get(chunkName);
if (entrypoint) {
compilation.entrypoints.delete(chunkName);
entrypoint.remove();
chunkGraph.disconnectEntries(newChunk);
}
}
if (item.cacheGroup.filename) {
newChunk.filenameTemplate = item.cacheGroup.filename;

View File

@ -0,0 +1,3 @@
export { default as moduleA } from "./moduleA";
export { default as moduleB } from "./moduleB";
export { default as moduleC } from "./moduleC";

View File

@ -0,0 +1,15 @@
import fs from "fs";
import path from "path";
it("should place the module correctly", async () => {
const { moduleA, moduleB, moduleC } = await import("./chunk");
expect(fs.readFileSync(path.resolve(__dirname, "a.js"), "utf-8")).toContain(
moduleA
);
expect(fs.readFileSync(path.resolve(__dirname, "b.js"), "utf-8")).toContain(
moduleB
);
expect(
fs.readFileSync(path.resolve(__dirname, "runtime.js"), "utf-8")
).toContain(moduleC);
});

View File

@ -0,0 +1 @@
export default "This is module a";

View File

@ -0,0 +1 @@
export default "This is module b";

View File

@ -0,0 +1 @@
export default "This is module c";

View File

@ -0,0 +1,5 @@
module.exports = {
findBundle: function (i, options) {
return ["runtime.js", "a.js", "b.js"];
}
};

View File

@ -0,0 +1,38 @@
/** @type {import("../../../../").Configuration} */
module.exports = {
entry: {
a: {
import: "./entryA",
runtime: "runtime"
},
b: {
import: "./entryB",
dependOn: "a"
}
},
output: {
filename: "[name].js"
},
optimization: {
chunkIds: "named",
splitChunks: {
cacheGroups: {
a: {
test: /moduleA/,
name: "a",
enforce: true
},
b: {
test: /moduleB/,
name: "b",
enforce: true
},
c: {
test: /moduleC/,
name: "runtime",
enforce: true
}
}
}
}
};