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)
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 76/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- angular, babel, typescript
- Área
- build-system, cli, tooling
Línea de trabajo
Empieza con @angular-devkit/build-angular/src/tools/babel/presets/application.js y JavaScriptOptimizerPlugin; después, ejecuta repro.cjs con la configuración de chrome 80. Se considera terminado cuando la salida optimizada conserva Injector.ɵprov y la reproducción completa de webpack-builder arranca sin NG0201.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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:
@babel/preset-envfor such targets lowers class fields (class-static-block lowering pulls intransform-class-properties). Static fields that reference their own class (static ɵprov = ɵɵdefineInjectable({ token: Injector, ... })) are emitted as a zero-argument IIFE:
For targets that only need private methods lowered (Chrome 84–93) the same code appears as a_Injector = Injector; (() => { _defineProperty(_Injector, "ɵprov", ɵɵdefineInjectable({ token: _Injector, ... })); _defineProperty(_Injector, "__NG_ELEMENT_ID__", -1); })();_staticBlock()helper call.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_staticBlockhelper name, so the wrapper gets/*#__PURE__*/.- esbuild in
JavaScriptOptimizerPlugindrops the annotated, unused call.Injector.ɵprovis gone →NG0201at 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.
- Lenguaje dominante
- TypeScript
- Estrellas
- 27k
- Forks
- 11.8k
- Merge medio
- 16 h 21 min
- PR fusionados (30 d)
- 170
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de angular/angular-cli
-
Can't use an array of hostnames in --allowedHosts cli parameter in @angular/build:dev-server Abiertoarea: @angular/build gemini-triaged
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
angular/angular-cli#33955 ·
-
area: @angular/cli gemini-triaged
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
angular/angular-cli#33055 · 1 comentario · 3 reacciones ·
-
angular/build:library area: @angular/build gemini-triaged
angular/angular-cli#34131 · 1 asignado ·
-
angular/build:library area: @angular/build gemini-triaged
angular/angular-cli#34130 · 1 asignado ·
-
angular/build:library area: @angular/build gemini-triaged
angular/angular-cli#34128 · 1 asignado ·
Todos los issues de angular/angular-cli
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
copse-dev/agent-pane#2953 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
Eynzof/Hermes-CN-Desktop#610 ·
-
[Bug]: Matrix progress drafts fail with "Matrix runtime not initialized" during tool activity Abiertobug clawsweeper:linked-pr-open clawsweeper:needs-live-repro clawsweeper:no-new-fix-pr impact:message-loss issue-rating: 🐚 platinum hermit P2 regression
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
-
Client support matrix inclusion Abiertoenhancement
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
-
calcite-components needs triage refactor
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
Esri/calcite-design-system#15203 ·