S267 AOT Testing Report

Documentation for Testing

S267: AOT Compilation Testing Report — Final

Session: S267.1 (continued) Date: 2026-03-24 Scope: Testing and validation of S264/S267 AOT compilation pipeline for Vitto Studio Live Preview Branch: session-264-268 (no commits — concurrent session active)


Executive Summary

The S264/S267 AOT compilation pipeline is architecturally sound but blocked by two remaining issues (Bugs 8-9) that need a dev server restart to verify. Combined work from S264.1-264.5 and S267 has found and fixed 9 bugs across 3 layers (parser, codegen, transformer). All fixes are in source files. Seven bugs were verified, two remain to be browser-tested after a vite dev server restart.

Bug tracking across sessions:

Browser test results:

Verdict: AOT is very close to working. Bugs 8-9 fixes are in source. After a dev server restart (yarn serve:space), the full pipeline should be re-tested. Do NOT flip the default to AOT until the checklist at the bottom of this report passes.


Complete Bug History

# Bug Severity Found Fixed Browser-verified
1 }} infinite loop in parseText() CRITICAL S267 S267 Yes (unit test)
2 [(ngModel)] parse order HIGH S267 S267 Yes (unit test)
3 Array literal ['/route'] got ctx. prefix HIGH S267 S267 Yes
4 ɵɵdefineComponent was a comment HIGH S267 S267 Yes
5 Missing dependencies → blank render BLOCKING S267 S264.4 Partial (S264.4 claims 16/16)
6 @for parser: items() broke on ) HIGH S264.4 S264.4 Yes (S264.4)
7 item.name → ctx.item.name in @for HIGH S264.4 S264.5 Yes (S264.5)
8 ɵɵStandaloneFeature not in Angular 20 core.mjs HIGH S267 S267 Yes (verified fix, then cache froze)
9 Missing AttributeMarker.Bindings in consts BLOCKING S267 S267 No — needs restart

Bug 8: ɵɵStandaloneFeature Not Exported in Angular 20

Location: decorator-transformer.ts, line ~151 (old) → removed Error: TypeError: fn is not a function at initFeatures in core.mjs:12545

Root Cause: The decorator-transformer added features: [ɵɵStandaloneFeature] to the ɵɵdefineComponent() call for standalone components. But ɵɵStandaloneFeature is NOT exported from the self-hosted Angular 20 runtime (core.mjs). Angular 20 handles standalone setup internally within ɵɵdefineComponent() when standalone: true is set — the standalone injector is automatically configured without an explicit feature function.

Evidence:

// Checked at runtime via Chrome DevTools:
const core = await import('/angular-runtime/core.mjs');
typeof core.ɵɵStandaloneFeature    // → "undefined"
typeof core.ɵɵdefineComponent      // → "function"

// But ɵɵdefineComponent handles standalone automatically:
const testDef = core.ɵɵdefineComponent({
  type: class Test {},
  selectors: [['test']],
  standalone: true,
  ...
});
typeof testDef.getStandaloneInjector  // → "function" ✅

Fix: Removed ɵɵStandaloneFeature from imports and removed features: [ɵɵStandaloneFeature] from the generated definition. The standalone: true property alone is sufficient.

Files modified:

Browser verification: Yes — error disappeared, Angular bootstrapped successfully.


Bug 9: Missing AttributeMarker.Bindings in Consts Arrays

Location: ivy-codegen.ts, generateElement() method, lines ~160-170 Symptom: Angular bootstraps, AppComponent template renders tab text, but routing is completely broken. <router-outlet> is empty. No errors in console.

Root Cause: The ivy-codegen builds consts arrays for elements containing only static attributes (e.g., ["routerLinkActive", "active"]). Angular's directive matching system requires AttributeMarker.Bindings (numeric value 3) followed by binding names in the consts array for elements with property bindings. Without these markers, Angular cannot match directive selectors like [routerLink] (from RouterLink) to elements that only have property bindings.

Real Angular AOT output (what the codegen should produce):

consts: [["routerLinkActive", "active", 3, "routerLink"]]
//                                       ^ AttributeMarker.Bindings

Our current output:

consts: [["routerLinkActive", "active"]]
// Missing: 3, "routerLink"

Impact: All directive-based features silently fail in AOT mode. This affects RouterLink, RouterLinkActive, NgClass, NgStyle, and any other directive matched by attribute selector. This is the single biggest remaining blocker for AOT.

Fix implemented in source (two locations):

  1. ivy-codegen.ts (primary fix): Modified generateElement() to extract binding names from node.bindings and append 3 (AttributeMarker.Bindings) + binding names to the consts entry.

  2. decorator-transformer.ts (backup/post-processing fix): Added injectBindingMarkers() function that post-processes the codegen output by parsing the templateFn string to map element indices → consts indices → binding names, then injects the markers.

  3. dist/.../ivy-codegen.js (dist fix): Also patched the pre-built dist copy, since vite was serving from dist for the ivy-codegen module.

