feat(compiler): bundle islands with a shared React chunk
splitting:true keeps React in one shared chunk so a page with several islands does not ship react-dom repeatedly. Island .tsx is compiled against React's JSX runtime via a Bun onLoad plugin. The repo's root tsconfig sets jsxImportSource to @wrnexus/core, so islands would otherwise compile to the HTML-string renderer and never mount. A @jsxImportSource pragma only affects the file carrying it, so injecting one into the generated entry is not enough — the injection has to happen per source file. App-authored islands stay plain .tsx. The JSX test asserts built output rather than generated entry text, because the entry-text assertion passed while the mechanism did not work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { afterAll, expect, test } from "bun:test";
|
||||
import { mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { assertReactAvailable, buildIslands, generateIslandEntry } from "../src/island-bundle.ts";
|
||||
|
||||
const created: string[] = [];
|
||||
afterAll(() => {
|
||||
for (const dir of created) rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("generates an entry that re-exports the island component", () => {
|
||||
const entry = generateIslandEntry({ name: "Chart", sourcePath: "/app/Chart.tsx" });
|
||||
expect(entry).toContain("/app/Chart.tsx");
|
||||
expect(entry).toContain("Chart");
|
||||
expect(entry).not.toContain("react-dom/server");
|
||||
});
|
||||
|
||||
test("island .tsx compiles against React's JSX runtime, not WRNexus's", async () => {
|
||||
// The root tsconfig points jsxImportSource at @wrnexus/core, so an island
|
||||
// would otherwise compile to WRNexus's HTML-string renderer and never mount.
|
||||
// A pragma applies only to the file carrying it, so this asserts the built
|
||||
// output rather than the generated entry text.
|
||||
// The fixture must live inside the repo: Bun resolves `react` from the
|
||||
// importing file's location, exactly as a real island resolves it from the
|
||||
// app that installed react.
|
||||
const root = mkdtempSync(join(process.cwd(), ".island-jsx-test-"));
|
||||
created.push(root);
|
||||
const source = join(root, "Chart.tsx");
|
||||
writeFileSync(
|
||||
source,
|
||||
`export default function Chart({ title }: { title: string }) {
|
||||
return <div className="chart">{title}</div>;
|
||||
}`,
|
||||
);
|
||||
|
||||
const outDir = join(root, "out");
|
||||
await buildIslands({ islands: [{ name: "Chart", sourcePath: source }], outDir });
|
||||
|
||||
const built = readdirSync(outDir)
|
||||
.filter((file) => file.endsWith(".js"))
|
||||
.map((file) => readFileSync(join(outDir, file), "utf8"))
|
||||
.join("\n");
|
||||
|
||||
expect(built).not.toContain("wrnexus");
|
||||
expect(built).toMatch(/react\/jsx|jsxDEV|jsx_runtime/);
|
||||
});
|
||||
|
||||
test("reports WRN-ISLAND-REACT-MISSING when react is not installed", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-noreact-"));
|
||||
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "app" }));
|
||||
|
||||
const diagnostic = assertReactAvailable(root);
|
||||
expect(diagnostic?.code).toBe("WRN-ISLAND-REACT-MISSING");
|
||||
expect(diagnostic?.message).toContain("bun add react react-dom");
|
||||
});
|
||||
|
||||
test("returns null when react resolves", () => {
|
||||
expect(assertReactAvailable(process.cwd())).toBeNull();
|
||||
});
|
||||
Reference in New Issue
Block a user