Repository navigation
fix(vite): downlevel async/await for zone.js like the Angular CLI - #642
Merged
Merged
Conversation
…and async lowered for zone.js
…itions, with tests
zone.js cannot track native `await`, so `fakeAsync` with `tick()` or `flushMicrotasks()` could not drive code that awaits. Unless the app is zoneless, lower `async`, async generators and `for await` in application code with `transformForBrowser`, as the Angular CLI does for apps with zone.js in their polyfills. `zoneless` defaults to whether zone.js is installed; server transforms stay native. The transform runs after Vitest hoists `vi.mock()`, so an async mock factory, now an eager `_asyncToGenerator(...)` call, still finds its helper import. Fixes voidzero-dev#641 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resolves the conflict with main's vendorSourceMap option next to the removed zoneless default. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JeanMeche
reviewed
Oct 26, 2026
# Conflicts: # crates/oxc_angular_compiler/src/browser.rs # napi/angular-compiler/index.d.ts # napi/angular-compiler/src/lib.rs # napi/angular-compiler/test/transform-for-browser.test.ts
- A hook-level `order: 'post'` runs after `vite:import-analysis`, so the dev server sent the bare `@oxc-project/runtime/helpers/...` import to the browser and the app failed to load. A plugin-level `enforce: 'post'` still runs after the TypeScript strip and Vitest's `vi.mock` hoisting, but before import analysis. - Name the code `.mjs` rather than `.js`: as `.js`, a file without `import`/`export` was parsed as a script and got `require()` helpers. - Report lowering errors by their message instead of `[object Object]`. - README: Vitest's `node` environment transforms as SSR, so it keeps async native. Co-Authored-By: Claude Opus 5.5 <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. |
As suggested in review: `lowerAsync` becomes `downlevelAsync` (not released yet), the Vite sub-plugin `@oxc-angular/vite-downlevel-async`, and the docs, tests and files follow. Also run the dev-server test from the real temp path: on Windows the temp dir can be an 8.3 short path (`RUNNER~1`), and Vite does not serve a file whose id differs from the real root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
|
@codex review |
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.
Fixes #641.
Builds on #607 (
transformForBrowser, merged).Problem
@oxc-angular/viteleavesasync/awaitnative. zone.js can't track a nativeawait, sofakeAsyncwithtick()/flushMicrotasks()can't drive code that awaits, and change detection after anawaitdoesn't run as a zone task. The Angular CLI downlevelsasyncto generators whenzone.jsis in the polyfills (esbuildsupported: { 'async-await': false },getFeatureSupportin@angular/build).Fix
A new sub-plugin,
@oxc-angular/vite-downlevel-async, runs #607'stransformForBrowserwithdownlevelAsync: trueon application scripts that containasync(notnode_modules).confighook: Vite 8 transforms with Oxc. Oxc'stargetdownlevels by ES version, with no per-feature switch like esbuild'ssupported. The Angular CLI's own Vite dev server hits the same limit and falls back toes2016for dependencies. That would also downlevel spread, optional chaining and class fields.esbuild.supportedneeds esbuild, and esbuild's inlinevar __asynchelper breaks undervi.mockhoisting (see vite: async/await is not lowered for zone.js, so fakeAsync/tick cannot control awaits #641).enforce: 'post', with a normal-ordertransformhook. So it runs:asyncis downleveled and class fields are left alone;vi.mock(). If the downleveling ran first, an async mock factory would become an eager_asyncToGenerator(...)call, and hoisting would move it above its helper's import:Cannot access '__vi_import_1__' before initialization;vite:import-analysis, which resolves the helper imports for the dev server. A hook-levelorder: 'post'would run after it, and the browser would get a bare@oxc-project/runtime/...import..mjs(.jsxfor JSX files), because Vite serves it as a module. As.js, a file withoutimport/exportwould get its helpers throughrequire().@oxc-project/runtime. Vite 8 resolves them without the package being installed: its builtinoxc-runtimeplugin covers dev and Vitest, and Rolldown inlines them in builds. No new dependency.zonelessoption was unused. It now controls the downleveling. When it is unset, an app is zoneless unlessnode_modules/zone.jsexists above the Vite root. That replaces the CLI's signal, zone.js in the polyfills, which a Vite app doesn't declare.nodeenvironment transforms as SSR, so it keeps async native too. The README says to run zone.js specs injsdom,happy-domor the browser.node_modulesand pre-bundled deps), which the CLI also downlevels.Checks
test/downlevel-async.test.ts(9 tests):import/exportgetsimported helpers, notrequire();transformRequest) gets the helper import resolved by import analysis;vi.mockfactory. It fails if the hook runs before the hoisting.fakeAsync+flushMicrotasks()repro from vite: async/await is not lowered for zone.js, so fakeAsync/tick cannot control awaits #641 under Vitest with zone.js 0.16.3. It passes with the downleveling. Withzoneless: trueit fails withexpected "vi.fn()" to be called at least once.import/export. Both run in zone and zoneless modes.vite buildwith async code. The helpers are inlined from Rolldown's runtime and noasyncis left. A zoneless build keeps it native.main):vitest run --dir ./test: 21 files, 397 tests pass.tsc -p tsconfig.json --noEmitis clean.oxfmt --checkandoxlint --type-aware --type-checkare clean on the changed files.🤖 Generated with Claude Code