Repository navigation
fix(vite): respect package sideEffects when marking top-level calls pure - #596
Merged
Brooooooklyn merged 6 commits intoOct 22, 2026
Merged
Conversation
… 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>
A package.json that fails to parse (or starts with a BOM) used to throw inside the transform handler, which skipped every optimizer pass for the file and was never cached. Now the read is wrapped, a leading BOM is stripped, non-string sideEffects entries are ignored, and an unreadable package.json counts as having side effects, so only markPure is turned off and the result is cached. Also fix the resolver doc: the nearest package.json lookup follows esbuild / the Angular CLI, only the pattern matching follows Vite. Tests: - add an @angular package without a sideEffects field (the zone.js case never reached that branch because zone.js is not under @angular/) - add BOM, invalid JSON and mixed-array package.json cases - reuse one plugin across packages to cover the per-plugin cache - pass forward-slash ids like Vite does, read fixture code from disk, drop the unused service.mjs fixture and realpathSync Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… bad ones The mixed sideEffects test only optimized the unlisted file, so a version that dropped the whole array on any non-string entry still passed. It now also optimizes the listed file and expects no pure marks there. Fix the JSDoc of the sideEffects resolver: the matching follows esbuild (the Angular CLI), not the Vite 8 build, which uses rolldown and differs for string values and for patterns like './x.mjs'. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The boolean sideEffects branch was only tested with `false`. The only `sideEffects: true` fixture was outside @angular, so the @angular check already turned markPure off there. Add an @angular fixture with `sideEffects: true` and check that its top-level calls stay unmarked. Also reword the markPure comment so it says pure annotations are added (not already present), and wrap it and the JSDoc bullet to 100 columns. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…son fixtures writePackage only reads its sideEffects argument to build the default package.json. The @angular/bom and @angular/broken fixtures pass a raw package.json, so their `false` argument was never used and read as if it set the field. Pass undefined there and note the rule on the helper. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Member
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #595.
In production builds,
@oxc-angular/vite-optimizerranoptimizeAngularPackage(..., { markPure: true })on everyfesm20*file and ignored the package'ssideEffects. Top-level calls that exist only for their side effects got/* @__PURE__ */and were tree-shaken:zone.js(nosideEffectsfield) bundled to nothing, so a zone-based app failed withNG0908at bootstrap.@angular/compiler("sideEffects": ["./fesm2022/compiler.mjs"]) lostpublishFacade(_global), so JIT failed withJIT compiler unavailable.What changed
The optimizer plugin now passes
markPure: trueonly for files undernode_modules/@angular/whose nearestpackage.jsonmarks them side-effect-free. Everything else getsmarkPure: false. Metadata elision, static member wrapping and enum adjustment run as before.sideEffectsis read from the nearestpackage.json, cached per directory, and matched the way Vite's resolver matches it: a boolean as is,[]as side-effect-free, and array patterns throughcreateFilterrelative to the package directory, with**/prepended to patterns that have no/.this.getModuleInfo(id).moduleSideEffectscan't be used here: in a Vite 8.3.4 build it isnullin thetransformhook forzone.js,@angular/coreand@angular/compiler.How it matches the Angular CLI
In
@angular/build(22.2),javascript-transformer-worker.jscomputessafeAngularPackage = sideEffects === false && /[\\/]node_modules[\\/]@angular[\\/]/.test(filename), and only safe packages get top-level pure annotations (topLevelSafeMode = !safeAngularPackage).sideEffectscomes from esbuild'sbuild.resolve(), which applies thepackage.jsonfield. Metadata elision, static member wrapping and enum adjustment run for every file, so they stay ungated here too.Two differences remain:
"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.newon a short list of known side-effect-free constructors. This PR doesn't port that.Verification
test/build-optimizer.test.tsdrives the plugin on fixture packages written to a temp dir. Cases: a package withoutsideEffects,sideEffects: false, an array that matches the file, an array that doesn't, a pattern with no/,[], a side-effect-free non-@angularpackage, and static member wrapping in a side-effectful package. 4 of the 8 tests fail onmain; all pass with the fix.pnpm test(455 passed),pnpm check,tsc -p tsconfig.json.vite build(Vite 8.3.4, Angular 22.2.0,zone.js0.16.0) of an app that importszone.jsand@angular/compilerand usesprovideZoneChangeDetection():@oxc-angular/vite@0.0.42, the bundle has noloadZone()/patchCommon()/publishFacade()calls, and the app throwsNG0908at bootstrap.setTimeoutin the component runs in theangularzone and updates the view, andng.ɵcompilerFacadeis set.@angular/core+@angular/platform-browseronly) builds to a byte-identical bundle with 0.0.42 and with this branch.Maintainer follow-up
Merged
main(#598) into the branch, then polished after a review pass:package.jsoncan no longer turn off the whole optimizer for a file. A BOM is stripped, non-stringsideEffectsentries are ignored, and an unreadable file counts as having side effects. Metadata elision, static wrapping and enum adjustment still run.package.jsonand esbuild-style matching, like the Angular CLI. Vite 8 (rolldown) reads only the package root, but real Angular 22 packages give the same answer either way (68 resolved@angularids checked, 0 differences).@angularpackage with nosideEffectsfield (the actual cause of optimizer: markPure ignores package sideEffects, tree-shakes zone.js and @angular/compiler in production builds #595),sideEffects: true, a BOM, broken JSON, and mixed arrays.Open questions, not changed here:
platformBrowserDynamic()now keep@angular/compiler(about 240 KB → 698 KB minified), because itspublishFacadeis no longer dropped. That is correct for optimizer: markPure ignores package sideEffects, tree-shakes zone.js and @angular/compiler in production builds #595. The Angular CLI avoids it by rewriting the AOT bootstrap toplatformBrowser(), which this plugin doesn't do. Follow-up?new InjectionToken(...)pure (sideEffectFreeConstructors). Not ported, as noted above.package.json(CLI/esbuild), or read the package root like Vite 8?🤖 Generated with Claude Code