Version
@rspack/core: 2.1.3
@rspack/cli: 2.1.3
- Node:
v24.16.0
- npm:
11.13.0
- Platform:
darwin arm64
Details
When cache: { type: "persistent" } is enabled and rspack build --watch is already running, deleting node_modules/.cache/rspack externally causes later rebuilds to keep compiling successfully, but persistent cache pack files are no longer written for the rest of that watch session.
Restarting the watch process makes persistent cache files generate again.
Expected behavior: after a rebuild, Rspack should either recreate the persistent cache pack files or surface a cache write error. It should not silently keep the watch build alive with an empty/non-writing persistent cache directory.
Reproduce link
No external repo. Minimal copy-paste repro below.
Reproduce Steps
Create these two files, then run npm install && npm test.
package.json
{
"name": "rspack-watch-persistent-cache-delete-repro",
"private": true,
"version": "1.0.0",
"scripts": {
"test": "node repro.js"
},
"devDependencies": {
"@rspack/cli": "2.1.3",
"@rspack/core": "2.1.3"
}
}
repro.js
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const { spawn } = require("node:child_process");
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rspack-watch-cache-"));
const srcDir = path.join(root, "src");
const cacheRoot = path.join(root, "node_modules/.cache/rspack");
fs.mkdirSync(srcDir, { recursive: true });
fs.writeFileSync(path.join(srcDir, "index.js"), "console.log(1);\n");
fs.writeFileSync(
path.join(root, "rspack.config.js"),
`module.exports = {
mode: "development",
entry: "./src/index.js",
output: { path: __dirname + "/dist", filename: "main.js" },
cache: { type: "persistent" }
};
`
);
const rspackBin =
process.env.RSPACK_BIN ||
path.join(
__dirname,
"node_modules/.bin",
process.platform === "win32" ? "rspack.cmd" : "rspack"
);
function listCacheFiles() {
if (!fs.existsSync(cacheRoot)) return [];
const files = [];
const walk = (dir) => {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const file = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(file);
} else {
files.push(path.relative(cacheRoot, file));
}
}
};
walk(cacheRoot);
return files.sort();
}
function waitForCompile(getCompileCount, expectedCount, output) {
return new Promise((resolve, reject) => {
const startedAt = Date.now();
const timer = setInterval(() => {
if (getCompileCount() >= expectedCount) {
clearInterval(timer);
resolve();
return;
}
if (Date.now() - startedAt > 30000) {
clearInterval(timer);
reject(
new Error(
`Timed out waiting for compile ${expectedCount}; saw ${getCompileCount()}.\n` +
output().slice(-2000)
)
);
}
}, 100);
});
}
async function main() {
console.log(`Temporary project: ${root}`);
console.log(`Rspack binary: ${rspackBin}`);
let output = "";
let compileCount = 0;
const child = spawn(rspackBin, ["build", "--watch", "-c", "rspack.config.js"], {
cwd: root,
env: process.env
});
const observe = (chunk) => {
const text = chunk.toString();
output += text;
process.stdout.write(text);
const matches = output.match(/Rspack compiled successfully/gi);
compileCount = matches ? matches.length : 0;
};
child.stdout.on("data", observe);
child.stderr.on("data", observe);
try {
await waitForCompile(() => compileCount, 1, () => output);
await new Promise((resolve) => setTimeout(resolve, 500));
const initialFiles = listCacheFiles();
console.log(`Initial persistent cache files: ${initialFiles.length}`);
if (initialFiles.length === 0) {
throw new Error("Initial watch build did not create persistent cache files.");
}
fs.rmSync(cacheRoot, { recursive: true, force: true });
console.log("Deleted node_modules/.cache/rspack while watch is still running.");
fs.writeFileSync(path.join(srcDir, "index.js"), "console.log(2);\n");
await waitForCompile(() => compileCount, 2, () => output);
await new Promise((resolve) => setTimeout(resolve, 1000));
const filesAfterRebuild = listCacheFiles();
console.log(`Persistent cache files after rebuild: ${filesAfterRebuild.length}`);
if (filesAfterRebuild.length === 0) {
throw new Error(
"BUG: rebuild succeeded, but persistent cache pack files were not recreated."
);
}
} finally {
child.kill("SIGTERM");
}
}
main().catch((error) => {
console.error(error.stack || error);
process.exitCode = 1;
});
Actual Output
Temporary project: /var/folders/.../T/rspack-watch-cache-I1MAnJ
Rspack binary: /.../node_modules/.bin/rspack
Rspack compiled successfully in 45 ms
Initial persistent cache files: 11
Deleted node_modules/.cache/rspack while watch is still running.
Rspack compiled successfully in 8 ms
Persistent cache files after rebuild: 0
Error: BUG: rebuild succeeded, but persistent cache pack files were not recreated.
Notes
A control run without deleting node_modules/.cache/rspack shows that watch rebuilds do update persistent cache files. The failure only appears after the cache directory is removed while the watch process is still alive.
Version
@rspack/core:2.1.3@rspack/cli:2.1.3v24.16.011.13.0darwin arm64Details
When
cache: { type: "persistent" }is enabled andrspack build --watchis already running, deletingnode_modules/.cache/rspackexternally causes later rebuilds to keep compiling successfully, but persistent cache pack files are no longer written for the rest of that watch session.Restarting the watch process makes persistent cache files generate again.
Expected behavior: after a rebuild, Rspack should either recreate the persistent cache pack files or surface a cache write error. It should not silently keep the watch build alive with an empty/non-writing persistent cache directory.
Reproduce link
No external repo. Minimal copy-paste repro below.
Reproduce Steps
Create these two files, then run
npm install && npm test.package.json{ "name": "rspack-watch-persistent-cache-delete-repro", "private": true, "version": "1.0.0", "scripts": { "test": "node repro.js" }, "devDependencies": { "@rspack/cli": "2.1.3", "@rspack/core": "2.1.3" } }repro.jsActual Output
Notes
A control run without deleting
node_modules/.cache/rspackshows that watch rebuilds do update persistent cache files. The failure only appears after the cache directory is removed while the watch process is still alive.