Browser verification: NOT YET VERIFIED. The vite dev server froze the platform-preview chunk in its optimization cache and stopped picking up any changes to files in this library. A dev server restart is required.


Architectural Findings

1. Angular Compiler Cannot Be Eliminated (confirmed S267, documented S264.4)

Angular library packages (HttpClient, Router, Forms) ship as partially-compiled using ɵɵngDeclareFactory / ɵɵngDeclareDirective. These require JIT compilation at runtime. Our AOT only pre-compiles user component templates. The compilerEliminated flag is correctly set to false in the AOT strategy.

2. Vite Dev Server Caching Issue

The vite dev server (via nx) pre-bundles library chunks and caches them aggressively. During this session, the platform-preview library chunk (chunk-V4WI2MJ4.js, 33MB) became frozen in the optimization cache. Early file edits (decorator-transformer StandaloneFeature removal) were picked up, but later edits to any file in the library were ignored. Workaround: restart the dev server with yarn serve:space.

3. Template Parser Limitations (observed, acceptable)

Many generated components use <app-crud-detail-shell> and <app-crud-form-shell> custom elements that the template parser can't fully handle. These gracefully fall back to JIT compilation (console warnings logged). The hybrid AOT approach handles this correctly.

4. ɵɵStandaloneFeature Removed in Angular 20

Angular 20 internalized the standalone feature setup into ɵɵdefineComponent(). The ɵɵStandaloneFeature function is no longer exported from @angular/core. This is a breaking change from earlier Angular versions where it was required in the features array. Our codegen must not reference it.


Files Modified in This Session (S267 continued)

File Change Purpose
decorator-transformer.ts Removed ɵɵStandaloneFeature import/features Bug 8 fix
decorator-transformer.ts Added injectBindingMarkers() function Bug 9 fix (post-processing)
decorator-transformer.ts Added IvyTemplateOutput type import Bug 9 support
ivy-codegen.ts (source) Changed consts type to (string|number)[][] Bug 9 fix
ivy-codegen.ts (source) Added binding markers to generateElement() Bug 9 fix (primary)
ivy-codegen.js (dist) Same binding markers fix in compiled JS Bug 9 fix (dist)
bundler.service.ts Temporarily switched to AOT, then reverted to JIT Testing only

What Needs to Happen Next

Immediate (before flipping to AOT)

  1. Restart dev server: yarn serve:space — this clears the vite optimization cache
  2. Switch to AOT: Change bundler.service.ts line 15 to new AotCompilationStrategy()
  3. Verify Bug 9 fix: The consts should now include binding markers. Check console for [DecoratorTransformer] Transformed N component(s) and verify the preview renders with routing, data loading, and click handlers working.
  4. Run the full checklist below

Pre-ship Checklist

[ ] Dev server restarted (fresh vite cache)
[ ] JIT baseline renders correctly (control)
[ ] AOT compilation succeeds (console: "Transformed N component(s)")
[ ] AOT preview renders (not blank, UI visible)
[ ] Data loads in AOT mode (console: "[useCrudList] Loaded N items")
[ ] RouterLink directives work (tabs are clickable links with href)
[ ] Router-outlet populates (module content visible)
[ ] Click handlers work in AOT mode
[ ] @for loops render correctly
[ ] No new console errors vs JIT
[ ] Performance: AOT compile time ≤ JIT compile time
[ ] Reverted to JIT works (no regression)

If Bug 9 Fix Doesn't Work After Restart

The injectBindingMarkers() function parses the generated templateFn string using regex. If the regex patterns don't match the actual codegen output format (e.g., different whitespace or instruction format), the function will silently fail. Debug steps:

  1. Add console.log in injectBindingMarkers() to see what it finds
  2. Check if ɵɵadvance calls use the expected format
  3. Verify the binding markers appear in the blob source (check consts: in the iframe blob)

Longer-term Improvements

  1. Move binding markers to the codegen (ivy-codegen.ts): The primary fix should be in the codegen, not the post-processing. Once verified, the injectBindingMarkers() function in the decorator-transformer can be removed.
  2. Add AttributeMarker.Classes (1) and AttributeMarker.Styles (2): For class bindings [class.active]="..." and style bindings [style.width]="...", similar markers may be needed.
  3. Rebuild dist after source changes: Run nx build platform-preview or set up the build pipeline to auto-rebuild on source changes.
  4. Integration test suite: Add browser-level tests that switch to AOT and verify rendering, not just unit tests on the compiler layers.