Repository navigation
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
There was a problem hiding this comment.
downleveling is maybe the term you're looking instead of "lower"
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.
Stacked on #607. The first three commits (
a1a0cc5,dd27b7f,6a5b004) are #607's, unchanged. This PR addsa59e630and a merge ofmain, which resolves a one-line conflict next tovendorSourceMap. Please merge #607 first. I'll rebase this PR after that.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 lowersasyncto generators whenzone.jsis in the polyfills (esbuildsupported: { 'async-await': false },getFeatureSupportin@angular/build).Fix
A new sub-plugin,
@oxc-angular/vite-lower-async, runs #607'stransformForBrowserwithlowerAsync: trueon application scripts that containasync(notnode_modules).confighook: Vite 8 transforms with Oxc. Oxc'stargetlowers 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 lower 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).transformwithorder: 'post', so it sees code after TypeScript is stripped. It passes that code as JavaScript, so onlyasyncis lowered and class fields are left alone. It also runs after Vitest hoistsvi.mock(). If the lowering 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.@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 lowering. 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. Server (SSR) transforms stay native, as feat(napi): transformForBrowser - TypeScript with Angular's settings, async lowered for zone.js #607'slowerAsync: falseintends. Note that the CLI does lower its server bundle for zone apps.node_modulesand pre-bundled deps), which the CLI also lowers.Checks
test/lower-async.test.ts(6 tests):vi.mockfactory. Withoutorder: 'post'it fails withCannot access '__vi_import_1__' before initialization. Without the lowering, it fails itsnot.toContain('async')assertion.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 lowering. Withzoneless: trueit fails withexpected "vi.fn()" to be called at least once.vite buildof a module with an async method and an async generator. The helpers are inlined from Rolldown's runtime and noasyncis left. An SSR build keeps it native.main):vitest run --dir ./test: 21 files, 387 tests pass.tsc -p tsconfig.json --noEmitis clean.oxfmt --checkandoxlint --type-aware --type-checkare clean on the changed files.🤖 Generated with Claude Code