Skip to content

Comments

Fix ESM resolver fallback for pnpm symlinked dependencies#207

Merged
infiton merged 1 commit intomainfrom
esm-resolver-fixes
Oct 15, 2025
Merged

Fix ESM resolver fallback for pnpm symlinked dependencies#207
infiton merged 1 commit intomainfrom
esm-resolver-fixes

Conversation

@infiton
Copy link
Contributor

@infiton infiton commented Oct 15, 2025

Fix ESM resolver fallback for pnpm symlinked dependencies

Problem

When using wds with ESM mode (esm: true) in pnpm workspaces, module resolution fails for symlinked peer dependencies. For example, trying to import graphql from within graphql-ws results in:

Error: Cannot find module 'graphql': graphql cannot be resolved in file:///Users/.../node_modules/.pnpm/graphql-ws@5.16.2_graphql@16.8.1/node_modules/graphql-ws/lib/server.mjs

Workaround: Setting esm: false resolves the issue because the CJS hook uses Node's native require() resolution which handles pnpm symlinks correctly.

Root Cause

The bug was in child-process-esm-loader.ts (lines 80-83). When oxc-resolver failed to resolve a module, it immediately threw an error instead of falling back to Node's built-in resolver:

if (resolved.error) {
  debugLog?.("esm custom resolver error", { specifier, parentURL, resolved, error: resolved.error });
  throw new Error(`${resolved.error}: ${specifier} cannot be resolved in ${context.parentURL}`);
}

This prevented the existing fallback logic (lines 103-129) from ever running.

Solution

Restructured the resolution logic to gracefully handle oxc-resolver failures:

  1. ✅ Try oxc-resolver first (for TypeScript extension aliasing)
  2. ✅ If oxc-resolver fails, log and continue to Node's native resolver (which handles pnpm symlinks)
  3. ✅ If Node's resolver fails, try CommonJS require as last resort

When using wds with ESM mode in pnpm workspaces, module resolution was
failing for symlinked peer dependencies (e.g., graphql from graphql-ws).
The oxc-resolver would fail to resolve these modules and immediately throw
an error, preventing fallback to Node's built-in resolver.

Changed the resolution logic to gracefully handle oxc-resolver failures by
allowing the code to continue to Node's native resolver, which properly
handles pnpm's symlink structure. The resolver now follows this cascade:

1. Try oxc-resolver (for TypeScript extension aliasing)
2. On failure, fall back to Node's native resolver (handles symlinks)
3. On failure, fall back to CommonJS require

This allows ESM mode to work correctly with pnpm workspaces while
maintaining the TypeScript resolution capabilities that oxc-resolver
provides.

Fixes issue where "Cannot find module 'graphql'" errors occurred despite
the module being properly installed in pnpm's .pnpm directory.
@infiton infiton merged commit e18abb7 into main Oct 15, 2025
5 checks passed
@infiton infiton deleted the esm-resolver-fixes branch October 15, 2025 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant