← All tasks
javascriptangular/angular-cli #33774Not a task: not reproduced

Angular 22.1: sourcemap builds silently switched from TypeScript emit to esbuild transpilation, changing production output (no opt-out)

envgap__angular__angular-cli-33774

01 / FAILURE SIGNATURE

As reported upstream

Under 22.0.7 (tsc emit) this was correct. Under 22.1 (esbuild transpilation) esbuild drops the `fuse.js` import while keeping `new Fuse(...)`, so the page throws `ReferenceError: Fuse is not defined`. I've filed the underlying esbuild bug separately: evanw/esbuild#4507.
Not a benchmark task.
  • In a clean container the reported failure did not reproduce, or the known fix did not make the project run.

02 / ENVIRONMENT RECIPE

Base commit
587accb683dacc2adc910437b821dad4b060479a
Manifest
package.json
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / ORIGINAL ISSUE TEXT

angular/angular-cli #33774 · read the original issue
## Which @angular/* package(s) are the source of the bug?

build

## Is this a regression?

Yes — this worked in `@angular/build` 22.0.7 and broke in 22.1.0.

## Description

`09d0a11a8` ("perf(@angular/build): enable fast-path AST printing with sourcemaps in AotCompilation") changed how `useTypeScriptTranspilation` is computed.

**22.0.7** — `aot-compilation.js`:
```js
const useTypeScriptTranspilation =
  !compilerOptions.isolatedModules ||
  !!compilerOptions.sourceMap ||
  !!compilerOptions.inlineSourceMap;
```

**22.1.x** — `aot-compilation.js` + `compiler-plugin.js`:
```js
const useTypeScriptTranspilation =
  compilerOptions['_useTypeScriptTranspilation'] ?? !compilerOptions.isolatedModules;
// _useTypeScriptTranspilation: !compilerOptions.isolatedModules || !!pluginOptions.instrumentForCoverage
```

The sourcemap condition is gone. For any project with `isolatedModules: true`, TS→JS emit moved from **TypeScript** to **esbuild**.

The commit message frames this as improving "rebuild performance for dev server builds", but the change is not scoped to the dev server — it applies to `ng build` production too, where sourcemaps are commonly enabled (`"sourceMap": { "hidden": true, "scripts": true }` is a normal production setting for uploading to an error tracker).

## The problem

esbuild's TypeScript transpilation is not semantically identical to `tsc`'s, and where it diverges the divergence surfaces as a **silent** miscompile rather than a build failure.

Concretely, this shipped a `ReferenceError` to our production app. A module contained:

```ts
import Fuse from 'fuse.js';
import FuseOptionKey = Fuse.FuseOptionKey;   // type-only alias

// ...
const fuse = new Fuse(items, { /* ... */ });
```

Under 22.0.7 (tsc emit) this was correct. Under 22.1 (esbuild transpilation) esbuild drops the `fuse.js` import while keeping `new Fuse(...)`, so the page throws `ReferenceError: Fuse is not defined`. I've filed the underlying esbuild bug separately: evanw/esbuild#4507.

I'm not arguing Angular should work around that specific esbuild bug. The issue here is the **change in blast radius**:

- The emit path for production builds changed as a side effect of a dev-server performance optimization.
- `isolatedModules` is normally the contract that says "each file can be transpiled independently". It is enforced by TypeScript, so it feels like a sufficient guarantee — but it only rejects constructs *TypeScript* knows are unsafe to emit per-file. `import X = Default.Y` is perfectly fine for `tsc`, so nothing flags it. `isolatedModules` therefore does not actually guarantee that a third-party transpiler will agree with `tsc`.
- There is **no supported opt-out**. `_useTypeScriptTranspilation` is internal and derived; the only lever a user has is turning off `isolatedModules` entirely, which is a large and unrelated change.
- The failure is invisible pre-merge: the build exits 0 with no warning, `tsc`/`ng build` type-check clean, and no unit or component test can observe it because every single-file compilation path (`ts.transpileModule`, full `ts.Program.emit`, `esbuild.transform`) emits correct code. Only the bundled artifact is wrong.

## Reproduction

`esbuild entry.ts --bundle` is enough to show the underlying miscompile (see evanw/esbuild#4507). To see it through Angular: any app with `isolatedModules: true` and sourcemaps enabled, containing a module that imports a package's default export and also declares `import Alias = ThatDefault.SomeType`. Build with 22.0.7 → import preserved. Build with 22.1.x → import dropped, `ReferenceError` at runtime.

## What I'd suggest

Any of these would have prevented this, roughly in order of preference:

1. **Expose a supported opt-out** (a builder option or a documented env var) to force TypeScript transpilation, so a project hitting a transpiler divergence has a same-day escape hatch that isn't "disable `isolatedModules`".
2. **Scope the fast path to the dev server**, which is where the commit's stated benefit ("rebuild performance") actually applies, and keep production on `tsc` emit where correctness matters more than rebuild speed.
3. If it stays on for production, **call it out in the release notes / migration guide** as a change of transpiler for production output, not just an internal perf tweak. It's currently indistinguishable from a no-op change from a consumer's point of view.

## Please provide the environment you discovered this bug in

```
Angular CLI: 22.1.2
Node: 24.16.0
Package Manager: pnpm 11.7.0
@angular/build: 22.1.2 (regressed) / 22.0.7 (working)
TypeScript: 6.0.3
esbuild: 0.28.1
```

Relevant tsconfig:
```jsonc
{
  "isolatedModules": true,
  "module": "preserve",
  "moduleResolution": "bundler",
  "target": "ES2022"
}
```
Production builder config includes `"sourceMap": { "hidden": true, "scripts": true, "styles": true }`.
Continue on GitHub ↗

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]