UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

233 lines (232 loc) 8.76 kB
import { Buffer } from "node:buffer"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { describe, expect, it, vi } from "vitest"; import { build } from "vite"; import { createRouteIndex } from "../../utils/routeIndex.mjs"; import { createVirtualEntry } from "./virtualEntryPlugin.mjs"; vi.mock("vxrn", () => ({ isNativeEnvironment: env => { return env?.name === "ios" || env?.name === "android"; } })); vi.mock("@vxrn/compiler", () => ({ configuration: { enableNativewind: false } })); function loadEntry(plugin, envName, command = "build", envMode = command === "serve" ? "dev" : "build") { plugin.configResolved?.({ root: "", command }); const ctx = { environment: { name: envName, mode: envMode } }; return plugin.load.handler.call(ctx, "\0virtual:one-entry"); } describe("virtualEntryPlugin", () => { it("keeps spa route matching without bundling the client module on the server", async () => { const root = mkdtempSync(join(tmpdir(), "one-spa-server-")); const appDir = join(root, "app"); mkdirSync(appDir); const spaRoute = join(appDir, "client-only+spa.tsx"); writeFileSync(spaRoute, `export const browserPayload = 'must-not-enter-server-graph' export default function ClientOnlyPage() { return null } `); try { const result = await build({ root, configFile: false, logLevel: "silent", plugins: [{ name: "test-one-entry", enforce: "pre", resolveId(id) { if (id === "one") return "\0test-one-entry"; }, load(id) { if (id === "\0test-one-entry") { return `export const createApp = (options) => options export const registerPreloadedRoute = () => {}`; } } }, createVirtualEntry({ root: "app", flags: {}, routeIndex: createRouteIndex({ routerRoot: appDir }) })], build: { ssr: true, write: false, rolldownOptions: { input: "virtual:one-entry" } } }); if (Array.isArray(result)) throw new Error("expected one server build output"); if (!("output" in result)) throw new Error("expected completed server build"); const moduleIds = result.output.flatMap(output => output.type === "chunk" ? output.moduleIds : []); expect(moduleIds).not.toContain(resolve(spaRoute)); expect(result.output.flatMap(output => output.type === "chunk" ? output.code : "").join("\n")).not.toContain("must-not-enter-server-graph"); const entry = result.output.find(output => output.type === "chunk" && output.isEntry); if (!entry || entry.type !== "chunk") throw new Error("missing server entry chunk"); const built = await import(`data:text/javascript;base64,${Buffer.from(entry.code).toString("base64")}`); expect(Object.keys(built.default.routes)).toContain("/app/client-only+spa.tsx"); await expect(built.default.routes["/app/client-only+spa.tsx"]()).resolves.toEqual({}); } finally { rmSync(root, { recursive: true, force: true }); } }); describe("setupFile", () => { const base = { root: "app", flags: {}, routeIndex: { getPaths: () => [], update: () => false } }; it("server (ssr) uses lazy dynamic import, not static import", () => { const plugin = createVirtualEntry({ ...base, setupFile: { client: "./src/setupClient.ts", server: "./src/setupServer.ts" } }); const code = loadEntry(plugin, "ssr"); expect(code).not.toContain('import "./src/setupServer.ts"'); expect(code).toContain("__oneGetSetupPromise = () => import("); expect(code).toContain("./src/setupServer.ts"); expect(code).toContain("getSetupPromise: __oneGetSetupPromise"); }); it("client uses dynamic import for setupFile", () => { const plugin = createVirtualEntry({ ...base, setupFile: { client: "./src/setupClient.ts", server: "./src/setupServer.ts" } }); const code = loadEntry(plugin, "client"); expect(code).toContain("__oneGetSetupPromise"); expect(code).toContain("import("); expect(code).toContain("./src/setupClient.ts"); expect(code).toContain("getSetupPromise: __oneGetSetupPromise"); expect(code).not.toContain('import "./src/setupClient.ts"'); }); it("native (ios) uses static import for setupFile", () => { const plugin = createVirtualEntry({ ...base, setupFile: { client: "./src/setupClient.ts", native: "./src/setupNative.ts" } }); const code = loadEntry(plugin, "ios"); expect(code).toContain('import "./src/setupNative.ts"'); expect(code).not.toContain("__oneGetSetupPromise"); expect(code).not.toContain("getSetupPromise"); }); it("android uses static import like ios", () => { const plugin = createVirtualEntry({ ...base, setupFile: { native: "./src/setupNative.ts" } }); const code = loadEntry(plugin, "android"); expect(code).toContain('import "./src/setupNative.ts"'); expect(code).not.toContain("__oneGetSetupPromise"); }); it("server (ssr) in dev mode uses static import for dep discovery", () => { const plugin = createVirtualEntry({ ...base, setupFile: { client: "./src/setupClient.ts", server: "./src/setupServer.ts" } }); const code = loadEntry(plugin, "ssr", "serve"); expect(code).toContain('import "./src/setupServer.ts"'); expect(code).not.toContain("__oneGetSetupPromise"); }); it("no setupFile produces no import", () => { const plugin = createVirtualEntry(base); const code = loadEntry(plugin, "ssr"); expect(code).not.toContain("setupServer"); expect(code).not.toContain("__oneGetSetupPromise"); expect(code).not.toContain("getSetupPromise"); }); it("string setupFile applies to all environments", () => { const plugin = createVirtualEntry({ ...base, setupFile: "./src/setup.ts" }); const ssrCode = loadEntry(plugin, "ssr"); const clientCode = loadEntry(plugin, "client"); const iosCode = loadEntry(plugin, "ios"); expect(ssrCode).toContain("./src/setup.ts"); expect(ssrCode).toContain("__oneGetSetupPromise"); expect(ssrCode).not.toContain('import "./src/setup.ts"'); expect(clientCode).toContain("./src/setup.ts"); expect(clientCode).toContain("__oneGetSetupPromise"); expect(clientCode).not.toContain('import "./src/setup.ts"'); expect(iosCode).toContain('import "./src/setup.ts"'); expect(iosCode).not.toContain("__oneGetSetupPromise"); }); it("passes router linking config to createApp", () => { const plugin = createVirtualEntry({ ...base, router: { linking: { scheme: "threepunchconvo", prefixes: ["threepunchconvo://app"] } } }); const code = loadEntry(plugin, "ios"); expect(code).toContain('linking: {"scheme":"threepunchconvo","prefixes":["threepunchconvo://app"]}'); }); }); describe("react-refresh preamble", () => { const base = { root: "app", flags: {}, routeIndex: { getPaths: () => [], update: () => false } }; it("dev client installs the preamble before route modules load", () => { const code = loadEntry(createVirtualEntry(base), "client", "serve"); expect(code).toContain(`from '/@react-refresh'`); expect(code).toContain("window.$RefreshReg$ = () => {}"); expect(code).toContain(`!window.$RefreshReg$`); expect(code.indexOf("__oneInjectRefresh(window)")).toBeLessThan(code.indexOf("createApp(")); }); it("production build emits no reference to the dev-only refresh runtime", () => { const code = loadEntry(createVirtualEntry(base), "client", "build"); expect(code).not.toContain("/@react-refresh"); expect(code).not.toContain("$RefreshReg$"); }); it("server and native entries never get the preamble", () => { for (const envName of ["ssr", "ios", "android"]) { const code = loadEntry(createVirtualEntry(base), envName, "serve"); expect(code).not.toContain("/@react-refresh"); expect(code).not.toContain("$RefreshReg$"); } }); }); }); //# sourceMappingURL=virtualEntryPlugin.test.mjs.map