diff --git a/.changeset/vitest-plugin-require-condition.md b/.changeset/vitest-plugin-require-condition.md new file mode 100644 index 00000000000..d5f5c0ba3d7 --- /dev/null +++ b/.changeset/vitest-plugin-require-condition.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/vitest-plugin": patch +--- + +Fix Worker tests failing when CommonJS dependencies require packages with separate `import` and `require` entries on Vite 8. The fix needs Vite 8.2.1 or later; Vite 8.0.x, 8.1.x and 8.2.0 stay affected. diff --git a/packages/vitest-plugin/src/pool/module-fallback.ts b/packages/vitest-plugin/src/pool/module-fallback.ts index 641a286d9f4..e39875f7fda 100644 --- a/packages/vitest-plugin/src/pool/module-fallback.ts +++ b/packages/vitest-plugin/src/pool/module-fallback.ts @@ -328,11 +328,17 @@ async function viteResolve( referrer: string, isRequire: boolean ): Promise { - const resolved = await vite.pluginContainer.resolveId(specifier, referrer, { - ssr: true, - // https://github.com/vitejs/vite/blob/v5.1.4/packages/vite/src/node/plugins/resolve.ts#L178-L179 - custom: { "node-resolve": { isRequire } }, - }); + const resolved = await vite.environments.ssr.pluginContainer.resolveId( + specifier, + referrer, + { + // Vite 8.2.1, and later, picks the `require` or `import` export condition from `kind` + kind: isRequire ? "require-call" : "import-statement", + // Vite 7 and earlier read `isRequire` from here instead + // https://github.com/vitejs/vite/blob/v5.1.4/packages/vite/src/node/plugins/resolve.ts#L178-L179 + custom: { "node-resolve": { isRequire } }, + } + ); if (resolved === null) { // Vite's resolution algorithm doesn't apply Node resolution to specifiers // starting with a dot. Unfortunately, the `@prisma/client` package includes diff --git a/packages/vitest-plugin/test/cjs-require.test.ts b/packages/vitest-plugin/test/cjs-require.test.ts index 6a9d50748a7..58593ca550b 100644 --- a/packages/vitest-plugin/test/cjs-require.test.ts +++ b/packages/vitest-plugin/test/cjs-require.test.ts @@ -32,3 +32,48 @@ test( expect(await result.exitCode).toBe(0); } ); + +test( + "resolves requires from CJS modules to the require export condition", + { timeout: 45_000 }, + async ({ expect, seed, vitestRun }) => { + await seed({ + "vitest.config.mts": vitestConfig(), + "node_modules/cjs-demo/package.json": JSON.stringify({ + name: "cjs-demo", + version: "1.0.0", + main: "index.cjs", + }), + "node_modules/cjs-demo/index.cjs": dedent` + module.exports = require("dual-demo"); + `, + "node_modules/dual-demo/package.json": JSON.stringify({ + name: "dual-demo", + version: "1.0.0", + exports: { + ".": { + import: "./index.mjs", + require: "./index.cjs", + }, + }, + }), + "node_modules/dual-demo/index.mjs": dedent` + export const format = "esm"; + `, + "node_modules/dual-demo/index.cjs": dedent` + module.exports = { format: "cjs" }; + `, + "index.test.ts": dedent` + import { expect, it } from "vitest"; + import demo from "cjs-demo"; + + it("loads the CJS entry of a dual-format package", () => { + expect(demo.format).toBe("cjs"); + }); + `, + }); + + const result = await vitestRun(); + expect(await result.exitCode).toBe(0); + } +); diff --git a/packages/vitest-plugin/test/module-fallback.test.ts b/packages/vitest-plugin/test/module-fallback.test.ts index 5e87ceb4837..29ef789a5ce 100644 --- a/packages/vitest-plugin/test/module-fallback.test.ts +++ b/packages/vitest-plugin/test/module-fallback.test.ts @@ -17,13 +17,18 @@ import { } from "../src/shared/module-path"; import type { Vite } from "vitest/node"; -// The fallback handler only reads `vite.pluginContainer.resolveId`, and only -// when a specifier can't be resolved directly from the filesystem. Returning -// `null` mimics Vite failing to resolve, exercising the 404 fall-through. +// The fallback handler only reads +// `vite.environments.ssr.pluginContainer.resolveId`, and only when a specifier +// can't be resolved directly from the filesystem. Returning `null` mimics Vite +// failing to resolve, exercising the 404 fall-through. function fakeVite(): Vite.ViteDevServer { return { - pluginContainer: { - resolveId: async () => null, + environments: { + ssr: { + pluginContainer: { + resolveId: async () => null, + }, + }, }, } as unknown as Vite.ViteDevServer; } @@ -37,8 +42,12 @@ function fakeVite(): Vite.ViteDevServer { // same rooted path that branch would have produced reaches the same place. function fakeViteResolvingTo(id: string): Vite.ViteDevServer { return { - pluginContainer: { - resolveId: async () => ({ id }), + environments: { + ssr: { + pluginContainer: { + resolveId: async () => ({ id }), + }, + }, }, } as unknown as Vite.ViteDevServer; } @@ -48,10 +57,14 @@ function fakeViteResolving( resolvedIds: Record ): Vite.ViteDevServer { return { - pluginContainer: { - resolveId: async (specifier: string) => { - const id = resolvedIds[specifier]; - return id === undefined ? null : { id }; + environments: { + ssr: { + pluginContainer: { + resolveId: async (specifier: string) => { + const id = resolvedIds[specifier]; + return id === undefined ? null : { id }; + }, + }, }, }, } as unknown as Vite.ViteDevServer;