add lazy compilation for entrypoints

This commit is contained in:
Tobias Koppers 2021-01-22 01:01:17 +01:00
parent 988cf8d47a
commit 100317556e
9 changed files with 79 additions and 34 deletions

View File

@ -1042,7 +1042,7 @@ export interface Experiments {
*/ */
layers?: boolean; layers?: boolean;
/** /**
* Compile import()s only when they are accessed. * Compile entrypoints and import()s only when they are accessed.
*/ */
lazyCompilation?: lazyCompilation?:
| boolean | boolean
@ -1064,6 +1064,10 @@ export interface Experiments {
* A custom client. * A custom client.
*/ */
client?: string; client?: string;
/**
* Enable/disable lazy compilation for entries.
*/
entries?: boolean;
}; };
/** /**
* Allow output javascript files as module source type. * Allow output javascript files as module source type.

View File

@ -10,7 +10,9 @@ module.exports = {
idleTimeout: 5000 idleTimeout: 5000
}, },
experiments: { experiments: {
lazyCompilation: true lazyCompilation: {
entries: false
}
}, },
devServer: { devServer: {
hot: true, hot: true,

View File

@ -2,16 +2,12 @@
"use strict"; "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)); var urlBase = decodeURIComponent(__resourceQuery.slice(1));
exports.keepAlive = function (options) { exports.keepAlive = function (options) {
var data = options.data; var data = options.data;
var onError = options.onError; var onError = options.onError;
var active = options.active;
var module = options.module;
var response; var response;
var request = require("http").request( var request = require("http").request(
urlBase + data, urlBase + data,
@ -22,6 +18,11 @@ exports.keepAlive = function (options) {
function (res) { function (res) {
response = res; response = res;
response.on("error", errorHandler); response.on("error", errorHandler);
if (!active && !module.hot) {
console.log(
"Hot Module Replacement is not enabled. Waiting for process restart..."
);
}
} }
); );
function errorHandler(err) { function errorHandler(err) {

View File

@ -2,9 +2,9 @@
"use strict"; "use strict";
if (typeof EventSource !== "function" || !module.hot) { if (typeof EventSource !== "function") {
throw new Error( 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) { exports.keepAlive = function (options) {
var data = options.data; var data = options.data;
var onError = options.onError; var onError = options.onError;
var active = options.active;
var module = options.module;
errorHandlers.add(onError); errorHandlers.add(onError);
var value = activeKeys.get(data) || 0; var value = activeKeys.get(data) || 0;
activeKeys.set(data, value + 1); activeKeys.set(data, value + 1);
if (value === 0) { if (value === 0) {
updateEventSource(); updateEventSource();
} }
if (!active && !module.hot) {
console.log(
"Hot Module Replacement is not enabled. Waiting for process restart..."
);
}
return function () { return function () {
errorHandlers.delete(onError); errorHandlers.delete(onError);

View File

@ -256,7 +256,10 @@ class WebpackOptionsApply extends OptionsApply {
options.experiments.lazyCompilation.client) || options.experiments.lazyCompilation.client) ||
`webpack/hot/lazy-compilation-${ `webpack/hot/lazy-compilation-${
options.externalsPresets.node ? "node" : "web" options.externalsPresets.node ? "node" : "web"
}.js` }.js`,
entries:
typeof options.experiments.lazyCompilation !== "object" ||
options.experiments.lazyCompilation.entries !== false
}).apply(compiler); }).apply(compiler);
} }

View File

@ -59,12 +59,8 @@ class LazyCompilationDependency extends Dependency {
registerNotSerializable(LazyCompilationDependency); registerNotSerializable(LazyCompilationDependency);
class LazyCompilationProxyModule extends Module { class LazyCompilationProxyModule extends Module {
constructor(originalModule, request, client, data, active) { constructor(context, originalModule, request, client, data, active) {
super( super("lazy-compilation-proxy", context, originalModule.layer);
"lazy-compilation-proxy",
originalModule.context,
originalModule.layer
);
this.originalModule = originalModule; this.originalModule = originalModule;
this.request = request; this.request = request;
this.client = client; this.client = client;
@ -172,14 +168,16 @@ class LazyCompilationProxyModule extends Module {
.dependencies[0]); .dependencies[0]);
const clientModule = moduleGraph.getModule(clientDep); const clientModule = moduleGraph.getModule(clientDep);
const block = this.blocks[0]; const block = this.blocks[0];
const keepActive = Template.asString([ const client = Template.asString([
`var client = ${runtimeTemplate.moduleExports({ `var client = ${runtimeTemplate.moduleExports({
module: clientModule, module: clientModule,
chunkGraph, chunkGraph,
request: clientDep.userRequest, request: clientDep.userRequest,
runtimeRequirements 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( `var dispose = client.keepAlive({ data, active: ${JSON.stringify(
!!block !!block
)}, module, onError });` )}, module, onError });`
@ -189,11 +187,7 @@ class LazyCompilationProxyModule extends Module {
const dep = block.dependencies[0]; const dep = block.dependencies[0];
const module = moduleGraph.getModule(dep); const module = moduleGraph.getModule(dep);
source = Template.asString([ source = Template.asString([
"module.hot.accept();", client,
`module.hot.accept(${JSON.stringify(
chunkGraph.getModuleId(module)
)}, function() { module.hot.invalidate(); });`,
"module.hot.dispose(function(data) { delete data.resolveSelf; dispose(data); });",
`module.exports = ${runtimeTemplate.moduleNamespacePromise({ `module.exports = ${runtimeTemplate.moduleNamespacePromise({
chunkGraph, chunkGraph,
block, block,
@ -203,17 +197,31 @@ class LazyCompilationProxyModule extends Module {
message: "import()", message: "import()",
runtimeRequirements 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 */ }", "function onError() { /* ignore */ }",
keepActive keepActive
]); ]);
} else { } else {
source = Template.asString([ source = Template.asString([
"module.hot.accept();", client,
"var resolveSelf, onError;", "var resolveSelf, onError;",
`module.exports = new Promise(function(resolve, reject) { resolveSelf = resolve; onError = reject; });`, `module.exports = new Promise(function(resolve, reject) { resolveSelf = resolve; onError = reject; });`,
"if (module.hot) {",
Template.indent([
"module.hot.accept();",
"if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);", "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);",
"module.hot.dispose(function(data) { data.resolveSelf = resolveSelf; dispose(data); });", "module.hot.dispose(function(data) { data.resolveSelf = resolveSelf; dispose(data); });"
]),
"}",
keepActive keepActive
]); ]);
} }
@ -263,10 +271,12 @@ class LazyCompilationPlugin {
* @param {Object} options options * @param {Object} options options
* @param {(function(Compiler, string, function(Error?, any?): void): void) | function(Compiler, string): Promise<any>} options.backend the backend * @param {(function(Compiler, string, function(Error?, any?): void): void) | function(Compiler, string): Promise<any>} options.backend the backend
* @param {string} options.client the client reference * @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.backend = backend;
this.client = client; this.client = client;
this.entries = entries;
} }
/** /**
* Apply the plugin * Apply the plugin
@ -292,20 +302,28 @@ class LazyCompilationPlugin {
} }
} }
); );
compiler.hooks.compilation.tap( compiler.hooks.thisCompilation.tap(
"LazyCompilationPlugin", "LazyCompilationPlugin",
(compilation, { normalModuleFactory }) => { (compilation, { normalModuleFactory }) => {
normalModuleFactory.hooks.module.tap( normalModuleFactory.hooks.module.tap(
"LazyCompilationPlugin", "LazyCompilationPlugin",
(originalModule, createData, resolveData) => { (originalModule, createData, resolveData) => {
if ( 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); const moduleInfo = backend.module(originalModule);
if (!moduleInfo) return; if (!moduleInfo) return;
const { client, data, active } = moduleInfo; const { client, data, active } = moduleInfo;
return new LazyCompilationProxyModule( return new LazyCompilationProxyModule(
compiler.context,
originalModule, originalModule,
resolveData.request, resolveData.request,
client, client,

View File

@ -582,7 +582,7 @@
"type": "boolean" "type": "boolean"
}, },
"lazyCompilation": { "lazyCompilation": {
"description": "Compile import()s only when they are accessed.", "description": "Compile entrypoints and import()s only when they are accessed.",
"anyOf": [ "anyOf": [
{ {
"type": "boolean" "type": "boolean"
@ -599,6 +599,10 @@
"client": { "client": {
"description": "A custom client.", "description": "A custom client.",
"type": "string" "type": "string"
},
"entries": {
"description": "Enable/disable lazy compilation for entries.",
"type": "boolean"
} }
} }
} }

View File

@ -3,6 +3,8 @@
/** @type {import("../../../../").Configuration} */ /** @type {import("../../../../").Configuration} */
module.exports = { module.exports = {
experiments: { experiments: {
lazyCompilation: true lazyCompilation: {
entries: false
}
} }
}; };

6
types.d.ts vendored
View File

@ -2956,7 +2956,7 @@ declare interface Experiments {
layers?: boolean; layers?: boolean;
/** /**
* Compile import()s only when they are accessed. * Compile entrypoints and import()s only when they are accessed.
*/ */
lazyCompilation?: lazyCompilation?:
| boolean | boolean
@ -2975,6 +2975,10 @@ declare interface Experiments {
* A custom client. * A custom client.
*/ */
client?: string; client?: string;
/**
* Enable/disable lazy compilation for entries.
*/
entries?: boolean;
}; };
/** /**