Skip to content

fix: resolve named CommonJS imports in dev-server bundling ("No matching export")#84

Open
theoryeffects wants to merge 1 commit into
native-federation:mainfrom
theoryeffects:fix/dev-server-named-commonjs-imports
Open

fix: resolve named CommonJS imports in dev-server bundling ("No matching export")#84
theoryeffects wants to merge 1 commit into
native-federation:mainfrom
theoryeffects:fix/dev-server-named-commonjs-imports

Conversation

@theoryeffects

@theoryeffects theoryeffects commented Jul 10, 2026

Copy link
Copy Markdown

Fixes #83.

Problem

On the Angular 22 line, ng serve fails while bundling external npm packages when a shared library imports a named export from a CommonJS package — e.g. import dayjs, { isDayjs } from 'dayjs':

X [ERROR] No matching export in "node_modules/dayjs/dayjs.min.js" for import "isDayjs"

The production build (ng build) of the same workspace succeeds. It also reproduces with, for example, quill importing { Op, AttributeMap } from quill-delta.

Root cause

In src/tools/esbuild/node-modules-bundler.ts, advancedOptimizations = !dev, so it is false during ng serve. The angular-linker plugin's onLoad then early-returns for any file that does not need Angular linking:

if (!needsLinking && !advancedOptimizations) {
  return null;
}

That early return skips jsTransformer.transformData, which is what performs the CommonJS→ESM interop that synthesizes named exports. So in dev, CommonJS vendor files reach esbuild untransformed and esbuild cannot resolve { named } imports from them. In production advancedOptimizations is true, every file is transformed, and the named exports resolve — which is why ng build works and ng serve does not.

In short: CommonJS-interop correctness is currently coupled to the advancedOptimizations optimization level.

Fix

Keep the dev fast-path for genuine ESM vendor files, but do not skip CommonJS files — they still need the interop transform. This preserves fast dev builds (only CommonJS modules pay the transform cost) instead of forcing advancedOptimizations on in dev:

const maybeCommonjs = /\bmodule\.exports\b|\bexports\.[A-Za-z_$]/.test(contents);

if (!needsLinking && !advancedOptimizations && !maybeCommonjs) {
  return null;
}

The CommonJS check is a lightweight heuristic; happy to switch it for a more robust detector if you would prefer.

Reproduction

  1. A Native Federation v22 host that shares a library doing import def, { named } from '<cjs-pkg>' where <cjs-pkg>'s main entry is CommonJS (e.g. dayjs).
  2. ng build → succeeds.
  3. ng serveX [ERROR] No matching export in "node_modules/<cjs-pkg>/..." for import "named".

Verification

  • pnpm build and pnpm lint pass; the changed file introduces no new lint findings.
  • This change (applied as a patch-package patch of the same logic) has been running in a real Angular 22 host: ng serve bundles the previously failing CommonJS dependencies without error, and the production build is unchanged.

Note

@chialab/esbuild-plugin-commonjs is already registered in the same plugin chain but does not resolve these named imports in dev. It may be worth confirming whether it is expected to cover this case; regardless, decoupling the interop from advancedOptimizations fixes the dev/prod discrepancy.

The node_modules bundler skips JavaScriptTransformer for non-linker files when advancedOptimizations is false (i.e. during `ng serve`). That transform provides the CommonJS->ESM interop that synthesizes named exports, so skipping it makes `import def, { named } from '<cjs-pkg>'` fail with "No matching export" in dev, while `ng build` (advancedOptimizations=true, transforms every file) works. Keep the dev fast-path for genuine ESM vendor files but not for CommonJS ones, so CJS interop no longer depends on the optimization level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

ng serve fails with "No matching export" for named CommonJS imports in shared deps (ng build works)

1 participant