From 328d31ae34a7448b19e4df8ff7867634ac34eb85 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:18:23 -0400 Subject: [PATCH] fix(@angular-devkit/build-angular): do not mark standalone expression statements as pure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Top-level call and new expressions that appear directly as expression statements are executed solely for their side effects, such as class property initialization, static blocks, and helper calls. When targeting browsers requiring class static block or property lowering (e.g., Chrome < 94), Babel's preset-env lowers self-referencing static properties into zero-argument IIFEs (and static blocks into _staticBlock() calls). Because pure-toplevel-functions previously only skipped IIFEs if they had arguments, zero-argument IIFE statements were marked as pure. Downstream minifiers (such as esbuild) then eliminated these standalone pure statements as dead code, dropping essential class members like Injector.ɵprov and causing NG0201 at bootstrap. This change ensures that call and new expressions that are directly expression statements are never annotated as pure, avoiding accidental elimination of side-effecting statements without requiring hardcoded whitelists of helper function names. Fixes #34017 --- .../babel/plugins/pure-toplevel-functions.ts | 14 ++++++++++++ .../plugins/pure-toplevel-functions_spec.ts | 22 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/packages/angular_devkit/build_angular/src/tools/babel/plugins/pure-toplevel-functions.ts b/packages/angular_devkit/build_angular/src/tools/babel/plugins/pure-toplevel-functions.ts index 08947506049f..75cbdd86ecdf 100644 --- a/packages/angular_devkit/build_angular/src/tools/babel/plugins/pure-toplevel-functions.ts +++ b/packages/angular_devkit/build_angular/src/tools/babel/plugins/pure-toplevel-functions.ts @@ -71,6 +71,13 @@ export default function (): PluginObject { return; } + // Do not annotate standalone expression statements. + // Standalone expression statements are executed solely for their side effects; + // marking them pure causes minifiers to drop them as dead code. + if (path.parentPath.isExpressionStatement()) { + return; + } + const callee = path.get('callee'); if ( (callee.isFunctionExpression() || callee.isArrowFunctionExpression()) && @@ -96,6 +103,13 @@ export default function (): PluginObject { return; } + // Do not annotate standalone expression statements. + // Standalone expression statements are executed solely for their side effects; + // marking them pure causes minifiers to drop them as dead code. + if (path.parentPath.isExpressionStatement()) { + return; + } + const { topLevelSafeMode = false } = state.opts; if (!topLevelSafeMode) { diff --git a/packages/angular_devkit/build_angular/src/tools/babel/plugins/pure-toplevel-functions_spec.ts b/packages/angular_devkit/build_angular/src/tools/babel/plugins/pure-toplevel-functions_spec.ts index c9d7eafd4aee..f3df13b1cca9 100644 --- a/packages/angular_devkit/build_angular/src/tools/babel/plugins/pure-toplevel-functions_spec.ts +++ b/packages/angular_devkit/build_angular/src/tools/babel/plugins/pure-toplevel-functions_spec.ts @@ -154,6 +154,28 @@ describe('pure-toplevel-functions Babel plugin', () => { `), ); + it('does not annotate top-level call expression statements', testCaseNoChange('someCall();')); + + it( + 'does not annotate top-level IIFE expression statements', + testCaseNoChange(` + (() => { + _defineProperty(_Injector, 'ɵprov', ɵɵdefineInjectable({ token: _Injector })); + _defineProperty(_Injector, '__NG_ELEMENT_ID__', -1); + })(); + `), + ); + + it( + 'does not annotate top-level static block helper calls', + testCaseNoChange(` + _staticBlock(); + _staticBlock2(); + `), + ); + + it('does not annotate top-level new expression statements', testCaseNoChange('new SomeClass();')); + describe('topLevelSafeMode: true', () => { it( 'annotates top-level `new InjectionToken` expressions',