1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | module.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 = (createServer());
|
89 | server.on("request", requestListener);
|
90 |
|
91 | let isClosing = false;
|
92 |
|
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 |
|
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 | };
|