Skip to content

Commit 54c7b04

Browse files
perf: share the compile cache with child processes and worker threads
`enableCompileCache()` only covers the current thread, so minimizer workers and other child processes compiled every module from scratch. Setting `NODE_COMPILE_CACHE` to the cache directory hands it to them at startup, unless the environment already names one or disables the cache.
1 parent 5d4e9e4 commit 54c7b04

5 files changed

Lines changed: 107 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack-cli": patch
3+
---
4+
5+
perf: share the compile cache with child processes and worker threads, such as minimizer workers, so they start warm too

‎packages/webpack-cli/bin/cli.js‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ const { enableCompileCache } = require("node:module");
88

99
if (enableCompileCache) {
1010
try {
11-
enableCompileCache();
11+
const { directory } = enableCompileCache();
12+
13+
// Minimizer workers and other child processes start without the cache;
14+
// the variable Node reads at startup hands them the same directory.
15+
if (directory && process.env.NODE_COMPILE_CACHE === undefined) {
16+
process.env.NODE_COMPILE_CACHE = directory;
17+
}
1218
} catch {
1319
// Ignore errors
1420
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"use strict";
2+
3+
const fs = require("node:fs");
4+
const { enableCompileCache } = require("node:module");
5+
const os = require("node:os");
6+
const path = require("node:path");
7+
const { run } = require("../../utils/test-utils");
8+
9+
/**
10+
* Runs the CLI on a configuration that reports the compile cache directory of the
11+
* child process and the worker thread it starts.
12+
* @param {Record<string, string>} env variables to set on top of a clean environment
13+
* @returns {Promise<{ variable: string | null, child: string | null, worker: string | null }>} what it reported
14+
*/
15+
const report = async (env) => {
16+
const cleanEnv = Object.fromEntries(
17+
Object.entries(process.env).filter(
18+
([name]) => !name.startsWith("NODE_COMPILE_CACHE") && name !== "NODE_DISABLE_COMPILE_CACHE",
19+
),
20+
);
21+
const { exitCode, stdout } = await run(__dirname, [], {
22+
env: { ...cleanEnv, ...env },
23+
extendEnv: false,
24+
});
25+
26+
expect(exitCode).toBe(0);
27+
28+
return JSON.parse(stdout.slice(stdout.indexOf("REPORT") + 6).split("\n")[0]);
29+
};
30+
31+
describe("compile cache", () => {
32+
// `module.enableCompileCache` is only available on Node.js >= 22.8.0
33+
if (!enableCompileCache) {
34+
it("should hand nothing on without a compile cache", async () => {
35+
expect(await report({})).toEqual({ variable: null, child: null, worker: null });
36+
});
37+
38+
return;
39+
}
40+
41+
it("should hand the compile cache to child processes and worker threads", async () => {
42+
const { variable, child, worker } = await report({});
43+
44+
expect(typeof variable).toBe("string");
45+
expect(child.startsWith(variable)).toBe(true);
46+
expect(worker.startsWith(variable)).toBe(true);
47+
});
48+
49+
it("should keep a directory the environment already names", async () => {
50+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "webpack-cli-compile-cache-"));
51+
52+
try {
53+
const { variable, child, worker } = await report({ NODE_COMPILE_CACHE: directory });
54+
55+
expect(variable).toBe(directory);
56+
expect(child.startsWith(directory)).toBe(true);
57+
expect(worker.startsWith(directory)).toBe(true);
58+
} finally {
59+
fs.rmSync(directory, { recursive: true, force: true });
60+
}
61+
});
62+
63+
it("should hand nothing on where the compile cache is disabled", async () => {
64+
expect(await report({ NODE_DISABLE_COMPILE_CACHE: "1" })).toEqual({
65+
variable: null,
66+
child: null,
67+
worker: null,
68+
});
69+
});
70+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("compile cache");
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"use strict";
2+
3+
const { execFileSync } = require("node:child_process");
4+
const { Worker } = require("node:worker_threads");
5+
6+
// `module.getCompileCacheDir` is only available on Node.js >= 22.8.0
7+
const script = "(require('node:module').getCompileCacheDir || (() => null))() || null";
8+
9+
const child = execFileSync(process.execPath, ["-p", `JSON.stringify(${script})`], {
10+
encoding: "utf8",
11+
});
12+
13+
new Worker(`require("node:worker_threads").parentPort.postMessage(${script})`, {
14+
eval: true,
15+
}).once("message", (worker) => {
16+
process.stdout.write(
17+
`REPORT${JSON.stringify({
18+
variable: process.env.NODE_COMPILE_CACHE || null,
19+
child: JSON.parse(child),
20+
worker,
21+
})}\n`,
22+
);
23+
process.exit(0);
24+
});

0 commit comments

Comments
 (0)