UNPKG

4.52 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8/** @typedef {import("http").ServerOptions} HttpServerOptions */
9/** @typedef {import("https").ServerOptions} HttpsServerOptions */
10/** @typedef {import("../../declarations/WebpackOptions").LazyCompilationDefaultBackendOptions} LazyCompilationDefaultBackendOptions */
11/** @typedef {import("../Compiler")} Compiler */
12
13/**
14 * @callback BackendHandler
15 * @param {Compiler} compiler compiler
16 * @param {function((Error | null)=, any=): void} callback callback
17 * @returns {void}
18 */
19
20/**
21 * @param {Omit<LazyCompilationDefaultBackendOptions, "client"> & { client: NonNullable<LazyCompilationDefaultBackendOptions["client"]>}} options additional options for the backend
22 * @returns {BackendHandler} backend
23 */
24module.exports = options => (compiler, callback) => {
25 const logger = compiler.getInfrastructureLogger("LazyCompilationBackend");
26 const activeModules = new Map();
27 const prefix = "/lazy-compilation-using-";
28
29 const isHttps =
30 options.protocol === "https" ||
31 (typeof options.server === "object" &&
32 ("key" in options.server || "pfx" in options.server));
33
34 const createServer =
35 typeof options.server === "function"
36 ? options.server
37 : (() => {
38 const http = isHttps ? require("https") : require("http");
39 return http.createServer.bind(http, options.server);
40 })();
41 const listen =
42 typeof options.listen === "function"
43 ? options.listen
44 : server => {
45 let listen = options.listen;
46 if (typeof listen === "object" && !("port" in listen))
47 listen = { ...listen, port: undefined };
48 server.listen(listen);
49 };
50
51 const protocol = options.protocol || (isHttps ? "https" : "http");
52
53 const requestListener = (req, res) => {
54 const keys = req.url.slice(prefix.length).split("@");
55 req.socket.on("close", () => {
56 setTimeout(() => {
57 for (const key of keys) {
58 const oldValue = activeModules.get(key) || 0;
59 activeModules.set(key, oldValue - 1);
60 if (oldValue === 1) {
61 logger.log(
62 `${key} is no longer in use. Next compilation will skip this module.`
63 );
64 }
65 }
66 }, 120000);
67 });
68 req.socket.setNoDelay(true);
69 res.writeHead(200, {
70 "content-type": "text/event-stream",
71 "Access-Control-Allow-Origin": "*",
72 "Access-Control-Allow-Methods": "*",
73 "Access-Control-Allow-Headers": "*"
74 });
75 res.write("\n");
76 let moduleActivated = false;
77 for (const key of keys) {
78 const oldValue = activeModules.get(key) || 0;
79 activeModules.set(key, oldValue + 1);
80 if (oldValue === 0) {
81 logger.log(`${key} is now in use and will be compiled.`);
82 moduleActivated = true;
83 }
84 }
85 if (moduleActivated && compiler.watching) compiler.watching.invalidate();
86 };
87
88 const server = /** @type {import("net").Server} */ (createServer());
89 server.on("request", requestListener);
90
91 let isClosing = false;
92 /** @type {Set<import("net").Socket>} */
93 const sockets = new Set();
94 server.on("connection", socket => {
95 sockets.add(socket);
96 socket.on("close", () => {
97 sockets.delete(socket);
98 });
99 if (isClosing) socket.destroy();
100 });
101 server.on("clientError", e => {
102 if (e.message !== "Server is disposing") logger.warn(e);
103 });
104 server.on("listening", err => {
105 if (err) return callback(err);
106 const addr = server.address();
107 if (typeof addr === "string") throw new Error("addr must not be a string");
108 const urlBase =
109 addr.address === "::" || addr.address === "0.0.0.0"
110 ? `${protocol}://localhost:${addr.port}`
111 : addr.family === "IPv6"
112 ? `${protocol}://[${addr.address}]:${addr.port}`
113 : `${protocol}://${addr.address}:${addr.port}`;
114 logger.log(
115 `Server-Sent-Events server for lazy compilation open at ${urlBase}.`
116 );
117 callback(null, {
118 dispose(callback) {
119 isClosing = true;
120 // Removing the listener is a workaround for a memory leak in node.js
121 server.off("request", requestListener);
122 server.close(err => {
123 callback(err);
124 });
125 for (const socket of sockets) {
126 socket.destroy(new Error("Server is disposing"));
127 }
128 },
129 module(originalModule) {
130 const key = `${encodeURIComponent(
131 originalModule.identifier().replace(/\\/g, "/").replace(/@/g, "_")
132 ).replace(/%(2F|3A|24|26|2B|2C|3B|3D|3A)/g, decodeURIComponent)}`;
133 const active = activeModules.get(key) > 0;
134 return {
135 client: `${options.client}?${encodeURIComponent(urlBase + prefix)}`,
136 data: key,
137 active
138 };
139 }
140 });
141 });
142 listen(server);
143};