1// @effect-diagnostics nodeBuiltinImport:off - builds and executes real dev bundles on disk.
2import * as NodeChildProcess from "node:child_process";
3import * as NodeFSP from "node:fs/promises";
4import * as NodeOS from "node:os";
5import * as NodePath from "node:path";
6import * as NodeURL from "node:url";
7import * as NodeUtil from "node:util";
8
9import react from "@vitejs/plugin-react";
10import { createServer } from "vite-plus";
11import { expect, it } from "vite-plus/test";
12
13const execFile = NodeUtil.promisify(NodeChildProcess.execFile);
14
15it("initializes React refresh before a shared UI chunk runs in bundled dev", async () => {
16 const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-bootstrap-"));
17 const output = NodePath.join(root, "output");
18 let resolveBundle!: (files: Map<string, string>) => void;
19 let rejectBundle!: (error: unknown) => void;
20 const bundled = new Promise<Map<string, string>>((resolve, reject) => {
21 resolveBundle = resolve;
22 rejectBundle = reject;
23 });
24 let server: Awaited<ReturnType<typeof createServer>> | undefined;
25
26 try {
27 await NodeFSP.mkdir(NodePath.join(root, "src/lib"), { recursive: true });
28 await NodeFSP.writeFile(NodePath.join(root, "package.json"), '{"type":"module"}');
29 for (const file of ["index.html", "src/bootstrap.ts", "src/lib/bootError.ts"]) {
30 await NodeFSP.copyFile(new URL(`../${file}`, import.meta.url), NodePath.join(root, file));
31 }
32 await NodeFSP.writeFile(
33 NodePath.join(root, "src/shared.tsx"),
34 "export function Shared() { return <div>ready</div>; }",
35 );
36 await NodeFSP.writeFile(
37 NodePath.join(root, "src/main.tsx"),
38 `import { Shared } from "./shared";
39export const startup = Promise.resolve().then(() => globalThis.onStarted(Shared()));`,
40 );
41
42 server = await createServer({
43 configFile: false,
44 root,
45 publicDir: NodeURL.fileURLToPath(new URL("../public", import.meta.url)),
46 logLevel: "silent",
47 resolve: {
48 alias: { react: NodePath.dirname(NodeURL.fileURLToPath(import.meta.resolve("react"))) },
49 },
50 experimental: { bundledDev: true },
51 plugins: [
52 react(),
53 {
54 name: "capture-bootstrap-bundle",
55 buildEnd(error) {
56 if (error) rejectBundle(error);
57 },
58 generateBundle(_options, bundle) {
59 resolveBundle(
60 new Map(
61 Object.values(bundle)
62 .filter((file) => file.type === "chunk")
63 .map((file) => [file.fileName, file.code]),
64 ),
65 );
66 },
67 },
68 ],
69 build: {
70 rolldownOptions: {
71 experimental: { devMode: { lazy: false } },
72 output: {
73 codeSplitting: {
74 groups: [
75 { name: "vendor", test: /node_modules|@react-refresh/, priority: 10 },
76 { name: "shared-ui", test: /shared\.tsx$/, includeDependenciesRecursively: false },
77 ],
78 },
79 },
80 },
81 },
82 server: { host: "127.0.0.1", port: 0 },
83 });
84 await server.listen();
85 for (const [file, code] of await bundled) {
86 const target = NodePath.join(output, file);
87 await NodeFSP.mkdir(NodePath.dirname(target), { recursive: true });
88 await NodeFSP.writeFile(target, code);
89 }
90
91 const runner = NodePath.join(output, "check.mjs");
92 await NodeFSP.writeFile(
93 runner,
94 `import assert from "node:assert/strict";
95const started = Promise.withResolvers();
96globalThis.window = globalThis;
97globalThis.document = {
98 createElement: () => ({ relList: { supports: () => true } }),
99 getElementById: () => null,
100};
101globalThis.__rolldown_runtime__ = {
102 registerGraph() {},
103 registerModule() {},
104 createModuleHotContext: () => ({ accept() {} }),
105};
106globalThis.onStarted = started.resolve;
107console.error = (_message, error) => started.reject(error);
108await import("./assets/index.js");
109const element = await started.promise;
110assert.equal(element.props.children, "ready");
111assert.equal(typeof window.$RefreshReg$, "function");
112console.log("App started with React refresh ready.");`,
113 );
114 const result = await execFile("node", [runner]);
115 expect(result.stdout).toContain("App started with React refresh ready.");
116 } finally {
117 await server?.close();
118 await NodeFSP.rm(root, { recursive: true, force: true });
119 }
120});