Workers SDK Issue Reports

← Back to Dashboard

#7157 Yarn PnP Compatibility Error with @cloudflare/vitest-pool-workers

Recommendation:KEEP OPEN
Difficulty:medium
Reasoning:

Yarn PnP virtual filesystem not supported. Module fallback uses fs.existsSync/readFileSync which fail for PnP zip archives. No PRs or fixes address this.

Suggested Action:

Keep open. Add PnP support via Node's findPnpApi in module-fallback.ts.

Analysis Report

Issue Review: cloudflare/workers-sdk#7157

Summary

Yarn PnP (Plug'n'Play) is incompatible with @cloudflare/vitest-pool-workers due to module resolution that assumes traditional node_modules directory structure.

Findings

  • Created: 2024-11-02
  • Updated: 2025-10-30
  • Version: 0.5.24 (reported) -> 0.12.6 (current)
  • Component: vitest-pool-workers
  • Labels: bug
  • Comments: 0

Key Evidence

  1. No PRs or changelog entries reference this issue - searched for #7157, "yarn pnp", "PnP vitest-pool-workers" with no results

  2. No PnP-specific handling in codebase - grep of module-fallback.ts and config/index.ts shows no PnP/Yarn handling

  3. Historical context - Issue #420 (closed 2022) documented that Wrangler does not support Yarn PnP, with workaround to use nodeLinker: node-modules

  4. Root cause identified in source code:

    • module-fallback.ts:258-281 uses Vite's pluginContainer.resolveId() for module resolution
    • module-fallback.ts:269-271 falls back to Node's require.resolve() for relative specifiers
    • Neither mechanism properly handles PnP's virtual filesystem where modules exist in .yarn/cache/*.zip files
    • The error message shows Vite trying to load from physical path /Users/xxx/@cloudflare/vitest-pool-workers which doesn't exist in PnP mode
  5. Error analysis: The error Failed to load url /path/to/project/@cloudflare/vitest-pool-workers indicates:

    • Vite's loadAndTransform function (vite-npm-5.4.10 in error stack) receives a filesystem path
    • In PnP mode, packages are stored in .yarn/berry/cache/*.zip archives, not in physical directories
    • The pool's module fallback service returns paths that PnP's virtual filesystem cannot resolve

Recommendation

Status: KEEP OPEN

Reasoning: This is a legitimate, unresolved compatibility issue. Yarn PnP represents a significant portion of the JavaScript ecosystem (~15% of projects per npm/yarn surveys). The issue has clear reproduction steps, a known workaround, and the root cause is identifiable in the codebase. No fixes have been merged since the issue was opened.

Action: Keep open and consider prioritizing based on user demand. The workaround (nodeLinker: node-modules or nodeLinker: pnpm) is documented and functional.


Root Cause Analysis

The issue stems from the module fallback service in vitest-pool-workers not supporting Yarn PnP's virtual filesystem.

Code References

Primary location: packages/vitest-pool-workers/src/pool/module-fallback.ts

  1. viteResolve() function (lines 244-295):
async function viteResolve(
	vite: ViteDevServer,
	specifier: string,
	referrer: string,
	isRequire: boolean
): Promise<string> {
	const resolved = await vite.pluginContainer.resolveId(specifier, referrer, {
		ssr: true,
		custom: { "node-resolve": { isRequire } },
	});
	// ... handles browser external and node: builtins
	
	// For require() of relative specifiers, falls back to Node resolution:
	if (isRequire && specifier[0] === ".") {
		return require.resolve(specifier, { paths: [referrer] });
	}
	// ...
}

The vite.pluginContainer.resolveId() call doesn't automatically use PnP resolution unless Vite is configured with PnP support. The fallback to require.resolve() also doesn't work because it's called with paths that don't exist on disk in PnP mode.

  1. maybeGetTargetFilePath() function (lines 209-221):
function maybeGetTargetFilePath(target: string): string | undefined {
	if (isFile(target)) {
		return target;
	}
	for (const extension of jsExtensions) {
		const targetWithExtension = target + extension;
		if (fs.existsSync(targetWithExtension)) {
			return targetWithExtension;
		}
	}
	// ...
}

This uses fs.existsSync() which returns false for PnP virtual paths.

  1. Pool index initialization (packages/vitest-pool-workers/src/pool/index.ts:675):
service = handleModuleFallbackRequest.bind(undefined, ctx.vitenode.server);

The fallback service is bound to Vite's dev server but doesn't account for PnP resolution.

Why PnP Fails

In Yarn PnP:

  • Packages are stored in .yarn/cache/ as zip files
  • Module resolution uses .pnp.cjs which patches Node's resolution
  • File paths like /project/@cloudflare/vitest-pool-workers don't physically exist
  • Standard fs.existsSync() and fs.readFileSync() calls fail without PnP's patched require

Proposed Solution

There are two potential approaches:

Option A: Add PnP detection and use yarn-pnp-plugin (Recommended)

Add support for Vite's built-in PnP handling by ensuring the PnP plugin is loaded:

// In module-fallback.ts or config/index.ts

import { findPnpApi } from 'module';

function isPnPEnabled(): boolean {
  try {
    return findPnpApi(__filename) !== null;
  } catch {
    return false;
  }
}

// In viteResolve or resolve function:
async function viteResolve(
  vite: ViteDevServer,
  specifier: string,
  referrer: string,
  isRequire: boolean
): Promise<string> {
  // For PnP environments, use the PnP API directly for resolution
  if (isPnPEnabled()) {
    const pnpApi = findPnpApi(referrer);
    if (pnpApi) {
      try {
        const resolved = pnpApi.resolveRequest(specifier, referrer);
        if (resolved) {
          return resolved;
        }
      } catch {
        // Fall through to standard resolution
      }
    }
  }
  
  // Existing resolution logic...
  const resolved = await vite.pluginContainer.resolveId(specifier, referrer, {
    ssr: true,
    custom: { "node-resolve": { isRequire } },
  });
  // ...
}

Option B: Document requirement for vite-plugin-yarn-pnp

Alternatively, require users to add PnP support via Vite config:

// vitest.config.ts
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
import { VitePluginYarnPnp } from "vite-plugin-yarn-pnp";

export default defineWorkersConfig({
  plugins: [VitePluginYarnPnp()],
  // ... rest of config
});

Recommended Changes

Files to modify:

  1. packages/vitest-pool-workers/src/pool/module-fallback.ts

    • Add PnP detection using findPnpApi from Node's module builtin
    • Add PnP-aware resolution fallback before standard resolution
    • Update file existence checks to use PnP API when available
  2. packages/vitest-pool-workers/src/config/index.ts

    • Optionally: Auto-detect PnP and configure Vite accordingly
    • Add PnP-related resolve conditions if needed
  3. packages/vitest-pool-workers/package.json

    • No new dependencies needed (findPnpApi is a Node.js builtin since v18.1.0)

Implementation Difficulty

Difficulty: Medium

Justification:

  • Pro: Node.js provides built-in PnP detection via findPnpApi
  • Pro: The resolution logic is centralized in module-fallback.ts
  • Pro: Clear error message pointing to the issue location
  • Con: Need to understand both Vite's plugin resolution and PnP's resolution API
  • Con: Testing requires setting up a Yarn PnP environment
  • Con: Edge cases around virtual modules, zip archive access, and workerd's module system
  • Con: May need changes to how file contents are read (PnP resolves to zip entries)

Testing Recommendations

  1. Unit Tests:

    • Mock findPnpApi to return a PnP API object
    • Verify viteResolve uses PnP resolution when available
    • Test fallback behavior when PnP resolution fails
  2. Integration Tests:

    • Create a test fixture with Yarn PnP enabled (nodeLinker: pnp in .yarnrc.yml)
    • Verify basic test execution works
    • Test importing npm packages from tests
    • Test importing local modules with relative paths
  3. E2E Test:

  4. Regression Tests:

    • Ensure existing tests still pass with standard node_modules
    • Test with pnpm's node_modules structure
    • Test with npm's node_modules structure

Suggested Comment

Thank you for reporting this issue. After investigation, we've confirmed that @cloudflare/vitest-pool-workers does not currently support Yarn PnP's virtual filesystem due to how module resolution is implemented.

Current Workaround: As you've discovered, setting nodeLinker: node-modules or nodeLinker: pnpm in your .yarnrc.yml will resolve the issue.

Root Cause: The pool's module fallback service uses filesystem operations (fs.existsSync, fs.readFileSync) that don't work with PnP's virtual modules stored in zip archives. Supporting PnP would require using Node's findPnpApi for resolution.

We're keeping this issue open as a feature request. If you'd like to contribute a fix, the main changes would be in packages/vitest-pool-workers/src/pool/module-fallback.ts.

Notes & Feedback (0)

No notes yet.

Add Note