Hacktoberfest 2026: die Issues, die Maintainer für den Oktober markiert haben – offen und einsteigerfreundlich. Hacktoberfest-Issues durchsuchen

pure-toplevel-functions marks the IIFE Babel emits for self-referencing static fields as PURE → Injector.ɵprov dropped, NG0201 (webpack builder, targets below Chrome 94)

Offen
#34,017 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
3/5
Geschätzter Aufwand
1-2 Tage
Anfängerfreundlichkeit
76/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Aktiv
Tech-Stack
angular, babel, typescript

Rechercherichtung

Beginne mit @angular-devkit/build-angular/src/tools/babel/presets/application.js und dem JavaScriptOptimizerPlugin und führe dann repro.cjs mit den chrome 80-Einstellungen aus. Die Aufgabe ist abgeschlossen, wenn die optimierte Ausgabe Injector.ɵprov beibehält und die vollständige webpack-builder-Reproduktion ohne NG0201 startet.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

area: @angular-devkit/build-angular gemini-triaged
Command

build, serve

Is this a regression?
  • Yes, this behavior used to work in the previous version
The previous version in which this bug was not present was

Same family as #29145 (Angular 19). #29250 fixed the _defineProperty(...) call shape, but the IIFE wrapper that Babel emits for self-referencing static fields is still annotated as pure.

Description

With the webpack builder (@angular-devkit/build-angular:browser), optimization: true / buildOptimizer: true and a browserslist that requires lowering class static blocks (any target below Chrome 94, e.g. chrome >= 80, Smart TV engines), the application fails at bootstrap with NG0201 because Injector.ɵprov (and __NG_ELEMENT_ID__) no longer exist on Injector from @angular/core.

Chain:

  1. @babel/preset-env for such targets lowers class fields (class-static-block lowering pulls in transform-class-properties). Static fields that reference their own class (static ɵprov = ɵɵdefineInjectable({ token: Injector, ... })) are emitted as a zero-argument IIFE:
    _Injector = Injector;
    (() => {
      _defineProperty(_Injector, "ɵprov", ɵɵdefineInjectable({ token: _Injector, ... }));
      _defineProperty(_Injector, "__NG_ELEMENT_ID__", -1);
    })();
    
    For targets that only need private methods lowered (Chrome 84–93) the same code appears as a _staticBlock() helper call.
  2. pure-toplevel-functions (running in the non-safe mode used for @angular/* packages) skips IIFEs only when they have arguments (path.node.arguments.length !== 0) and does not know the _staticBlock helper name, so the wrapper gets /*#__PURE__*/.
  3. esbuild in JavaScriptOptimizerPlugin drops the annotated, unused call. Injector.ɵprov is gone → NG0201 at bootstrap.

#29250 added an exemption for direct _defineProperty(...) calls, which covers the non-self-referencing fields (THROW_IF_NOT_FOUND, NULL) but not the IIFE that Babel wraps around self-referencing ones.

Minimal Reproduction

Standalone script (run inside a project with @angular-devkit/build-angular installed; it uses the real application Babel preset with the options the webpack loader passes for @angular/* packages):

// repro.cjs
const { transformSync } = require('@babel/core');
const esbuild = require('esbuild');
const preset = require('@angular-devkit/build-angular/src/tools/babel/presets/application.js').default;
const src = `
export class Injector {
  static THROW_IF_NOT_FOUND = THROW_IF_NOT_FOUND;
  static NULL = new NullInjector();
  static create(options, parent) { return createInjector(options, parent); }
  static ɵprov = /* @__PURE__ */ ɵɵdefineInjectable({ token: Injector, providedIn: 'any', factory: () => ɵɵinject(INJECTOR) });
  static __NG_ELEMENT_ID__ = -1;
}`;
const lowered = transformSync(src, {
  configFile: false, babelrc: false, sourceType: 'module', compact: false,
  filename: '/x/node_modules/@angular/core/fesm2022/core.mjs',
  presets: [[preset, { supportedBrowsers: ['chrome 80'], forceAsyncTransformation: false, optimize: { topLevelSafeMode: false, wrapDecorators: true } }]],
}).code;
console.log(lowered);
const out = esbuild.transformSync(lowered, { treeShaking: true, format: 'esm', target: 'chrome80', charset: 'utf8' });
console.log(out.code);
console.log(/ɵprov/.test(out.code) ? 'OK: Injector.ɵprov kept' : 'BUG: Injector.ɵprov dropped');

Output after the preset (note the /*#__PURE__*/ on the IIFE):

_Injector = Injector;
_defineProperty(Injector, "THROW_IF_NOT_FOUND", THROW_IF_NOT_FOUND);
_defineProperty(Injector, "NULL", /*#__PURE__*/new NullInjector());
/*#__PURE__*/(() => {
  _defineProperty(_Injector, "ɵprov", /* @__PURE__ */ɵɵdefineInjectable({ token: _Injector, providedIn: 'any', factory: () => ɵɵinject(INJECTOR) }));
  _defineProperty(_Injector, "__NG_ELEMENT_ID__", -1);
})();

After esbuild the IIFE is gone: BUG: Injector.ɵprov dropped.

Full-app steps: ng new, switch to @angular-devkit/build-angular:browser, .browserslistrc = chrome >= 80, optimization: true, buildOptimizer: true, ng build, open the app → NG0201 at bootstrap.

Exception or Error
NG0201 at bootstrap: no provider for Injector (Injector.ɵprov was removed from the bundle); the app stays on the splash screen.
Your Environment
Angular CLI: 22.0.9 (@angular/build 22.0.9 nested under @angular-devkit/build-angular); the plugin source is unchanged in @angular/build 22.1.7
Angular: 22.1.0
@babel/core / @babel/preset-env: 7.29.7
esbuild: 0.19.9
Node: 22.23.1
OS: darwin
Anything else relevant?

A minimal fix that works for us (applied via patch-package): never annotate IIFEs regardless of argument count, and treat _staticBlock/_staticBlock2… as Babel helper names. Class wrappers that are safe to drop are already annotated by adjust-static-class-members, so tree-shaking is unaffected in practice (main bundle +0.06% in our legacy build).

 function isBabelHelperName(name) {
-    return babelHelpers.has(name);
+    return babelHelpers.has(name) || /^_staticBlock\d*$/.test(name);
 }
 ...
                 const callee = path.get('callee');
-                if ((callee.isFunctionExpression() || callee.isArrowFunctionExpression()) &&
-                    path.node.arguments.length !== 0) {
+                if (callee.isFunctionExpression() || callee.isArrowFunctionExpression()) {
                     return;
                 }

Since #33751 moved these plugins into @angular-devkit/build-angular, the fix would land there for 22.2. Happy to send a PR.

Vorherrschende Sprache
TypeScript
Sterne
27k
Forks
11.8k
Ø Merge
17 Std. 25 Min.
Gemergte PRs (30 T.)
183

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus angular/angular-cli

Alle Issues in angular/angular-cli

Ähnliche Issues

Weitere Issues zu TypeScript

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.