webpack/lib/hmr/lazyCompilationBackend.js

171 lines
5.4 KiB
JavaScript
Raw Normal View History

2021-01-21 23:32:49 +08:00
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
2025-08-22 06:40:41 +08:00
/** @typedef {import("../../declarations/WebpackOptions").LazyCompilationDefaultBackendOptions} LazyCompilationDefaultBackendOptions */
2024-08-07 23:22:25 +08:00
/** @typedef {import("http").IncomingMessage} IncomingMessage */
/** @typedef {import("http").RequestListener} RequestListener */
/** @typedef {import("http").ServerOptions} HttpServerOptions */
2024-08-07 23:22:25 +08:00
/** @typedef {import("http").ServerResponse} ServerResponse */
2025-08-22 06:40:41 +08:00
/** @typedef {import("http").Server} HttpServer */
/** @typedef {import("https").ServerOptions} HttpsServerOptions */
2025-08-22 06:40:41 +08:00
/** @typedef {import("https").Server} HttpsServer */
2024-08-07 23:22:25 +08:00
/** @typedef {import("net").AddressInfo} AddressInfo */
2021-01-21 23:32:49 +08:00
/** @typedef {import("../Compiler")} Compiler */
2024-08-07 23:22:25 +08:00
/** @typedef {import("../Module")} Module */
/** @typedef {import("./LazyCompilationPlugin").BackendApi} BackendApi */
2024-10-24 05:41:15 +08:00
/** @typedef {import("./LazyCompilationPlugin").BackendHandler} BackendHandler */
2021-10-25 18:12:52 +08:00
2025-08-22 06:40:41 +08:00
/** @typedef {HttpServer | HttpsServer} Server */
/** @typedef {(server: Server) => void} Listen */
2021-10-25 18:12:52 +08:00
/**
* @param {Omit<LazyCompilationDefaultBackendOptions, "client"> & { client: NonNullable<LazyCompilationDefaultBackendOptions["client"]>}} options additional options for the backend
* @returns {BackendHandler} backend
*/
module.exports = (options) => (compiler, callback) => {
2021-01-21 23:32:49 +08:00
const logger = compiler.getInfrastructureLogger("LazyCompilationBackend");
const activeModules = new Map();
const prefix = "/lazy-compilation-using-";
2021-10-25 18:12:52 +08:00
const isHttps =
options.protocol === "https" ||
(typeof options.server === "object" &&
("key" in options.server || "pfx" in options.server));
2025-08-22 06:40:41 +08:00
/** @type {() => Server} */
2021-10-25 18:12:52 +08:00
const createServer =
typeof options.server === "function"
? options.server
: (() => {
const http = isHttps ? require("https") : require("http");
2025-08-22 06:40:41 +08:00
return /** @type {(this: import("http") | import("https"), options: HttpServerOptions | HttpsServerOptions) => Server} */ (
http.createServer
).bind(
2024-08-07 23:22:25 +08:00
http,
/** @type {HttpServerOptions | HttpsServerOptions} */
(options.server)
);
2024-01-14 09:41:34 +08:00
})();
2025-08-22 06:40:41 +08:00
/** @type {Listen} */
2021-10-25 18:12:52 +08:00
const listen =
typeof options.listen === "function"
? options.listen
: (server) => {
2021-11-02 20:26:17 +08:00
let listen = options.listen;
if (typeof listen === "object" && !("port" in listen)) {
2021-11-02 20:26:17 +08:00
listen = { ...listen, port: undefined };
}
2021-11-02 20:26:17 +08:00
server.listen(listen);
2024-01-14 09:41:34 +08:00
};
2021-10-25 18:12:52 +08:00
const protocol = options.protocol || (isHttps ? "https" : "http");
2024-08-07 23:22:25 +08:00
/** @type {RequestListener} */
2021-07-07 16:02:07 +08:00
const requestListener = (req, res) => {
2024-08-07 23:22:25 +08:00
if (req.url === undefined) return;
2021-01-21 23:32:49 +08:00
const keys = req.url.slice(prefix.length).split("@");
req.socket.on("close", () => {
setTimeout(() => {
for (const key of keys) {
const oldValue = activeModules.get(key) || 0;
activeModules.set(key, oldValue - 1);
if (oldValue === 1) {
logger.log(
`${key} is no longer in use. Next compilation will skip this module.`
);
}
}
}, 120000);
});
req.socket.setNoDelay(true);
res.writeHead(200, {
"content-type": "text/event-stream",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Headers": "*"
2021-01-21 23:32:49 +08:00
});
res.write("\n");
let moduleActivated = false;
for (const key of keys) {
const oldValue = activeModules.get(key) || 0;
activeModules.set(key, oldValue + 1);
if (oldValue === 0) {
logger.log(`${key} is now in use and will be compiled.`);
moduleActivated = true;
}
}
if (moduleActivated && compiler.watching) compiler.watching.invalidate();
2021-07-07 16:02:07 +08:00
};
2025-08-22 06:40:41 +08:00
const server = createServer();
2021-10-25 18:12:52 +08:00
server.on("request", requestListener);
let isClosing = false;
/** @type {Set<import("net").Socket>} */
const sockets = new Set();
server.on("connection", (socket) => {
sockets.add(socket);
socket.on("close", () => {
sockets.delete(socket);
});
if (isClosing) socket.destroy();
});
server.on("clientError", (e) => {
2021-10-25 18:21:14 +08:00
if (e.message !== "Server is disposing") logger.warn(e);
});
2024-08-07 23:22:25 +08:00
server.on(
"listening",
/**
* @param {Error} err error
* @returns {void}
*/
(err) => {
2024-08-07 23:22:25 +08:00
if (err) return callback(err);
const _addr = server.address();
if (typeof _addr === "string") {
2024-08-07 23:22:25 +08:00
throw new Error("addr must not be a string");
}
2024-08-07 23:22:25 +08:00
const addr = /** @type {AddressInfo} */ (_addr);
const urlBase =
addr.address === "::" || addr.address === "0.0.0.0"
? `${protocol}://localhost:${addr.port}`
: addr.family === "IPv6"
? `${protocol}://[${addr.address}]:${addr.port}`
: `${protocol}://${addr.address}:${addr.port}`;
logger.log(
`Server-Sent-Events server for lazy compilation open at ${urlBase}.`
);
callback(null, {
dispose(callback) {
isClosing = true;
// Removing the listener is a workaround for a memory leak in node.js
server.off("request", requestListener);
server.close((err) => {
2024-08-07 23:22:25 +08:00
callback(err);
});
for (const socket of sockets) {
socket.destroy(new Error("Server is disposing"));
}
},
module(originalModule) {
const key = `${encodeURIComponent(
originalModule.identifier().replace(/\\/g, "/").replace(/@/g, "_")
).replace(/%(2F|3A|24|26|2B|2C|3B|3D)/g, decodeURIComponent)}`;
const active = activeModules.get(key) > 0;
return {
client: `${options.client}?${encodeURIComponent(urlBase + prefix)}`,
data: key,
active
};
}
2024-08-07 23:22:25 +08:00
});
}
);
2021-10-25 18:12:52 +08:00
listen(server);
2021-01-21 23:32:49 +08:00
};