Skip to content

fix(vite): respect package sideEffects when marking top-level calls pure - #596

Open
edbzn wants to merge 1 commit into
voidzero-dev:mainfrom
edbzn:fix/optimizer-respect-side-effects
Open

edbzn wants to merge 1 commit into
voidzero-dev:mainfrom
edbzn:fix/optimizer-respect-side-effects

Conversation

@edbzn

@edbzn edbzn commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #595.

In production builds, @oxc-angular/vite-optimizer ran optimizeAngularPackage(..., { markPure: true }) on every fesm20* file and ignored the package's sideEffects. Top-level calls that exist only for their side effects got /* @__PURE__ */ and were tree-shaken:

  • zone.js (no sideEffects field) bundled to nothing, so a zone-based app failed with NG0908 at bootstrap.
  • @angular/compiler ("sideEffects": ["./fesm2022/compiler.mjs"]) lost publishFacade(_global), so JIT failed with JIT compiler unavailable.

What changed

The optimizer plugin now passes markPure: true only for files under node_modules/@angular/ whose nearest package.json marks them side-effect-free. Everything else gets markPure: false. Metadata elision, static member wrapping and enum adjustment run as before.

sideEffects is read from the nearest package.json, cached per directory, and matched the way Vite's resolver matches it: a boolean as is, [] as side-effect-free, and array patterns through createFilter relative to the package directory, with **/ prepended to patterns that have no /. this.getModuleInfo(id).moduleSideEffects can't be used here: in a Vite 8.3.4 build it is null in the transform hook for zone.js, @angular/core and @angular/compiler.

How it matches the Angular CLI

In @angular/build (22.2), javascript-transformer-worker.js computes safeAngularPackage = sideEffects === false && /[\\/]node_modules[\\/]@angular[\\/]/.test(filename), and only safe packages get top-level pure annotations (topLevelSafeMode = !safeAngularPackage). sideEffects comes from esbuild's build.resolve(), which applies the package.json field. Metadata elision, static member wrapping and enum adjustment run for every file, so they stay ungated here too.

Two differences remain:

  • Non-Angular packages that declare "sideEffects": false (e.g. @ngrx/store) no longer get top-level pure annotations, as in the Angular CLI. Those modules are still dropped whole when unused.
  • In safe mode, the Angular CLI still annotates new on a short list of known side-effect-free constructors. This PR doesn't port that.

Verification

  • New test/build-optimizer.test.ts drives the plugin on fixture packages written to a temp dir. Cases: a package without sideEffects, sideEffects: false, an array that matches the file, an array that doesn't, a pattern with no /, [], a side-effect-free non-@angular package, and static member wrapping in a side-effectful package. 4 of the 8 tests fail on main; all pass with the fix.
  • pnpm test (455 passed), pnpm check, tsc -p tsconfig.json.
  • Real vite build (Vite 8.3.4, Angular 22.2.0, zone.js 0.16.0) of an app that imports zone.js and @angular/compiler and uses provideZoneChangeDetection():
    • With @oxc-angular/vite@0.0.42, the bundle has no loadZone()/patchCommon()/publishFacade() calls, and the app throws NG0908 at bootstrap.
    • With this branch, all of those calls are in the bundle, a setTimeout in the component runs in the angular zone and updates the view, and ng.ɵcompilerFacade is set.
  • A zoneless app (@angular/core + @angular/platform-browser only) builds to a byte-identical bundle with 0.0.42 and with this branch.

🤖 Generated with Claude Code

… pure

The build optimizer annotated top-level calls `/* @__PURE__ */` in every
`fesm20*` file, so production builds tree-shook side-effect-only calls:
zone.js bundled to nothing and `@angular/compiler` lost `publishFacade`.

Like the Angular CLI, only mark top-level calls pure in `@angular/*`
packages whose nearest `package.json` declares the file side-effect-free.
`sideEffects` arrays are matched as Vite matches them. Metadata elision,
static member wrapping and enum adjustment are unchanged.

Fixes voidzero-dev#595

Co-Authored-By: Claude Opus 5.5 (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.

optimizer: markPure ignores package sideEffects, tree-shakes zone.js and @angular/compiler in production builds

1 participant