diff --git a/packages/void/src/index.ts b/packages/void/src/index.ts index a4771a181..b5335a783 100644 --- a/packages/void/src/index.ts +++ b/packages/void/src/index.ts @@ -396,6 +396,25 @@ function createDefaultWorkerEnvironmentPlugin(): Plugin { }; } +function createWorkerEnvironmentSanitizerPlugin(environmentNames: ReadonlyArray): Plugin { + const names = new Set(environmentNames); + return { + name: 'void:worker-environment-sanitizer', + enforce: 'pre', + configResolved(config) { + for (const name of names) { + const resolve = config.environments[name]?.resolve; + if (resolve && 'external' in resolve) { + // Test runners may seed Node externals into every Vite environment. + // Cloudflare Worker environments cannot externalize dependencies, but + // Cloudflare's validator expects the normalized array shape to remain. + resolve.external = []; + } + } + }, + }; +} + function createSandboxSdkResolvePlugin(): Plugin { let resolved: string | undefined; @@ -1370,6 +1389,10 @@ export function voidPlugin(options?: { return [ createCloudflareBuiltinsExternalPlugin(), + createWorkerEnvironmentSanitizerPlugin([ + viteEnvironment.name, + ...(viteEnvironment.childEnvironments ?? []), + ]), createWranglerCompatPersistPlugin(root, config), restartPlugin(), bindingWatchPlugin, @@ -1500,6 +1523,7 @@ export function voidPlugin(options?: { return [ createCloudflareBuiltinsExternalPlugin(), createDefaultWorkerEnvironmentPlugin(), + createWorkerEnvironmentSanitizerPlugin([DEFAULT_WORKER_ENVIRONMENT_NAME]), createWranglerCompatPersistPlugin(root, config), restartPlugin(), bindingWatchPlugin, diff --git a/packages/void/test/unit/plugin-vite-environment.test.ts b/packages/void/test/unit/plugin-vite-environment.test.ts index 3c20eb107..ae2150333 100644 --- a/packages/void/test/unit/plugin-vite-environment.test.ts +++ b/packages/void/test/unit/plugin-vite-environment.test.ts @@ -79,4 +79,70 @@ describe('voidPlugin Vite environments', () => { }, }); }); + + it('clears resolve.external from the worker environment configured by test runners', async () => { + const root = createProjectRoot(); + vi.spyOn(process, 'cwd').mockReturnValue(root); + + const config = await resolveConfig( + { + root, + plugins: [voidPlugin()], + environments: { + void_worker: { + resolve: { + external: ['node:fs'], + conditions: ['workerd'], + }, + build: { + emptyOutDir: false, + }, + }, + }, + }, + 'serve', + 'test', + ); + + expect(config.environments.void_worker.resolve.external).toEqual([]); + expect(config.environments.void_worker.resolve.conditions).toContain('workerd'); + expect(config.environments.void_worker.build).toMatchObject({ + emptyOutDir: false, + outDir: 'dist/ssr', + }); + }); + + it('clears resolve.external from class A framework worker environments', async () => { + const root = createProjectRoot(); + vi.spyOn(process, 'cwd').mockReturnValue(root); + writeFileSync( + join(root, 'package.json'), + JSON.stringify({ + type: 'module', + dependencies: { + '@tanstack/react-start': '^1.0.0', + }, + }), + ); + + const config = await resolveConfig( + { + root, + plugins: [voidPlugin()], + environments: { + ssr: { + resolve: { + external: ['node:fs'], + conditions: ['workerd'], + }, + }, + }, + }, + 'serve', + 'test', + ); + + expect(config.environments.ssr.resolve.external).toEqual([]); + expect(config.environments.ssr.resolve.conditions).toContain('workerd'); + }); });