angular / angular/angular-cli

@angular/build:unit-test (vitest): zoneless project fails to compile with "Top-level await is not available" when zone.js is a transitive dep

Đang mở
#33,324 3 bình luận 2 reaction 0 người được giao Xem trên GitHub
area: @angular/build gemini-triaged
Ngôn ngữ chính
TypeScript
Star
27k
Fork
11.8k
Merge trung bình
14 giờ 23 phút
Pull request đã merge (30 ngày)
162

Mô tả

### Command

test

### Is this a regression?

- [x] Yes, this behavior used to work in the previous version

### The previous version in which this bug was not present was

21.2.13

### Description

## Description

A zoneless Angular project (`provideZonelessChangeDetection()` in `main.ts`, no `zone.js` in the `polyfills` array) fails to compile its unit-test bundle with:
X [ERROR] Top-level await is not available in the configured target environment ("chrome145.0", "ios17.0", "safari17.0" + 2 overrides)
### Root cause

In `@angular/build/src/builders/unit-test/runners/vitest/build-options.js`, `getZoneTestingStrategy()` decides what to emit into the virtual `angular:test-bed-init` module:

```js
function getZoneTestingStrategy(buildOptions, projectSourceRoot) {
if (buildOptions.polyfills?.includes('zone.js/testing')) return 'none';
if (buildOptions.polyfills?.includes('zone.js')) return 'static';
try {
projectRequire.resolve('zone.js');
return 'dynamic'; // ← falls through to here for zoneless projects
} catch { return 'none'; }
}
```
For a zoneless app, polyfills correctly contains neither 'zone.js' nor 'zone.js/testing' — but zone.js is still resolvable on disk because it's an optional/peer dep of @angular/core (and several Angular test utilities like fakeAsync / tick still reference it). The strategy therefore resolves to 'dynamic', which emits this into the virtual module:
```
if (typeof Zone !== 'undefined') {
// 'zone.js/testing' is used to initialize the ZoneJS testing environment.
// It must be imported dynamically to avoid a static dependency on 'zone.js'.
await import('zone.js/testing');
}
```

The runtime guard typeof Zone !== 'undefined' would correctly skip the import on a zoneless app, but esbuild evaluates the top-level await at compile time and rejects the syntax — regardless of whether the branch can actually run. The build fails before the guard ever executes.

### Expected

A zoneless project should resolve the strategy to `'none'` and emit no zone-init code. The unit-test bundle should compile cleanly.

### Actual

Strategy resolves to `'dynamic'` because `zone.js` is transitively installed. The virtual `angular:test-bed-init` module contains a top-level `await`, which esbuild rejects against the configured browser targets — even though those targets (Chrome 145, iOS 17, Safari 17) actually do support TLA. The compile-time rejection appears to be tied to the output format guarantees rather than the targets themselves, so tightening browserslist does not help.

### Minimal Reproduction

1. Generate a zoneless Angular 22 app (or upgrade an existing zoneless app).
2. Confirm main.ts uses provideZonelessChangeDetection() and polyfills in angular.json does not include 'zone.js' or 'zone.js/testing'.
3. Configure test to use @angular/build:unit-test with the vitest runner.
4. Run ng test.

### Exception or Error

```text
Top-level await is not available
```

### Your Environment

```text
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/

Angular CLI : 22.0.0
Angular : 22.0.0
Node.js : 24.16.0
Package Manager : npm 11.13.0
Operating System : win32 x64

┌────────────────────────────────────┬───────────────────┬───────────────────┐
│ Package │ Installed Version │ Requested Version │
├────────────────────────────────────┼───────────────────┼───────────────────┤
│ @angular-devkit/build-angular │ 22.0.0 │ 22.0.0 │
│ @angular/animations │ 22.0.0 │ 22.0.0 │
│ @angular/build │ 22.0.0 │ 22.0.0 │
│ @angular/cdk │ 21.2.13 │ 21.2.13 │
│ @angular/cli │ 22.0.0 │ 22.0.0 │
│ @angular/common │ 22.0.0 │ 22.0.0 │
│ @angular/compiler │ 22.0.0 │ 22.0.0 │
│ @angular/compiler-cli │ 22.0.0 │ 22.0.0 │
│ @angular/core │ 22.0.0 │ 22.0.0 │
│ @angular/forms │ 22.0.0 │ 22.0.0 │
│ @angular/google-maps │ 21.2.13 │ 21.2.13 │
│ @angular/language-service │ 22.0.0 │ 22.0.0 │
│ @angular/material │ 21.2.13 │ 21.2.13 │
│ @angular/material-date-fns-adapter │ 21.2.13 │ 21.2.13 │
│ @angular/platform-browser │ 22.0.0 │ 22.0.0 │
│ @angular/platform-browser-dynamic │ 22.0.0 │ 22.0.0 │
│ @angular/platform-server │ 22.0.0 │ 22.0.0 │
│ @angular/router │ 22.0.0 │ 22.0.0 │
│ @angular/service-worker │ 22.0.0 │ 22.0.0 │
│ @angular/ssr │ 22.0.0 │ 22.0.0 │
│ rxjs │ 7.8.2 │ 7.8.2 │
│ typescript │ 6.0.3 │ 6.0.3 │
│ vitest │ 4.1.7 │ 4.1.7 │
```

### Anything else relevant?

_No response_

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Hướng nghiên cứu

Đọc @angular/build/src/builders/unit-test/runners/vitest/build-options.js, tập trung vào getZoneTestingStrategy() và module ảo angular:test-bed-init. Tái hiện lỗi bằng ng test trong một dự án zoneless, sau đó xác minh rằng strategy không phát ra mã khởi tạo zone và bundle unit-test biên dịch mà không gặp lỗi top-level-await.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
angular, typescript
Lĩnh vực
build-system, testing, tooling
Loại issue
Lỗi
Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
70/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.