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>
74 lines
2.5 KiB
TypeScript
74 lines
2.5 KiB
TypeScript
import { afterAll, expect, test } from "bun:test";
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { compile, generate, islandNamesFrom, resolveWrnImports } from "@wrnexus/compiler";
|
|
import { collectScripts } from "../src/script-selection.ts";
|
|
|
|
const created: string[] = [];
|
|
afterAll(() => {
|
|
for (const dir of created) rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
function app() {
|
|
const root = mkdtempSync(join(process.cwd(), ".island-e2e-"));
|
|
created.push(root);
|
|
mkdirSync(join(root, "app"), { recursive: true });
|
|
writeFileSync(
|
|
join(root, "app", "Chart.tsx"),
|
|
`export default function Chart({ title }: { title: string }) { return <div>{title}</div>; }`,
|
|
);
|
|
writeFileSync(join(root, "app", "Card.wrn"), `component Card { view { <div>card</div> } }`);
|
|
return root;
|
|
}
|
|
|
|
test("a .wrn importing a .tsx emits an island marker and requests the bootstrap", () => {
|
|
const root = app();
|
|
const page = join(root, "app", "page.wrn");
|
|
const source = [
|
|
'import Chart from "./Chart"',
|
|
'import Card from "./Card"',
|
|
"page Home {",
|
|
" view {",
|
|
' <Chart title="Revenue" client:visible />',
|
|
" <Card />",
|
|
" }",
|
|
"}",
|
|
].join("\n");
|
|
writeFileSync(page, source);
|
|
|
|
const ast = compile(source, page).ast;
|
|
const islands = islandNamesFrom(
|
|
resolveWrnImports(ast.structuredImports, page, { appRoot: root }),
|
|
);
|
|
|
|
// Only the .tsx import is an island; the .wrn component is not.
|
|
expect([...islands]).toEqual(["Chart"]);
|
|
|
|
const out = generate(ast, { islands });
|
|
expect(out).toContain('data-wrn-island="Chart"');
|
|
expect(out).toContain('data-wrn-island-strategy="visible"');
|
|
expect(out).toContain('data-component="Card"');
|
|
|
|
// Rendered island markup must pull in the island bootstrap.
|
|
expect(collectScripts('<div data-wrn-island="Chart"></div>')).toContain("/__wrnexus/islands.js");
|
|
});
|
|
|
|
test("a page with no .tsx imports emits no island markup and no island script", () => {
|
|
const root = app();
|
|
const page = join(root, "app", "plain.wrn");
|
|
const source = ['import Card from "./Card"', "page Plain {", " view { <Card /> }", "}"].join(
|
|
"\n",
|
|
);
|
|
writeFileSync(page, source);
|
|
|
|
const ast = compile(source, page).ast;
|
|
const islands = islandNamesFrom(
|
|
resolveWrnImports(ast.structuredImports, page, { appRoot: root }),
|
|
);
|
|
expect(islands.size).toBe(0);
|
|
|
|
const out = generate(ast, { islands });
|
|
expect(out).not.toContain("data-wrn-island");
|
|
expect(collectScripts(out)).not.toContain("/__wrnexus/islands.js");
|
|
});
|