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:
- S267 found and fixed: Bugs 1, 2, 3, 4 + architectural finding (hybrid AOT)
- S264.4-264.5 fixed: Bugs 5, 6, 7
- S267 continued found and fixed: Bugs 8, 9
Browser test results:
- JIT baseline: works perfectly (2 assets, 139-160ms, 6 items loaded)
- AOT with Bugs 1-7 fixed:
TypeError: fn is not a functionatinitFeatures→ Bug 8 - AOT with Bug 8 fixed: Angular bootstraps, tabs render as text, routing broken → Bug 9
- Both Bugs 8 and 9 have fixes implemented in source. Cannot verify in browser due to vite's aggressive chunk caching (the platform-preview library chunk became frozen in vite's optimization cache mid-session)
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:
decorator-transformer.ts: Removed lines 150-152 (import add) and line 159 (features template)
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):
ivy-codegen.ts(primary fix): ModifiedgenerateElement()to extract binding names fromnode.bindingsand append3(AttributeMarker.Bindings) + binding names to the consts entry.decorator-transformer.ts(backup/post-processing fix): AddedinjectBindingMarkers()function that post-processes the codegen output by parsing thetemplateFnstring to map element indices → consts indices → binding names, then injects the markers.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)
- Restart dev server:
yarn serve:space— this clears the vite optimization cache - Switch to AOT: Change
bundler.service.tsline 15 tonew AotCompilationStrategy() - 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. - 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:
- Add
console.logininjectBindingMarkers()to see what it finds - Check if
ɵɵadvancecalls use the expected format - Verify the binding markers appear in the blob source (check
consts:in the iframe blob)
Longer-term Improvements
- 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. - Add
AttributeMarker.Classes(1) andAttributeMarker.Styles(2): For class bindings[class.active]="..."and style bindings[style.width]="...", similar markers may be needed. - Rebuild dist after source changes: Run
nx build platform-previewor set up the build pipeline to auto-rebuild on source changes. - Integration test suite: Add browser-level tests that switch to AOT and verify rendering, not just unit tests on the compiler layers.