Skip to content

fix(linker): use the file's @angular/core namespace in generated templates - #526

Merged
Brooooooklyn merged 1 commit into
voidzero-dev:mainfrom
ashley-hunter:fix/linker-minified-core-namespace
Oct 3, 2026
Merged

Brooooooklyn merged 1 commit into
voidzero-dev:mainfrom
ashley-hunter:fix/linker-minified-core-namespace

Conversation

@ashley-hunter

Copy link
Copy Markdown
Collaborator

Problem

Minifiers such as esbuild rename a library's @angular/core namespace import, e.g. import * as e from "@angular/core". The linker links the ɵɵngDeclare* calls, but the template and host-binding functions it generates still reference i0 (i0.ɵɵelementStart(...), i0.ɵɵclassProp(...)). i0 isn't defined in that file, so those functions throw ReferenceError: i0 is not defined. Components and directives (host bindings) are both affected.

The linker's own output already used the file's namespace. The template pipeline hardcodes i0 in about 20 places, and that namespace was never passed to it.

Fix

  • JsEmitter::with_core_namespace(ns) prints ns in place of any i0 variable read. JsEmitter::new() is unchanged, so no other output changes.
  • compile_template_for_linker and compile_host_bindings_for_linker take the namespace. The linker passes it for both components and directives.

This matches Angular's Babel linker, which emits the file's namespace.

Verification

  • New unit test test_link_component_uses_minified_namespace covers a directive with host bindings, and a component with a listener, a pipe, @if, i18n and @defer. It fails before the fix and passes after.

  • End to end: a library compiled with ngc in partial mode, minified with esbuild (--minify --charset=utf8), linked, imported in Node, then its generated functions called.

    Linker stray i0 refs template / host functions
    oxc before 14 ReferenceError: i0 is not defined
    oxc after 0 same result as Angular
    Angular Babel linker 0 Angular's normal "no view" error

    After the fix, oxc and Angular both reference e.ɵɵ… 16 times.

  • Local CI steps:

    • cargo test, cargo fmt --check, conformance (1252/1252, snapshot unchanged): pass.
    • pnpm test (106), pnpm check: pass.
    • Compare fixtures: 678/681. The other 3 are fixtures already marked skip.
    • Playwright e2e: 23/23.

Follow-up (not in this PR)

esbuild's default --minify (--charset=ascii) writes ɵ as ɵ. The linker doesn't link such a file at all, so the library falls back to JIT. That will be fixed separately.

…lates

Minifiers rename the `import * as i0 from "@angular/core"` namespace (e.g. to
`e`). The linker already used that name in its own output, but the template
and host-binding pipeline hardcodes `i0`, so linked components and directives
referenced an unbound `i0` and threw `ReferenceError: i0 is not defined`.

The emitter can now print the file's namespace in place of `i0`, and the
linker sets it for template and host-binding output, matching Angular's
Babel linker.

@Brooooooklyn Brooooooklyn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review on the PR branch, verified locally:

  • Every i0 in generated code flows through ReadVarExpr — the ReadVar emit site is the single, correct seam for the namespace rename. Consts, initializers, ngContentSelectors, declarations, and the main template fn all emit through the namespaced emitter.
  • User identifiers can't collide: tested @for (i0 of items), @let i0, {{ i0 }}, and let-i0 template vars end to end — the pipeline renames user vars to i0_r1-style or reads them via ctx, so ReadVar("i0") only ever means the core namespace.
  • WrappedNode/External/DynamicImport either error out or aren't on the linker path, so no i0 bypasses the rename.
  • Reverting just the emitter change makes test_link_component_uses_minified_namespace fail (stray i0 refs); restoring passes. All 81 linker tests green.
  • Mergeable against current main with PR 527's linker changes in flight — no conflicts.

@Brooooooklyn
Brooooooklyn merged commit d0d3fae into voidzero-dev:main Oct 3, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants