diff --git a/bun.lock b/bun.lock index d968aff3..2093fc71 100644 --- a/bun.lock +++ b/bun.lock @@ -325,7 +325,7 @@ }, "packages/cli": { "name": "@wrnexus/cli", - "version": "0.8.74", + "version": "0.8.75", "bin": { "wrnexus": "src/index.ts", }, diff --git a/packages/cli/package.json b/packages/cli/package.json index f434ff47..b6bd4376 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.8.74", + "version": "0.8.75", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/cli/src/build.ts b/packages/cli/src/build.ts index 3ab9a88f..683244b9 100644 --- a/packages/cli/src/build.ts +++ b/packages/cli/src/build.ts @@ -344,7 +344,14 @@ export async function runBuild(appRoot: string): Promise { `WRN-CLIENT-BUNDLE: failed to bundle ${file}\n${browserResult.logs.map(String).join("\n")}`, ); } - writeFileSync(clientOut, await browserResult.outputs[0]!.text(), "utf8"); + // Cache identity must cover bundled dependencies, not only the .wrn source. + // Otherwise a renderer/package update reuses an immutable browser URL. + const browserSource = await browserResult.outputs[0]!.text(); + const bundleHash = createHash("sha256").update(browserSource).digest("hex").slice(0, 16); + const bundledClientOut = join(clientModulesDir, `${clientStem}-${bundleHash}.mjs`); + writeFileSync(bundledClientOut, browserSource, "utf8"); + clientFiles.set(file, bundledClientOut); + writeFileSync(out, code.replaceAll(clientUrl, `/__wrnexus/client/${basename(bundledClientOut)}`), "utf8"); rmSync(clientEntry, { force: true }); } catch (error) { compiledFiles.delete(file); diff --git a/packages/cli/test/client-dependency-cache.test.ts b/packages/cli/test/client-dependency-cache.test.ts new file mode 100644 index 00000000..4fb9b1c0 --- /dev/null +++ b/packages/cli/test/client-dependency-cache.test.ts @@ -0,0 +1,24 @@ +import {afterAll,expect,test} from "bun:test"; +import {mkdirSync,mkdtempSync,readFileSync,readdirSync,rmSync,writeFileSync} from "node:fs"; +import {join} from "node:path"; +import {runBuild} from "../src/build.ts"; +const scratch=join(import.meta.dir,".tmp-client-dependency"); +mkdirSync(scratch,{recursive:true}); +afterAll(()=>rmSync(scratch,{recursive:true,force:true})); +test("changing a bundled dependency changes the immutable client URL",async()=>{ + const root=mkdtempSync(join(scratch,"app-")); + mkdirSync(join(root,"app/pages"),{recursive:true}); + writeFileSync(join(root,"package.json"),JSON.stringify({name:"client-cache-fixture"})); + writeFileSync(join(root,"wrnexus.config.ts"),"export default {};\n"); + writeFileSync(join(root,"app/pages/index.wrn"),'import {message} from "../message.ts"\npage Home { functions { client function show(){alert(message)} } view { } }'); + const files:string[]=[]; + for(const message of ["first dependency","updated dependency"]){ + writeFileSync(join(root,"app/message.ts"),`export const message=${JSON.stringify(message)};`); + await runBuild(root); + const file=readdirSync(join(root,"dist/client")).find(name=>name.startsWith("index-")&&readFileSync(join(root,"dist/client",name),"utf8").includes(message)); + expect(file).toBeDefined(); + files.push(file!); + expect(readFileSync(join(root,"dist/server.js"),"utf8")).toContain(`/__wrnexus/client/${file}`); + } + expect(files[0]).not.toBe(files[1]); +},120000);