The island pieces existed but nothing connected .wrn compilation to island
emission. Now:
- codegen emits a data-wrn-island placeholder for component tags bound to
.tsx imports, keeping .wrn components on the normal mount path
- the dev pipeline and static build resolve island imports, thread the
names into codegen, and build the bundles
- collectScripts adds /__wrnexus/islands.js only when island markup is
present, so island-free pages still ship nothing
- island routes classify as static-interactive via hasIslands
Three bugs found by driving a real page in the browser:
1. The mount runtime was never built anywhere, so the bootstrap 404'd and
no island mounted.
2. Building the runtime separately from the islands gave each its own copy
of React: "Cannot read properties of null (reading 'useState')". The
runtime is now an entrypoint of the same build so React stays in one
shared chunk. The existing single-React test only compared bundles
within one build and could not see across build boundaries.
3. Island props arrived as attribute strings, so start={3} was "3" and
incrementing produced "31" then "311". Props now follow JSX semantics:
{…} parses as JSON, quoted values stay strings, and a runtime
expression is a WRN-ISLAND-PROPS build error rather than a silent
wrong value.
island-codegen.ts no longer imports @wrnexus/core. Compiler modules are
bundled into the Node-only VS Code extension, which contains no other
packages, so a runtime import of core broke the editor compiler; the two
helpers are implemented locally and the core dependency is dropped again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
91 lines
2.9 KiB
TypeScript
91 lines
2.9 KiB
TypeScript
import { afterAll, expect, test } from "bun:test";
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { runBuild } from "../src/build.ts";
|
|
|
|
const scratchRoot = join(import.meta.dir, ".tmp-plugin-runtime-production");
|
|
mkdirSync(scratchRoot, { recursive: true });
|
|
|
|
afterAll(() => rmSync(scratchRoot, { recursive: true, force: true }));
|
|
|
|
test("production renders and serves a content-addressed plugin runtime", async () => {
|
|
const root = mkdtempSync(join(scratchRoot, "app-"));
|
|
mkdirSync(join(root, "app", "pages"), { recursive: true });
|
|
mkdirSync(join(root, "app", "components"), { recursive: true });
|
|
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "plugin-runtime-fixture" }));
|
|
writeFileSync(
|
|
join(root, "wrnexus.config.ts"),
|
|
`import { definePlugin } from "@wrnexus/plugin";
|
|
|
|
export default {
|
|
plugins: [definePlugin({
|
|
name: "runtime-fixture",
|
|
clientRuntimes: [{
|
|
id: "runtime-fixture",
|
|
source: "window.__runtimeFixture = true;",
|
|
type: "script",
|
|
bundle: false,
|
|
}],
|
|
})],
|
|
};
|
|
`,
|
|
);
|
|
writeFileSync(
|
|
join(root, "app", "components", "RuntimeProbe.wrn"),
|
|
`component RuntimeProbe { view { <main {...attrs} data-wrnexus-runtime="runtime-fixture">Runtime fixture</main> } }\n`,
|
|
);
|
|
writeFileSync(
|
|
join(root, "app", "pages", "index.wrn"),
|
|
`import RuntimeProbe from "../components/RuntimeProbe.wrn"\npage Home { view { <RuntimeProbe /> } }\n`,
|
|
);
|
|
|
|
await runBuild(root);
|
|
|
|
const proc = Bun.spawn({
|
|
cmd: ["bun", join(root, "dist", "server.js")],
|
|
env: { ...process.env, PORT: "0" },
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
cwd: root,
|
|
});
|
|
|
|
let port: number | undefined;
|
|
try {
|
|
const reader = proc.stdout.getReader();
|
|
const decoder = new TextDecoder();
|
|
let output = "";
|
|
const giveUp = setTimeout(() => proc.kill(), 20_000);
|
|
try {
|
|
while (port === undefined) {
|
|
const { value, done } = await reader.read();
|
|
if (done) break;
|
|
output += decoder.decode(value, { stream: true });
|
|
const match = /listening on http:\/\/[^:]+:(\d+)/.exec(output);
|
|
if (match) port = Number(match[1]);
|
|
}
|
|
} finally {
|
|
clearTimeout(giveUp);
|
|
reader.releaseLock();
|
|
}
|
|
|
|
if (port === undefined) {
|
|
throw new Error(
|
|
`production server failed to start: ${await new Response(proc.stderr).text()}`,
|
|
);
|
|
}
|
|
|
|
const page = await fetch(`http://127.0.0.1:${port}/`);
|
|
expect(page.status).toBe(200);
|
|
const html = await page.text();
|
|
const assetPath = html.match(/(\/__wrnexus\/assets\/runtime-fixture\.[a-f0-9]+\.js)/)?.[1];
|
|
expect(assetPath).toBeTruthy();
|
|
|
|
const asset = await fetch(`http://127.0.0.1:${port}${assetPath}`);
|
|
expect(asset.status).toBe(200);
|
|
expect(await asset.text()).toContain("window.__runtimeFixture = true");
|
|
} finally {
|
|
proc.kill();
|
|
await proc.exited;
|
|
}
|
|
}, 30_000);
|