You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			144 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
		
		
			
		
	
	
			144 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			JavaScript
		
	
| 
											9 months ago
										 | /* | ||
|  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | ||
|  | 	Author Tobias Koppers @sokra | ||
|  | */ | ||
|  | 
 | ||
|  | "use strict"; | ||
|  | 
 | ||
|  | /** @typedef {import("http").ServerOptions} HttpServerOptions */ | ||
|  | /** @typedef {import("https").ServerOptions} HttpsServerOptions */ | ||
|  | /** @typedef {import("../../declarations/WebpackOptions").LazyCompilationDefaultBackendOptions} LazyCompilationDefaultBackendOptions */ | ||
|  | /** @typedef {import("../Compiler")} Compiler */ | ||
|  | 
 | ||
|  | /** | ||
|  |  * @callback BackendHandler | ||
|  |  * @param {Compiler} compiler compiler | ||
|  |  * @param {function((Error | null)=, any=): void} callback callback | ||
|  |  * @returns {void} | ||
|  |  */ | ||
|  | 
 | ||
|  | /** | ||
|  |  * @param {Omit<LazyCompilationDefaultBackendOptions, "client"> & { client: NonNullable<LazyCompilationDefaultBackendOptions["client"]>}} options additional options for the backend | ||
|  |  * @returns {BackendHandler} backend | ||
|  |  */ | ||
|  | module.exports = options => (compiler, callback) => { | ||
|  | 	const logger = compiler.getInfrastructureLogger("LazyCompilationBackend"); | ||
|  | 	const activeModules = new Map(); | ||
|  | 	const prefix = "/lazy-compilation-using-"; | ||
|  | 
 | ||
|  | 	const isHttps = | ||
|  | 		options.protocol === "https" || | ||
|  | 		(typeof options.server === "object" && | ||
|  | 			("key" in options.server || "pfx" in options.server)); | ||
|  | 
 | ||
|  | 	const createServer = | ||
|  | 		typeof options.server === "function" | ||
|  | 			? options.server | ||
|  | 			: (() => { | ||
|  | 					const http = isHttps ? require("https") : require("http"); | ||
|  | 					return http.createServer.bind(http, options.server); | ||
|  | 			  })(); | ||
|  | 	const listen = | ||
|  | 		typeof options.listen === "function" | ||
|  | 			? options.listen | ||
|  | 			: server => { | ||
|  | 					let listen = options.listen; | ||
|  | 					if (typeof listen === "object" && !("port" in listen)) | ||
|  | 						listen = { ...listen, port: undefined }; | ||
|  | 					server.listen(listen); | ||
|  | 			  }; | ||
|  | 
 | ||
|  | 	const protocol = options.protocol || (isHttps ? "https" : "http"); | ||
|  | 
 | ||
|  | 	const requestListener = (req, res) => { | ||
|  | 		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": "*" | ||
|  | 		}); | ||
|  | 		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(); | ||
|  | 	}; | ||
|  | 
 | ||
|  | 	const server = /** @type {import("net").Server} */ (createServer()); | ||
|  | 	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 => { | ||
|  | 		if (e.message !== "Server is disposing") logger.warn(e); | ||
|  | 	}); | ||
|  | 	server.on("listening", err => { | ||
|  | 		if (err) return callback(err); | ||
|  | 		const addr = server.address(); | ||
|  | 		if (typeof addr === "string") throw new Error("addr must not be a string"); | ||
|  | 		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 => { | ||
|  | 					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|3A)/g, decodeURIComponent)}`;
 | ||
|  | 				const active = activeModules.get(key) > 0; | ||
|  | 				return { | ||
|  | 					client: `${options.client}?${encodeURIComponent(urlBase + prefix)}`, | ||
|  | 					data: key, | ||
|  | 					active | ||
|  | 				}; | ||
|  | 			} | ||
|  | 		}); | ||
|  | 	}); | ||
|  | 	listen(server); | ||
|  | }; |