Skip to content

Commit 54fc33f

Browse files
committed
fix(@angular/build): correct misleading error message for top-level await
When top-level await is used in an application that includes Zone.js, esbuild reports that top-level await is not available in the configured target environment even though the actual cause is the async/await downleveling required for Zone.js support. The error is now augmented with a note explaining the Zone.js limitation and pointing to the zoneless guide. Closes #28904
1 parent 34d558c commit 54fc33f

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

packages/angular/build/src/builders/application/execute-build.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { extractLicenses } from '../../tools/esbuild/license-extractor';
1919
import { profileAsync } from '../../tools/esbuild/profiling';
2020
import {
2121
calculateEstimatedTransferSizes,
22+
isZonelessApp,
2223
logBuildStats,
2324
transformSupportedBrowsersToTargets,
2425
} from '../../tools/esbuild/utils';
@@ -35,6 +36,10 @@ import { inlineI18n, loadActiveTranslations } from './i18n';
3536
import { NormalizedApplicationBuildOptions } from './options';
3637
import { createComponentStyleBundler, setupBundlerContexts } from './setup-bundling';
3738

39+
/** The esbuild error text prefix used to detect top-level await errors. */
40+
const TOP_LEVEL_AWAIT_ERROR_TEXT =
41+
'Top-level await is not available in the configured target environment';
42+
3843
// eslint-disable-next-line max-lines-per-function
3944
export async function executeBuild(
4045
options: NormalizedApplicationBuildOptions,
@@ -157,6 +162,24 @@ export async function executeBuild(
157162

158163
// Return if the bundling has errors
159164
if (bundlingResult.errors) {
165+
// If Zone.js is used, augment top-level await errors with a more helpful message.
166+
// esbuild's default error mentions "target environment" with browser versions, but
167+
// the actual reason is that async/await is downleveled for Zone.js compatibility.
168+
if (!isZonelessApp(options.polyfills)) {
169+
for (const error of bundlingResult.errors) {
170+
if (error.text?.startsWith(TOP_LEVEL_AWAIT_ERROR_TEXT)) {
171+
error.notes ??= [];
172+
error.notes.push({
173+
text:
174+
'Top-level await is not supported in applications that use Zone.js. ' +
175+
'Consider removing Zone.js or moving this code into an async function. \n' +
176+
'For more information about zoneless Angular applications, visit: https://angular.dev/guide/zoneless',
177+
location: null,
178+
});
179+
}
180+
}
181+
}
182+
160183
executionResult.addErrors(bundlingResult.errors);
161184

162185
return executionResult;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import { buildApplication } from '../../index';
10+
import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup';
11+
12+
describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
13+
describe('Behavior: "Top-level await error message"', () => {
14+
it('should show a Zone.js-specific error when top-level await is used with Zone.js', async () => {
15+
await harness.writeFile(
16+
'src/main.ts',
17+
`
18+
// The export makes this file a module, which is required for top-level await.
19+
export const value = await Promise.resolve('test');
20+
console.log(value);
21+
`,
22+
);
23+
24+
harness.useTarget('build', {
25+
...BASE_OPTIONS,
26+
polyfills: ['zone.js'],
27+
});
28+
29+
const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false });
30+
expect(result?.success).toBeFalse();
31+
expect(logs).toContain(
32+
jasmine.objectContaining({
33+
message: jasmine.stringMatching(
34+
'Top-level await is not supported in applications that use Zone.js',
35+
),
36+
}),
37+
);
38+
});
39+
40+
it('should not show a Zone.js-specific error when top-level await is used without Zone.js', async () => {
41+
await harness.writeFile(
42+
'src/main.ts',
43+
`
44+
// The export makes this file a module, which is required for top-level await.
45+
export const value = await Promise.resolve('test');
46+
console.log(value);
47+
`,
48+
);
49+
50+
harness.useTarget('build', {
51+
...BASE_OPTIONS,
52+
polyfills: [],
53+
});
54+
55+
const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false });
56+
expect(result?.success).toBeTrue();
57+
expect(logs).not.toContain(
58+
jasmine.objectContaining({
59+
level: 'error',
60+
message: jasmine.stringContaining(
61+
'Top-level await is not supported in applications that use Zone.js',
62+
),
63+
}),
64+
);
65+
});
66+
});
67+
});

0 commit comments

Comments
 (0)