diff --git a/declarations/WebpackOptions.d.ts b/declarations/WebpackOptions.d.ts index 7f982c544..94b932338 100644 --- a/declarations/WebpackOptions.d.ts +++ b/declarations/WebpackOptions.d.ts @@ -100,6 +100,10 @@ export type LibraryType = * If `output.libraryTarget` is set to umd and `output.library` is set, setting this to true will name the AMD module. */ export type UmdNamedDefine = boolean; +/** + * The name of the runtime chunk. If set a runtime chunk with this name is created or an existing entrypoint is used as runtime. + */ +export type EntryRuntime = string; /** * An entry point without name. */ @@ -798,6 +802,10 @@ export interface EntryDescription { * Options for library. */ library?: LibraryOptions; + /** + * The name of the runtime chunk. If set a runtime chunk with this name is created or an existing entrypoint is used as runtime. + */ + runtime?: EntryRuntime; } /** * Options for library. @@ -2008,6 +2016,10 @@ export interface EntryDescriptionNormalized { * Options for library. */ library?: LibraryOptions; + /** + * The name of the runtime chunk. If set a runtime chunk with this name is created or an existing entrypoint is used as runtime. + */ + runtime?: EntryRuntime; } /** * Multiple entry bundles are created. The key is the entry name. The value is an entry description object. diff --git a/lib/Compilation.js b/lib/Compilation.js index f12484946..beb264c67 100644 --- a/lib/Compilation.js +++ b/lib/Compilation.js @@ -1743,7 +1743,10 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si chunk.filenameTemplate = options.filename; } const entrypoint = new Entrypoint(name); - if (!options.dependOn) entrypoint.setRuntimeChunk(chunk); + if (!options.dependOn && !options.runtime) { + entrypoint.setRuntimeChunk(chunk); + } + entrypoint.setEntrypointChunk(chunk); this.namedChunkGroups.set(name, entrypoint); this.entrypoints.set(name, entrypoint); this.chunkGroups.push(entrypoint); @@ -1788,7 +1791,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si for (const [ name, { - options: { dependOn } + options: { dependOn, runtime } } ] of this.entries) { if (dependOn) { @@ -1802,6 +1805,13 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si } connectChunkGroupParentAndChild(dependency, entry); } + } else if (runtime) { + const entry = this.entrypoints.get(name); + const chunk = this.addChunk(runtime); + chunk.preventIntegration = true; + entry.unshiftChunk(chunk); + chunk.addGroup(entry); + entry.setRuntimeChunk(chunk); } } buildChunkGraph(this, chunkGraphInit); diff --git a/lib/Entrypoint.js b/lib/Entrypoint.js index 123d3b23b..6f0b44d6d 100644 --- a/lib/Entrypoint.js +++ b/lib/Entrypoint.js @@ -23,7 +23,9 @@ class Entrypoint extends ChunkGroup { constructor(name) { super(name); /** @type {Chunk=} */ - this.runtimeChunk = undefined; + this._runtimeChunk = undefined; + /** @type {Chunk=} */ + this._entrypointChunk = undefined; } /** @@ -40,7 +42,7 @@ class Entrypoint extends ChunkGroup { * @returns {void} */ setRuntimeChunk(chunk) { - this.runtimeChunk = chunk; + this._runtimeChunk = chunk; } /** @@ -48,20 +50,39 @@ class Entrypoint extends ChunkGroup { * @returns {Chunk | null} returns the runtime chunk or null if there is none */ getRuntimeChunk() { - if (this.runtimeChunk) return this.runtimeChunk; + if (this._runtimeChunk) return this._runtimeChunk; for (const parent of this.parentsIterable) { if (parent instanceof Entrypoint) return parent.getRuntimeChunk(); } return null; } + /** + * Sets the chunk with the entrypoint modules for an entrypoint. + * @param {Chunk} chunk the chunk being set as the entrypoint chunk. + * @returns {void} + */ + setEntrypointChunk(chunk) { + this._entrypointChunk = chunk; + } + + /** + * Returns the chunk which contains the entrypoint modules + * (or at least the execution of them) + * @returns {Chunk} chunk + */ + getEntrypointChunk() { + return this._entrypointChunk; + } + /** * @param {Chunk} oldChunk chunk to be replaced * @param {Chunk} newChunk New chunk that will be replaced with * @returns {boolean} returns true if the replacement was successful */ replaceChunk(oldChunk, newChunk) { - if (this.runtimeChunk === oldChunk) this.runtimeChunk = newChunk; + if (this._runtimeChunk === oldChunk) this._runtimeChunk = newChunk; + if (this._entrypointChunk === oldChunk) this._entrypointChunk = newChunk; return super.replaceChunk(oldChunk, newChunk); } } diff --git a/lib/buildChunkGraph.js b/lib/buildChunkGraph.js index c6de20469..8219865a2 100644 --- a/lib/buildChunkGraph.js +++ b/lib/buildChunkGraph.js @@ -208,7 +208,7 @@ const visitModules = ( } else { // The application may start here: We start with an empty list of available modules chunkGroupInfo.minAvailableModules = EMPTY_SET; - const chunk = chunkGroup.chunks[0]; + const chunk = chunkGroup.getEntrypointChunk(); for (const module of modules) { queue.push({ action: ADD_AND_ENTER_MODULE, diff --git a/lib/optimize/RuntimeChunkPlugin.js b/lib/optimize/RuntimeChunkPlugin.js index 8b89c3135..5dceaa36e 100644 --- a/lib/optimize/RuntimeChunkPlugin.js +++ b/lib/optimize/RuntimeChunkPlugin.js @@ -5,8 +5,6 @@ "use strict"; -const { STAGE_ADVANCED } = require("../OptimizationStages"); - /** @typedef {import("../Compiler")} Compiler */ class RuntimeChunkPlugin { @@ -24,34 +22,17 @@ class RuntimeChunkPlugin { */ apply(compiler) { compiler.hooks.thisCompilation.tap("RuntimeChunkPlugin", compilation => { - compilation.hooks.optimizeChunks.tap( - { - name: "RuntimeChunkPlugin", - stage: STAGE_ADVANCED - }, - () => { - const chunkGraph = compilation.chunkGraph; - for (const entrypoint of compilation.entrypoints.values()) { - const chunk = entrypoint.getRuntimeChunk(); - // Only insert a runtime chunk when the current runtime chunk is part of the entrypoint - if (!entrypoint.chunks.includes(chunk)) continue; + compilation.hooks.addEntry.tap( + "RuntimeChunkPlugin", + (_, { name: entryName }) => { + const data = compilation.entries.get(entryName); + if (data.options.runtime === undefined && !data.options.dependOn) { // Determine runtime chunk name let name = this.options.name; if (typeof name === "function") { - name = name(entrypoint); - } - // Avoid adding runtime chunk twice - if ( - chunkGraph.getNumberOfChunkModules(chunk) > 0 || - !chunk.preventIntegration || - chunk.name !== name - ) { - const newChunk = compilation.addChunk(name); - newChunk.preventIntegration = true; - entrypoint.unshiftChunk(newChunk); - newChunk.addGroup(entrypoint); - entrypoint.setRuntimeChunk(newChunk); + name = name({ name: entryName }); } + data.options.runtime = name; } } ); diff --git a/schemas/WebpackOptions.json b/schemas/WebpackOptions.json index d3619244e..c533834fa 100644 --- a/schemas/WebpackOptions.json +++ b/schemas/WebpackOptions.json @@ -217,6 +217,9 @@ }, "library": { "$ref": "#/definitions/LibraryOptions" + }, + "runtime": { + "$ref": "#/definitions/EntryRuntime" } }, "required": ["import"] @@ -253,6 +256,9 @@ }, "library": { "$ref": "#/definitions/LibraryOptions" + }, + "runtime": { + "$ref": "#/definitions/EntryRuntime" } } }, @@ -313,6 +319,11 @@ ] } }, + "EntryRuntime": { + "description": "The name of the runtime chunk. If set a runtime chunk with this name is created or an existing entrypoint is used as runtime.", + "type": "string", + "minLength": 1 + }, "EntryStatic": { "description": "A static entry description.", "anyOf": [ diff --git a/types.d.ts b/types.d.ts index ae863b527..64d1ccbd8 100644 --- a/types.d.ts +++ b/types.d.ts @@ -935,7 +935,7 @@ declare class Compilation { Dependency, { name: string } & Pick< EntryDescriptionNormalized, - "filename" | "dependOn" | "library" + "filename" | "dependOn" | "library" | "runtime" > ], void @@ -945,7 +945,7 @@ declare class Compilation { Dependency, { name: string } & Pick< EntryDescriptionNormalized, - "filename" | "dependOn" | "library" + "filename" | "dependOn" | "library" | "runtime" >, Error ], @@ -956,7 +956,7 @@ declare class Compilation { Dependency, { name: string } & Pick< EntryDescriptionNormalized, - "filename" | "dependOn" | "library" + "filename" | "dependOn" | "library" | "runtime" >, Module ], @@ -1185,7 +1185,7 @@ declare class Compilation { | string | ({ name: string } & Pick< EntryDescriptionNormalized, - "filename" | "dependOn" | "library" + "filename" | "dependOn" | "library" | "runtime" >), callback: (err?: WebpackError, result?: Module) => void ): void; @@ -1194,7 +1194,7 @@ declare class Compilation { dependency: Dependency, options: { name: string } & Pick< EntryDescriptionNormalized, - "filename" | "dependOn" | "library" + "filename" | "dependOn" | "library" | "runtime" >, callback: (err?: WebpackError, result?: Module) => void ): void; @@ -2244,7 +2244,7 @@ declare interface EntryData { */ options: { name: string } & Pick< EntryDescriptionNormalized, - "filename" | "dependOn" | "library" + "filename" | "dependOn" | "library" | "runtime" >; } declare abstract class EntryDependency extends ModuleDependency {} @@ -2272,6 +2272,11 @@ declare interface EntryDescription { * Options for library. */ library?: LibraryOptions; + + /** + * The name of the runtime chunk. If set a runtime chunk with this name is created or an existing entrypoint is used as runtime. + */ + runtime?: string; } /** @@ -2297,6 +2302,11 @@ declare interface EntryDescriptionNormalized { * Options for library. */ library?: LibraryOptions; + + /** + * The name of the runtime chunk. If set a runtime chunk with this name is created or an existing entrypoint is used as runtime. + */ + runtime?: string; } type EntryItem = string | [string, ...string[]]; type EntryNormalized = @@ -2321,7 +2331,7 @@ declare class EntryPlugin { | string | ({ name: string } & Pick< EntryDescriptionNormalized, - "filename" | "dependOn" | "library" + "filename" | "dependOn" | "library" | "runtime" >) ); context: string; @@ -2330,7 +2340,7 @@ declare class EntryPlugin { | string | ({ name: string } & Pick< EntryDescriptionNormalized, - "filename" | "dependOn" | "library" + "filename" | "dependOn" | "library" | "runtime" >); /** @@ -2343,7 +2353,7 @@ declare class EntryPlugin { | string | ({ name: string } & Pick< EntryDescriptionNormalized, - "filename" | "dependOn" | "library" + "filename" | "dependOn" | "library" | "runtime" >) ): EntryDependency; } @@ -2356,8 +2366,6 @@ declare interface EntryStaticNormalized { [index: string]: EntryDescriptionNormalized; } declare abstract class Entrypoint extends ChunkGroup { - runtimeChunk: Chunk; - /** * Sets the runtimeChunk for an entrypoint. */ @@ -2367,6 +2375,17 @@ declare abstract class Entrypoint extends ChunkGroup { * Fetches the chunk reference containing the webpack bootstrap code */ getRuntimeChunk(): Chunk; + + /** + * Sets the chunk with the entrypoint modules for an entrypoint. + */ + setEntrypointChunk(chunk: Chunk): void; + + /** + * Returns the chunk which contains the entrypoint modules + * (or at least the execution of them) + */ + getEntrypointChunk(): Chunk; } declare class EnvironmentPlugin { constructor(...keys: any[]);