From 8c9238e1d75be67f1adce5f6c46f0aa5007ebbb0 Mon Sep 17 00:00:00 2001 From: Maruthan G Date: Sat, 28 Mar 2026 02:01:39 +0530 Subject: [PATCH 1/2] fix(@angular/build): correct misleading error message for top-level await When using top-level await in a Zone.js application, esbuild would show an error mentioning "target environment" with browser versions, which is misleading. The actual reason is that async/await is downleveled for Zone.js compatibility and top-level await cannot be downleveled. This change augments the esbuild error with a note explaining that top-level await is not supported in applications that use Zone.js, and provides a link to the zoneless Angular documentation. Fixes #28904 --- .../src/builders/application/execute-build.ts | 28 +++++++++ .../behavior/top-level-await-error_spec.ts | 58 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 packages/angular/build/src/builders/application/tests/behavior/top-level-await-error_spec.ts diff --git a/packages/angular/build/src/builders/application/execute-build.ts b/packages/angular/build/src/builders/application/execute-build.ts index aaddc5b6ef7e..a4c4c4df44ff 100644 --- a/packages/angular/build/src/builders/application/execute-build.ts +++ b/packages/angular/build/src/builders/application/execute-build.ts @@ -21,6 +21,7 @@ import { extractLicenses } from '../../tools/esbuild/license-extractor'; import { profileAsync } from '../../tools/esbuild/profiling'; import { calculateEstimatedTransferSizes, + isZonelessApp, logBuildStats, transformSupportedBrowsersToTargets, } from '../../tools/esbuild/utils'; @@ -156,6 +157,33 @@ export async function executeBuild( // Return if the bundling has errors if (bundlingResult.errors) { + // If Zone.js is used, augment top-level await errors with a more helpful message. + // esbuild's default error mentions "target environment" with browser versions, but + // the actual reason is that async/await is downleveled for Zone.js compatibility. + if (!isZonelessApp(options.polyfills)) { + for (const error of bundlingResult.errors) { + if ( + error.text?.startsWith( + 'Top-level await is not available in the configured target environment', + ) + ) { + error.notes = [ + { + text: + 'Top-level await is not supported in applications that use Zone.js. ' + + 'Consider removing Zone.js or moving this code into an async function.', + location: null, + }, + { + text: 'For more information about zoneless Angular applications, visit: https://angular.dev/guide/zoneless', + location: null, + }, + ...(error.notes ?? []), + ]; + } + } + } + executionResult.addErrors(bundlingResult.errors); return executionResult; diff --git a/packages/angular/build/src/builders/application/tests/behavior/top-level-await-error_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/top-level-await-error_spec.ts new file mode 100644 index 000000000000..bccd8019ce62 --- /dev/null +++ b/packages/angular/build/src/builders/application/tests/behavior/top-level-await-error_spec.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { buildApplication } from '../../index'; +import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; + +describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { + describe('Behavior: "Top-level await error message"', () => { + it('should show a Zone.js-specific error when top-level await is used with Zone.js', async () => { + await harness.writeFile( + 'src/main.ts', + ` + const value = await Promise.resolve('test'); + console.log(value); + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + polyfills: ['zone.js'], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + expect(result?.success).toBeFalse(); + expect(logs).toContain( + jasmine.objectContaining({ + message: jasmine.stringMatching( + 'Top-level await is not supported in applications that use Zone.js', + ), + }), + ); + }); + + it('should not show a Zone.js-specific error when top-level await is used without Zone.js', async () => { + await harness.writeFile( + 'src/main.ts', + ` + const value = await Promise.resolve('test'); + console.log(value); + `, + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + polyfills: [], + }); + + const { result, logs } = await harness.executeOnce({ outputLogsOnFailure: false }); + // Without Zone.js, top-level await should be supported and the build should succeed + expect(result?.success).toBeTrue(); + }); + }); +}); From 12d555fc935b0f19d3e05e52e2d1c6961e024fbe Mon Sep 17 00:00:00 2001 From: Maruthan G Date: Thu, 2 Apr 2026 15:41:46 +0530 Subject: [PATCH 2/2] refactor(@angular/build): extract esbuild top-level await error string into named constant --- .../build/src/builders/application/execute-build.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/angular/build/src/builders/application/execute-build.ts b/packages/angular/build/src/builders/application/execute-build.ts index a4c4c4df44ff..e6b375bfcf20 100644 --- a/packages/angular/build/src/builders/application/execute-build.ts +++ b/packages/angular/build/src/builders/application/execute-build.ts @@ -38,6 +38,10 @@ import { inlineI18n, loadActiveTranslations } from './i18n'; import { NormalizedApplicationBuildOptions } from './options'; import { createComponentStyleBundler, setupBundlerContexts } from './setup-bundling'; +/** The esbuild error text prefix used to detect top-level await errors. */ +const TOP_LEVEL_AWAIT_ERROR_TEXT = + 'Top-level await is not available in the configured target environment'; + // eslint-disable-next-line max-lines-per-function export async function executeBuild( options: NormalizedApplicationBuildOptions, @@ -162,11 +166,7 @@ export async function executeBuild( // the actual reason is that async/await is downleveled for Zone.js compatibility. if (!isZonelessApp(options.polyfills)) { for (const error of bundlingResult.errors) { - if ( - error.text?.startsWith( - 'Top-level await is not available in the configured target environment', - ) - ) { + if (error.text?.startsWith(TOP_LEVEL_AWAIT_ERROR_TEXT)) { error.notes = [ { text: