Files
2026-09-14 10:32:43 +01:00

189 lines
6.3 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createServer, type ViteDevServer } from "vite-plus";
import { describe, expect, it } from "vite-plus/test";
import { resolveRuntimeEntryModule } from "../packages/vinext/src/entries/runtime-entry-module.js";
import vinext, { type VinextOptions } from "../packages/vinext/src/index.js";
async function loadVirtualModule(
root: string,
id: string,
options: {
cache?: VinextOptions["cache"];
hostPluginName?: string;
} = {},
): Promise<string> {
let server: ViteDevServer | undefined;
try {
server = await createServer({
root,
configFile: false,
plugins: [
vinext({ cache: options.cache }),
...(options.hostPluginName ? [{ name: options.hostPluginName }] : []),
],
server: { port: 0 },
logLevel: "silent",
});
const resolved = await server.pluginContainer.resolveId(id);
expect(resolved?.id).toBe(`\0${id}`);
const loaded = await server.pluginContainer.load(resolved!.id);
return typeof loaded === "string" ? loaded : ((loaded as { code?: string })?.code ?? "");
} finally {
await server?.close();
}
}
function loadUnifiedFetchHandler(
root: string,
options: Parameters<typeof loadVirtualModule>[2] = {},
): Promise<string> {
return loadVirtualModule(root, "virtual:vinext-worker-entry", options);
}
describe("unified Worker fetch handler", () => {
it("delegates App Router apps to the App Router worker entry", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "vinext-fetch-handler-app-"));
try {
fs.mkdirSync(path.join(root, "app"), { recursive: true });
fs.writeFileSync(
path.join(root, "app/page.tsx"),
"export default function Page() { return <div>app</div>; }\n",
);
await expect(loadUnifiedFetchHandler(root)).resolves.toBe(
'export { default } from "vinext/server/app-router-entry";',
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
it("delegates Pages Router apps to the Pages Router worker entry", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "vinext-fetch-handler-pages-"));
try {
fs.mkdirSync(path.join(root, "pages"), { recursive: true });
fs.writeFileSync(
path.join(root, "pages/index.tsx"),
"export default function Page() { return <div>pages</div>; }\n",
);
await expect(loadUnifiedFetchHandler(root)).resolves.toBe(
'export { default } from "vinext/server/pages-router-entry";',
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
it("keeps adapter-owned multi-stage output out of the dev server", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "vinext-fetch-handler-stages-"));
try {
fs.mkdirSync(path.join(root, "app"), { recursive: true });
fs.writeFileSync(
path.join(root, "app/page.tsx"),
"export default function Page() { return <div>app</div>; }\n",
);
const entry = "/adapter/stage-gateway.js";
const cache: VinextOptions["cache"] = {
cdn: {
adapter: "/adapter/cache.js",
output: {
entry,
matchesBuild: ({ plugins }) =>
plugins.some(({ name }) => name === "independent-stage-host"),
type: "multi-stage",
},
},
};
await expect(
loadUnifiedFetchHandler(root, { cache, hostPluginName: "independent-stage-host" }),
).resolves.toBe('export { default } from "vinext/server/app-router-entry";');
await expect(loadUnifiedFetchHandler(root, { cache })).resolves.toBe(
'export { default } from "vinext/server/app-router-entry";',
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
it.each(["app-router-entry", "pages-router-entry"])(
"keeps a direct %s main on its ordinary dev entry",
async (entryName) => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "vinext-direct-router-stages-"));
let server: ViteDevServer | undefined;
try {
fs.mkdirSync(path.join(root, "app"), { recursive: true });
fs.writeFileSync(
path.join(root, "app/page.tsx"),
"export default function Page() { return <div>app</div>; }\n",
);
server = await createServer({
root,
configFile: false,
plugins: [
vinext({
cache: {
cdn: {
adapter: "/adapter/cache.js",
output: { entry: "/adapter/stage-gateway.js", type: "multi-stage" },
},
},
}),
],
server: { port: 0 },
logLevel: "silent",
});
await expect(
server.pluginContainer.resolveId(`vinext/server/${entryName}`),
).resolves.not.toMatchObject({ id: "\0virtual:vinext-worker-entry" });
} finally {
await server?.close();
fs.rmSync(root, { recursive: true, force: true });
}
},
);
it.each([
[
"App",
"app",
"app/page.tsx",
"export default function Page() { return <div>app</div>; }\n",
"app-request-stage-independent-entry",
"app-response-stage-entry",
],
[
"Pages",
"pages",
"pages/index.tsx",
"export default function Page() { return <div>pages</div>; }\n",
"pages-request-stage-entry",
"pages-response-stage-entry",
],
])(
"exposes %s request and response stages as independent virtual entries",
async (_router, directory, file, source, requestEntry, responseEntry) => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), `vinext-${directory}-stages-`));
try {
fs.mkdirSync(path.join(root, directory), { recursive: true });
fs.writeFileSync(path.join(root, file), source);
await expect(loadVirtualModule(root, "virtual:vinext-request-stage")).resolves.toBe(
`export { handleRequestStage } from ${JSON.stringify(resolveRuntimeEntryModule(requestEntry))};\n`,
);
await expect(loadVirtualModule(root, "virtual:vinext-response-stage")).resolves.toBe(
`export * from ${JSON.stringify(resolveRuntimeEntryModule(responseEntry))};\n`,
);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
},
);
});