one
Version:
One is a new React Framework that makes Vite serve both native and web.
233 lines (232 loc) • 8.76 kB
JavaScript
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