// @effect-diagnostics nodeBuiltinImport:off - builds and executes real dev bundles on disk.import * as NodeChildProcess from "node:child_process";import * as NodeEvents from "node:events";import * as NodeFSP from "node:fs/promises";import * as NodeOS from "node:os";import * as NodePath from "node:path";import * as NodeURL from "node:url";import * as NodeUtil from "node:util"; import react from "@vitejs/plugin-react";import { createLogger, createServer } from "vite-plus";import { expect, it } from "vite-plus/test"; import { tailwindPlugins } from "../vite/tailwind"; const execFile = NodeUtil.promisify(NodeChildProcess.execFile); it("initializes React refresh before a shared UI chunk runs in bundled dev", async () => { const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-bootstrap-")); const output = NodePath.join(root, "output"); let resolveBundle!: (files: Map<string, string>) => void; let rejectBundle!: (error: unknown) => void; const bundled = new Promise<Map<string, string>>((resolve, reject) => { resolveBundle = resolve; rejectBundle = reject; }); let server: Awaited<ReturnType<typeof createServer>> | undefined; try { await NodeFSP.mkdir(NodePath.join(root, "src/lib"), { recursive: true }); await NodeFSP.writeFile(NodePath.join(root, "package.json"), '{"type":"module"}'); for (const file of ["index.html", "src/bootstrap.ts", "src/lib/bootError.ts"]) { await NodeFSP.copyFile(new URL(`../${file}`, import.meta.url), NodePath.join(root, file)); } await NodeFSP.writeFile( NodePath.join(root, "src/shared.tsx"), "export function Shared() { return <div>ready</div>; }", ); await NodeFSP.writeFile( NodePath.join(root, "src/main.tsx"), `import { Shared } from "./shared";export const startup = Promise.resolve().then(() => globalThis.onStarted(Shared()));`, ); server = await createServer({ configFile: false, root, publicDir: NodeURL.fileURLToPath(new URL("../public", import.meta.url)), logLevel: "silent", resolve: { alias: { react: NodePath.dirname(NodeURL.fileURLToPath(import.meta.resolve("react"))) }, }, experimental: { bundledDev: true }, plugins: [ react(), { name: "capture-bootstrap-bundle", buildEnd(error) { if (error) rejectBundle(error); }, generateBundle(_options, bundle) { resolveBundle( new Map( Object.values(bundle) .filter((file) => file.type === "chunk") .map((file) => [file.fileName, file.code]), ), ); }, }, ], build: { rolldownOptions: { experimental: { devMode: { lazy: false } }, output: { // Reproduce the shared chunks Vite creates after lazy routes load, // without needing a browser to trigger the lazy compiler first. codeSplitting: { groups: [ { name: "vendor", test: /node_modules|@react-refresh/, priority: 10 }, { name: "shared-ui", test: /shared\.tsx$/, includeDependenciesRecursively: false }, ], }, }, }, }, server: { host: "127.0.0.1", port: 0 }, }); await server.listen(); for (const [file, code] of await bundled) { const target = NodePath.join(output, file); await NodeFSP.mkdir(NodePath.dirname(target), { recursive: true }); await NodeFSP.writeFile(target, code); } // Run the actual generated ES modules so their import order and refresh // checks execute. These stubs replace only the browser and HMR transport. const runner = NodePath.join(output, "check.mjs"); await NodeFSP.writeFile( runner, `import assert from "node:assert/strict";const started = Promise.withResolvers();globalThis.window = globalThis;globalThis.document = { createElement: () => ({ relList: { supports: () => true } }), getElementById: () => null,};globalThis.__rolldown_runtime__ = { registerGraph() {}, registerModule() {}, createModuleHotContext: () => ({ accept() {} }),};globalThis.onStarted = started.resolve;console.error = (_message, error) => started.reject(error);await import("./assets/index.js");const element = await started.promise;assert.equal(element.props.children, "ready");assert.equal(typeof window.$RefreshReg$, "function");console.log("App started with React refresh ready.");`, ); const result = await execFile("node", [runner]); expect(result.stdout).toContain("App started with React refresh ready."); } finally { await server?.close(); await NodeFSP.rm(root, { recursive: true, force: true }); }}); it("hot updates Tailwind classes when a source file changes in bundled dev", async () => { const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-tailwind-")); const events = new NodeEvents.EventEmitter(); let server: Awaited<ReturnType<typeof createServer>> | undefined; let socket: WebSocket | undefined; let css = ""; try { await NodeFSP.writeFile( NodePath.join(root, "index.html"), '<html><body><script type="module" src="/main.ts"></script></body></html>', ); const source = 'import "./style.css"; export const margin = "m-[13px]"; import.meta.hot?.accept();'; await NodeFSP.writeFile(NodePath.join(root, "main.ts"), source); await NodeFSP.writeFile( NodePath.join(root, "style.css"), '@import "tailwindcss" source(none); @source "./main.ts";', ); const logger = createLogger("silent"); logger.error = (message) => events.emit("error", new Error(message)); const connected = NodeEvents.EventEmitter.once(events, "connected"); const ready = NodeEvents.EventEmitter.once(events, "ready"); server = await createServer({ configFile: false, root, customLogger: logger, resolve: { alias: { tailwindcss: NodeURL.fileURLToPath( new URL("../node_modules/tailwindcss/index.css", import.meta.url), ), }, }, experimental: { bundledDev: true }, plugins: [ ...tailwindPlugins(true), { name: "observe-tailwind-output", enforce: "pre", transform(code, id) { if (id.endsWith("/style.css")) css = code; }, async generateBundle() { // Keep the build pending until the socket can receive Vite's ready message. await connected; }, }, ], server: { host: "127.0.0.1", port: 0 }, }); await server.listen(); const address = server.httpServer?.address(); if (!address || typeof address === "string") throw new Error("Vite did not bind a port"); server.ws.on("vite:client-connected", () => events.emit("connected")); socket = new WebSocket( `ws://127.0.0.1:${address.port}/?token=${server.config.webSocketToken}`, "vite-hmr", ); socket.addEventListener("open", () => { socket?.send( JSON.stringify({ type: "custom", event: "vite:client-connected", data: { clientId: "tailwind-test" }, }), ); }); socket.addEventListener("message", ({ data }) => { const message: unknown = JSON.parse(String(data)); if (message !== null && typeof message === "object" && "type" in message) { // generateBundle runs before Vite stores the files for HTTP requests. if ( message.type === "full-reload" && "ifFallback" in message && message.ifFallback === true ) { events.emit("ready"); } else if (message.type === "bundled-dev-update") { events.emit("updated"); } } }); await connected; await ready; const entry = await fetch(`http://127.0.0.1:${address.port}/assets/index.js`); expect(entry.headers.get("content-type")).toContain("javascript"); await entry.text(); const updated = NodeEvents.EventEmitter.once(events, "updated"); await NodeFSP.writeFile(NodePath.join(root, "main.ts"), source.replace("13px", "137px")); await updated; expect(css).toContain("margin: 137px"); } finally { socket?.close(); await server?.close(); await NodeFSP.rm(root, { recursive: true, force: true }); }});
// @effect-diagnostics nodeBuiltinImport:off - builds and executes real dev bundles on disk.
import * as NodeChildProcess from "node:child_process";
import * as NodeEvents from "node:events";
import * as NodeFSP from "node:fs/promises";
import * as NodeOS from "node:os";
import * as NodePath from "node:path";
import * as NodeURL from "node:url";
import * as NodeUtil from "node:util";
import react from "@vitejs/plugin-react";
import { createLogger, createServer } from "vite-plus";
import { expect, it } from "vite-plus/test";
import { tailwindPlugins } from "../vite/tailwind";
const execFile = NodeUtil.promisify(NodeChildProcess.execFile);
it("initializes React refresh before a shared UI chunk runs in bundled dev", async () => {
const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-bootstrap-"));
const output = NodePath.join(root, "output");
let resolveBundle!: (files: Map<string, string>) => void;
let rejectBundle!: (error: unknown) => void;
const bundled = new Promise<Map<string, string>>((resolve, reject) => {
resolveBundle = resolve;
rejectBundle = reject;
});
let server: Awaited<ReturnType<typeof createServer>> | undefined;
try {
await NodeFSP.mkdir(NodePath.join(root, "src/lib"), { recursive: true });
await NodeFSP.writeFile(NodePath.join(root, "package.json"), '{"type":"module"}');
for (const file of ["index.html", "src/bootstrap.ts", "src/lib/bootError.ts"]) {
await NodeFSP.copyFile(new URL(`../${file}`, import.meta.url), NodePath.join(root, file));
}
await NodeFSP.writeFile(
NodePath.join(root, "src/shared.tsx"),
"export function Shared() { return <div>ready</div>; }",
);
await NodeFSP.writeFile(
NodePath.join(root, "src/main.tsx"),
`import { Shared } from "./shared";
export const startup = Promise.resolve().then(() => globalThis.onStarted(Shared()));`,
);
server = await createServer({
configFile: false,
root,
publicDir: NodeURL.fileURLToPath(new URL("../public", import.meta.url)),
logLevel: "silent",
resolve: {
alias: { react: NodePath.dirname(NodeURL.fileURLToPath(import.meta.resolve("react"))) },
},
experimental: { bundledDev: true },
plugins: [
react(),
{
name: "capture-bootstrap-bundle",
buildEnd(error) {
if (error) rejectBundle(error);
},
generateBundle(_options, bundle) {
resolveBundle(
new Map(
Object.values(bundle)
.filter((file) => file.type === "chunk")
.map((file) => [file.fileName, file.code]),
),
);
},
},
],
build: {
rolldownOptions: {
experimental: { devMode: { lazy: false } },
output: {
// Reproduce the shared chunks Vite creates after lazy routes load,
// without needing a browser to trigger the lazy compiler first.
codeSplitting: {
groups: [
{ name: "vendor", test: /node_modules|@react-refresh/, priority: 10 },
{ name: "shared-ui", test: /shared\.tsx$/, includeDependenciesRecursively: false },
],
},
},
},
},
server: { host: "127.0.0.1", port: 0 },
});
await server.listen();
for (const [file, code] of await bundled) {
const target = NodePath.join(output, file);
await NodeFSP.mkdir(NodePath.dirname(target), { recursive: true });
await NodeFSP.writeFile(target, code);
}
// Run the actual generated ES modules so their import order and refresh
// checks execute. These stubs replace only the browser and HMR transport.
const runner = NodePath.join(output, "check.mjs");
await NodeFSP.writeFile(
runner,
`import assert from "node:assert/strict";
const started = Promise.withResolvers();
globalThis.window = globalThis;
globalThis.document = {
createElement: () => ({ relList: { supports: () => true } }),
getElementById: () => null,
};
globalThis.__rolldown_runtime__ = {
registerGraph() {},
registerModule() {},
createModuleHotContext: () => ({ accept() {} }),
};
globalThis.onStarted = started.resolve;
console.error = (_message, error) => started.reject(error);
await import("./assets/index.js");
const element = await started.promise;
assert.equal(element.props.children, "ready");
assert.equal(typeof window.$RefreshReg$, "function");
console.log("App started with React refresh ready.");`,
);
const result = await execFile("node", [runner]);
expect(result.stdout).toContain("App started with React refresh ready.");
} finally {
await server?.close();
await NodeFSP.rm(root, { recursive: true, force: true });
}
});
it("hot updates Tailwind classes when a source file changes in bundled dev", async () => {
const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-tailwind-"));
const events = new NodeEvents.EventEmitter();
let server: Awaited<ReturnType<typeof createServer>> | undefined;
let socket: WebSocket | undefined;
let css = "";
try {
await NodeFSP.writeFile(
NodePath.join(root, "index.html"),
'<html><body><script type="module" src="/main.ts"></script></body></html>',
);
const source =
'import "./style.css"; export const margin = "m-[13px]"; import.meta.hot?.accept();';
await NodeFSP.writeFile(NodePath.join(root, "main.ts"), source);
await NodeFSP.writeFile(
NodePath.join(root, "style.css"),
'@import "tailwindcss" source(none); @source "./main.ts";',
);
const logger = createLogger("silent");
logger.error = (message) => events.emit("error", new Error(message));
const connected = NodeEvents.EventEmitter.once(events, "connected");
const ready = NodeEvents.EventEmitter.once(events, "ready");
server = await createServer({
configFile: false,
root,
customLogger: logger,
resolve: {
alias: {
tailwindcss: NodeURL.fileURLToPath(
new URL("../node_modules/tailwindcss/index.css", import.meta.url),
),
},
},
experimental: { bundledDev: true },
plugins: [
...tailwindPlugins(true),
{
name: "observe-tailwind-output",
enforce: "pre",
transform(code, id) {
if (id.endsWith("/style.css")) css = code;
},
async generateBundle() {
// Keep the build pending until the socket can receive Vite's ready message.
await connected;
},
},
],
server: { host: "127.0.0.1", port: 0 },
});
await server.listen();
const address = server.httpServer?.address();
if (!address || typeof address === "string") throw new Error("Vite did not bind a port");
server.ws.on("vite:client-connected", () => events.emit("connected"));
socket = new WebSocket(
`ws://127.0.0.1:${address.port}/?token=${server.config.webSocketToken}`,
"vite-hmr",
);
socket.addEventListener("open", () => {
socket?.send(
JSON.stringify({
type: "custom",
event: "vite:client-connected",
data: { clientId: "tailwind-test" },
}),
);
});
socket.addEventListener("message", ({ data }) => {
const message: unknown = JSON.parse(String(data));
if (message !== null && typeof message === "object" && "type" in message) {
// generateBundle runs before Vite stores the files for HTTP requests.
if (
message.type === "full-reload" &&
"ifFallback" in message &&
message.ifFallback === true
) {
events.emit("ready");
} else if (message.type === "bundled-dev-update") {
events.emit("updated");
}
}
});
await connected;
await ready;
const entry = await fetch(`http://127.0.0.1:${address.port}/assets/index.js`);
expect(entry.headers.get("content-type")).toContain("javascript");
await entry.text();
const updated = NodeEvents.EventEmitter.once(events, "updated");
await NodeFSP.writeFile(NodePath.join(root, "main.ts"), source.replace("13px", "137px"));
await updated;
expect(css).toContain("margin: 137px");
} finally {
socket?.close();
await server?.close();
await NodeFSP.rm(root, { recursive: true, force: true });
}
});