From 100317556ebc27b34c8eddc4174dba2efa247458 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 22 Jan 2021 01:01:17 +0100 Subject: [PATCH] add lazy compilation for entrypoints --- declarations/WebpackOptions.d.ts | 6 +- examples/lazy-compilation/webpack.config.js | 4 +- hot/lazy-compilation-node.js | 13 +++-- hot/lazy-compilation-web.js | 11 +++- lib/WebpackOptionsApply.js | 5 +- lib/hmr/LazyCompilationPlugin.js | 58 ++++++++++++------- schemas/WebpackOptions.json | 6 +- .../lazy-compilation/simple/webpack.config.js | 4 +- types.d.ts | 6 +- 9 files changed, 79 insertions(+), 34 deletions(-) diff --git a/declarations/WebpackOptions.d.ts b/declarations/WebpackOptions.d.ts index 483f179726c..1d78a780c5e 100644 --- a/declarations/WebpackOptions.d.ts +++ b/declarations/WebpackOptions.d.ts @@ -1042,7 +1042,7 @@ export interface Experiments { */ layers?: boolean; /** - * Compile import()s only when they are accessed. + * Compile entrypoints and import()s only when they are accessed. */ lazyCompilation?: | boolean @@ -1064,6 +1064,10 @@ export interface Experiments { * A custom client. */ client?: string; + /** + * Enable/disable lazy compilation for entries. + */ + entries?: boolean; }; /** * Allow output javascript files as module source type. diff --git a/examples/lazy-compilation/webpack.config.js b/examples/lazy-compilation/webpack.config.js index 2ba5928f24f..23c949cf511 100644 --- a/examples/lazy-compilation/webpack.config.js +++ b/examples/lazy-compilation/webpack.config.js @@ -10,7 +10,9 @@ module.exports = { idleTimeout: 5000 }, experiments: { - lazyCompilation: true + lazyCompilation: { + entries: false + } }, devServer: { hot: true, diff --git a/hot/lazy-compilation-node.js b/hot/lazy-compilation-node.js index e32573c842b..5ec254db312 100644 --- a/hot/lazy-compilation-node.js +++ b/hot/lazy-compilation-node.js @@ -2,16 +2,12 @@ "use strict"; -if (!module.hot) { - throw new Error( - "Environment doesn't support lazy compilation (requires Hot Module Replacement enabled)" - ); -} - var urlBase = decodeURIComponent(__resourceQuery.slice(1)); exports.keepAlive = function (options) { var data = options.data; var onError = options.onError; + var active = options.active; + var module = options.module; var response; var request = require("http").request( urlBase + data, @@ -22,6 +18,11 @@ exports.keepAlive = function (options) { function (res) { response = res; response.on("error", errorHandler); + if (!active && !module.hot) { + console.log( + "Hot Module Replacement is not enabled. Waiting for process restart..." + ); + } } ); function errorHandler(err) { diff --git a/hot/lazy-compilation-web.js b/hot/lazy-compilation-web.js index a7d8e59c929..62d955c5a22 100644 --- a/hot/lazy-compilation-web.js +++ b/hot/lazy-compilation-web.js @@ -2,9 +2,9 @@ "use strict"; -if (typeof EventSource !== "function" || !module.hot) { +if (typeof EventSource !== "function") { throw new Error( - "Environment doesn't support lazy compilation (requires EventSource and Hot Module Replacement enabled)" + "Environment doesn't support lazy compilation (requires EventSource)" ); } @@ -45,12 +45,19 @@ var updateEventSource = function updateEventSource() { exports.keepAlive = function (options) { var data = options.data; var onError = options.onError; + var active = options.active; + var module = options.module; errorHandlers.add(onError); var value = activeKeys.get(data) || 0; activeKeys.set(data, value + 1); if (value === 0) { updateEventSource(); } + if (!active && !module.hot) { + console.log( + "Hot Module Replacement is not enabled. Waiting for process restart..." + ); + } return function () { errorHandlers.delete(onError); diff --git a/lib/WebpackOptionsApply.js b/lib/WebpackOptionsApply.js index bf08c9b0765..3ef5ccd7bb5 100644 --- a/lib/WebpackOptionsApply.js +++ b/lib/WebpackOptionsApply.js @@ -256,7 +256,10 @@ class WebpackOptionsApply extends OptionsApply { options.experiments.lazyCompilation.client) || `webpack/hot/lazy-compilation-${ options.externalsPresets.node ? "node" : "web" - }.js` + }.js`, + entries: + typeof options.experiments.lazyCompilation !== "object" || + options.experiments.lazyCompilation.entries !== false }).apply(compiler); } diff --git a/lib/hmr/LazyCompilationPlugin.js b/lib/hmr/LazyCompilationPlugin.js index 6450820c6cc..056056cea3c 100644 --- a/lib/hmr/LazyCompilationPlugin.js +++ b/lib/hmr/LazyCompilationPlugin.js @@ -59,12 +59,8 @@ class LazyCompilationDependency extends Dependency { registerNotSerializable(LazyCompilationDependency); class LazyCompilationProxyModule extends Module { - constructor(originalModule, request, client, data, active) { - super( - "lazy-compilation-proxy", - originalModule.context, - originalModule.layer - ); + constructor(context, originalModule, request, client, data, active) { + super("lazy-compilation-proxy", context, originalModule.layer); this.originalModule = originalModule; this.request = request; this.client = client; @@ -172,14 +168,16 @@ class LazyCompilationProxyModule extends Module { .dependencies[0]); const clientModule = moduleGraph.getModule(clientDep); const block = this.blocks[0]; - const keepActive = Template.asString([ + const client = Template.asString([ `var client = ${runtimeTemplate.moduleExports({ module: clientModule, chunkGraph, request: clientDep.userRequest, runtimeRequirements })}`, - `var data = ${JSON.stringify(this.data)};`, + `var data = ${JSON.stringify(this.data)};` + ]); + const keepActive = Template.asString([ `var dispose = client.keepAlive({ data, active: ${JSON.stringify( !!block )}, module, onError });` @@ -189,11 +187,7 @@ class LazyCompilationProxyModule extends Module { const dep = block.dependencies[0]; const module = moduleGraph.getModule(dep); source = Template.asString([ - "module.hot.accept();", - `module.hot.accept(${JSON.stringify( - chunkGraph.getModuleId(module) - )}, function() { module.hot.invalidate(); });`, - "module.hot.dispose(function(data) { delete data.resolveSelf; dispose(data); });", + client, `module.exports = ${runtimeTemplate.moduleNamespacePromise({ chunkGraph, block, @@ -203,17 +197,31 @@ class LazyCompilationProxyModule extends Module { message: "import()", runtimeRequirements })};`, - "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);", + "if (module.hot) {", + Template.indent([ + "module.hot.accept();", + `module.hot.accept(${JSON.stringify( + chunkGraph.getModuleId(module) + )}, function() { module.hot.invalidate(); });`, + "module.hot.dispose(function(data) { delete data.resolveSelf; dispose(data); });", + "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);" + ]), + "}", "function onError() { /* ignore */ }", keepActive ]); } else { source = Template.asString([ - "module.hot.accept();", + client, "var resolveSelf, onError;", `module.exports = new Promise(function(resolve, reject) { resolveSelf = resolve; onError = reject; });`, - "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);", - "module.hot.dispose(function(data) { data.resolveSelf = resolveSelf; dispose(data); });", + "if (module.hot) {", + Template.indent([ + "module.hot.accept();", + "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);", + "module.hot.dispose(function(data) { data.resolveSelf = resolveSelf; dispose(data); });" + ]), + "}", keepActive ]); } @@ -263,10 +271,12 @@ class LazyCompilationPlugin { * @param {Object} options options * @param {(function(Compiler, string, function(Error?, any?): void): void) | function(Compiler, string): Promise} options.backend the backend * @param {string} options.client the client reference + * @param {boolean} options.entries true, when entries are lazy compiled */ - constructor({ backend, client }) { + constructor({ backend, client, entries }) { this.backend = backend; this.client = client; + this.entries = entries; } /** * Apply the plugin @@ -292,20 +302,28 @@ class LazyCompilationPlugin { } } ); - compiler.hooks.compilation.tap( + compiler.hooks.thisCompilation.tap( "LazyCompilationPlugin", (compilation, { normalModuleFactory }) => { normalModuleFactory.hooks.module.tap( "LazyCompilationPlugin", (originalModule, createData, resolveData) => { if ( - resolveData.dependencies.every(dep => dep.type === "import()") + resolveData.dependencies.every( + dep => + dep.type === "import()" || + (this.entries && dep.type === "entry") + ) && + !/webpack[/\\]hot[/\\]|webpack-dev-server[/\\]client/.test( + resolveData.request + ) ) { const moduleInfo = backend.module(originalModule); if (!moduleInfo) return; const { client, data, active } = moduleInfo; return new LazyCompilationProxyModule( + compiler.context, originalModule, resolveData.request, client, diff --git a/schemas/WebpackOptions.json b/schemas/WebpackOptions.json index fd8271b93fd..776c515045c 100644 --- a/schemas/WebpackOptions.json +++ b/schemas/WebpackOptions.json @@ -582,7 +582,7 @@ "type": "boolean" }, "lazyCompilation": { - "description": "Compile import()s only when they are accessed.", + "description": "Compile entrypoints and import()s only when they are accessed.", "anyOf": [ { "type": "boolean" @@ -599,6 +599,10 @@ "client": { "description": "A custom client.", "type": "string" + }, + "entries": { + "description": "Enable/disable lazy compilation for entries.", + "type": "boolean" } } } diff --git a/test/hotCases/lazy-compilation/simple/webpack.config.js b/test/hotCases/lazy-compilation/simple/webpack.config.js index 6943fecaee6..aef58ef1aaf 100644 --- a/test/hotCases/lazy-compilation/simple/webpack.config.js +++ b/test/hotCases/lazy-compilation/simple/webpack.config.js @@ -3,6 +3,8 @@ /** @type {import("../../../../").Configuration} */ module.exports = { experiments: { - lazyCompilation: true + lazyCompilation: { + entries: false + } } }; diff --git a/types.d.ts b/types.d.ts index 52d6f353491..8508e862353 100644 --- a/types.d.ts +++ b/types.d.ts @@ -2956,7 +2956,7 @@ declare interface Experiments { layers?: boolean; /** - * Compile import()s only when they are accessed. + * Compile entrypoints and import()s only when they are accessed. */ lazyCompilation?: | boolean @@ -2975,6 +2975,10 @@ declare interface Experiments { * A custom client. */ client?: string; + /** + * Enable/disable lazy compilation for entries. + */ + entries?: boolean; }; /**