Skip to content

fix(common): remove moduleResolution override in ts-node register (fixes #2025)#2187

Merged
just-jeb merged 1 commit intomasterfrom
fix/2025-ts-node-module-resolution
Apr 26, 2026
Merged

fix(common): remove moduleResolution override in ts-node register (fixes #2025)#2187
just-jeb merged 1 commit intomasterfrom
fix/2025-ts-node-module-resolution

Conversation

@just-jeb
Copy link
Copy Markdown
Owner

@just-jeb just-jeb commented Apr 25, 2026

Problem

When a user sets moduleResolution: "bundler" in their tsconfig.json, the custom-webpack builder was silently overriding it with "node" inside ts-node's register() call. This caused TypeScript to fail resolving packages that are only accessible via the package.json exports field (subpath exports), producing TS2307: Cannot find module errors.

Example: @angular/core/primitives/di has no physical file on disk — it is only accessible via the exports field. With moduleResolution: "bundler" it resolves fine; with "node" it does not.

Reported in #2025.

Root Cause

In packages/common/src/load-module.ts, _tsNodeRegister() passes compilerOptions.moduleResolution: 'node' to ts-node. This overrides whatever the user set in their tsconfig.json, regardless of their intent.

// Before (buggy)
loadTsNode().register({
  project: tsConfig,
  compilerOptions: {
    module: 'CommonJS',
    moduleResolution: 'node',  // <-- BUG: forces node resolution, ignoring user's tsconfig
    types: ['node'],
  },
});

Fix

Remove the moduleResolution override entirely. TypeScript explicitly allows moduleResolution: "bundler" together with module: "CommonJS" — the combination type-checks using bundler resolution semantics (respects exports field) while emitting CommonJS output for the Node.js process.

// After (fixed)
loadTsNode().register({
  project: tsConfig,
  compilerOptions: {
    module: 'CommonJS',   // kept — Angular CLI runs in a CJS process
    // moduleResolution not set — user's tsconfig value is preserved
    types: ['node'],     // kept — gives webpack configs access to Node globals
  },
});

Regression Test

Adds a new bundler-resolution-ts angular.json configuration in full-cycle-app that uses a TypeScript webpack config importing from @angular/core/primitives/di (a subpath export, no physical file). The integration test ts-config-bundler-module-resolution in integration.js verifies that ng build -c bundler-resolution-ts succeeds. This fails before the fix and passes after.

Previously attempted

An earlier iteration replaced ts-node with tsx. This was reverted because tsx uses esbuild for transpilation and skips TypeScript's type checker entirely — a regression for users who choose .ts config files specifically for type safety.

Fixes #2025

@just-jeb just-jeb force-pushed the fix/2025-ts-node-module-resolution branch 2 times, most recently from d698d04 to 46250df Compare April 25, 2026 16:38
@just-jeb just-jeb changed the title fix(common): replace ts-node with tsx for .ts config loading (fixes #2025) fix(common): remove moduleResolution override in ts-node register (fixes #2025) Apr 25, 2026
@just-jeb just-jeb force-pushed the fix/2025-ts-node-module-resolution branch from 46250df to a6c3d2e Compare April 25, 2026 16:45
 #2025)

Root cause: ts-node's register() was called with compilerOptions.moduleResolution='node',
which silently overrides the user's tsconfig setting. When a user sets moduleResolution
to 'bundler' (required by Angular's subpath exports like `@angular/core/primitives/di`),
TypeScript would still run with 'node' resolution — causing TS2307 errors because 'node'
resolution cannot find packages accessed only via the package.json exports field.

Fix: remove the moduleResolution override entirely. TypeScript explicitly allows
moduleResolution:bundler with module:CommonJS — this combination type-checks using
bundler semantics (respects package.json exports field) while still emitting CommonJS
output, which is what the Angular CLI CJS process requires. All other moduleResolution
values (node, node16, nodenext) also continue to work as before.

The remaining overrides are kept:
- module: 'CommonJS' — required, Angular CLI runs in a CJS process
- types: ['node'] — gives webpack configs access to Node.js globals

Adds integration test: a new 'bundler-resolution-ts' angular.json configuration in
full-cycle-app uses a TS webpack config that imports from `@angular/core/primitives/di`
— a subpath export with no physical file on disk. This only resolves with
moduleResolution:bundler, so it fails on the old code and passes on the fix.
The test is registered in integration.js so it runs automatically in CI.
@just-jeb just-jeb force-pushed the fix/2025-ts-node-module-resolution branch from a6c3d2e to b0d1451 Compare April 25, 2026 17:03
@just-jeb just-jeb merged commit a0daa6a into master Apr 26, 2026
45 checks passed
@just-jeb just-jeb deleted the fix/2025-ts-node-module-resolution branch April 26, 2026 13:35
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.

Silent tsconfig

1 participant