fix(dev-server): invalidate WRN cache by content
This commit is contained in:
@@ -119,17 +119,21 @@ function compileWireToTs(file: string, version = 0): string {
|
|||||||
const cacheDir = compileCacheDir ?? join(dirname(file), ".wrnexus");
|
const cacheDir = compileCacheDir ?? join(dirname(file), ".wrnexus");
|
||||||
const name = basename(file).replace(/\.wrn$/, "");
|
const name = basename(file).replace(/\.wrn$/, "");
|
||||||
const suffix = version ? `-hmr-${version}` : "";
|
const suffix = version ? `-hmr-${version}` : "";
|
||||||
const out = join(cacheDir, `${name}-${hashPath(file)}${suffix}.wrn.ts`);
|
const source = readFileSync(file, "utf8");
|
||||||
|
// Include the source contents in the cache identity. Package managers, git
|
||||||
|
// checkouts, archive extraction, and linked dependencies can all replace a
|
||||||
|
// file while preserving (or moving backwards) its mtime. An mtime-only cache
|
||||||
|
// then serves an older compiled component even across a clean build.
|
||||||
|
const out = join(cacheDir, `${name}-${hashPath(file)}-${hashPath(source)}${suffix}.wrn.ts`);
|
||||||
|
|
||||||
// Skip recompiling when the on-disk cache is already newer than the source
|
// The content hash makes this safe even when source timestamps are preserved.
|
||||||
// (e.g. reused across dev restarts) — avoids a read + compile + write.
|
|
||||||
try {
|
try {
|
||||||
if (statSync(out).mtimeMs >= statSync(file).mtimeMs) return out;
|
if (statSync(out).isFile()) return out;
|
||||||
} catch {
|
} catch {
|
||||||
/* cache missing → compile below */
|
/* cache missing → compile below */
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = compileWireFile(readFileSync(file, "utf8"), file);
|
const code = compileWireFile(source, file);
|
||||||
mkdirSync(cacheDir, { recursive: true });
|
mkdirSync(cacheDir, { recursive: true });
|
||||||
writeFileSync(out, code, "utf8");
|
writeFileSync(out, code, "utf8");
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test } from "bun:test";
|
import { expect, test } from "bun:test";
|
||||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
import { mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { invalidateModule, loadModule, setCompileCacheDir } from "../src/pipeline.ts";
|
import { invalidateModule, loadModule, setCompileCacheDir } from "../src/pipeline.ts";
|
||||||
@@ -41,3 +41,24 @@ test("invalidateModule recompiles changed WRN files in-process", async () => {
|
|||||||
rmSync(root, { recursive: true, force: true });
|
rmSync(root, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("WRN cache follows content when a replacement has an older mtime", async () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), "wrnexus-cache-"));
|
||||||
|
const file = join(root, "component.wrn");
|
||||||
|
setCompileCacheDir(join(root, ".wrnexus"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
writeFileSync(file, "component Example { view { <p>Old</p> } }\n");
|
||||||
|
const first = (await loadModule(file)).render as () => unknown;
|
||||||
|
expect(String(first())).toContain("Old");
|
||||||
|
|
||||||
|
writeFileSync(file, "component Example { view { <p>Current</p> } }\n");
|
||||||
|
utimesSync(file, new Date(0), new Date(0));
|
||||||
|
invalidateModule(file);
|
||||||
|
|
||||||
|
const current = (await loadModule(file)).render as () => unknown;
|
||||||
|
expect(String(current())).toContain("Current");
|
||||||
|
} finally {
|
||||||
|
rmSync(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user