Skip to content

Commit 6ee53e7

Browse files
committed
wip: phase details
1 parent 9e03989 commit 6ee53e7

File tree

5 files changed

+181
-71
lines changed

5 files changed

+181
-71
lines changed

angular.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@
8484
}
8585
},
8686
"cli": {
87-
"analytics": "6a505997-9ce1-42b3-9d81-62a5be67a151"
87+
"analytics": "6a505997-9ce1-42b3-9d81-62a5be67a151",
88+
"cache": {
89+
"enabled": false
90+
}
8891
}
8992
}

package-lock.json

Lines changed: 17 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,17 @@
1010
},
1111
"private": true,
1212
"dependencies": {
13-
"@angular/animations": "^19.0.0",
13+
"@angular/animations": "^19.0.0",
1414
"@angular/cdk": "^19.0.0",
1515
"@angular/common": "^19.0.0",
1616
"@angular/compiler": "^19.0.0",
17-
"@angular/core": "^19.0.0",
17+
"@angular/core": "^19.0.0",
1818
"@angular/forms": "^19.0.0",
1919
"@angular/material": "^19.0.0",
2020
"@angular/platform-browser": "^19.0.0",
2121
"@angular/platform-browser-dynamic": "^19.0.0",
2222
"@angular/router": "^19.0.0",
23+
"nxt-json-view": "^19.0.0",
2324
"prettier": "^3.2.5",
2425
"rxjs": "~7.8.0",
2526
"shiki": "^1.2.0",

src/app/app.component.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import { CdkMenuModule } from '@angular/cdk/menu';
22
import {
33
Component,
4-
effect,
5-
ElementRef,
64
HostListener,
75
inject,
86
resource,
97
signal,
108
VERSION,
11-
viewChild,
129
} from '@angular/core';
1310
import { FormsModule } from '@angular/forms';
1411
import { ActivatedRoute, Router } from '@angular/router';
@@ -19,13 +16,15 @@ import { formatAngularTemplate } from './prettier';
1916
import { Template, templates } from './templates';
2017
import { unzip, zip } from './zip';
2118
import { DomSanitizer } from '@angular/platform-browser';
19+
import { JsonPipe } from '@angular/common';
20+
import { JsonViewComponent, JsonViewModule } from 'nxt-json-view';
2221

2322
// Please don't blame for what you're gonna read
2423

2524
@Component({
2625
selector: 'app-root',
2726
standalone: true,
28-
imports: [FormsModule, CdkMenuModule, MatIcon],
27+
imports: [FormsModule, CdkMenuModule, MatIcon, JsonViewModule],
2928
template: `
3029
<header>Angular Template Compiler - based on {{ version }}</header>
3130
<main>
@@ -94,6 +93,11 @@ import { DomSanitizer } from '@angular/platform-browser';
9493
<code>renderFlag: 1=create, 2=update</code>
9594
9695
<hr />
96+
-
97+
<!-- <pre><code>{{ details() | json }}</code></pre> -->
98+
-
99+
<nxt-json-view [data]="details()" [levelOpen]="5"></nxt-json-view>
100+
97101
<footer>
98102
<div>
99103
<h2>About</h2>
@@ -152,6 +156,7 @@ export class AppComponent {
152156
protected readonly template = signal(templates[0].content);
153157
protected readonly errors = signal<{ message: string; line: number }[]>([]);
154158
protected readonly currentTemplate = signal(templates[0].label);
159+
protected readonly details = signal<any>(null);
155160

156161
protected readonly compiledTemplate = resource({
157162
request: this.template,
@@ -178,7 +183,9 @@ export class AppComponent {
178183
}
179184

180185
async compileTemplate(template: string) {
181-
const { output, errors } = await compileFormatAndHighlight(template);
186+
const { output, errors, details } =
187+
await compileFormatAndHighlight(template);
188+
this.details.set(Object.fromEntries(details.entries()));
182189
this.errors.set(
183190
errors?.map((e) => {
184191
return { message: e.msg, line: e.span.start.line };

src/app/compile.ts

Lines changed: 145 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { HighlighterGeneric, createHighlighter } from 'shiki';
33

44
import { formatJs } from './prettier';
55
import { Context, Printer } from './printer';
6+
import { map } from 'rxjs';
67

78
let highlighter: HighlighterGeneric<any, any>;
89

@@ -16,67 +17,87 @@ export async function initHightlighter() {
1617
interface CompileOutput {
1718
output: string;
1819
errors: ng.ParseError[] | null;
20+
details: any;
1921
}
2022

21-
export function compileTemplate(templateStr: string): CompileOutput {
23+
export async function compileFormatAndHighlight(
24+
template: string,
25+
): Promise<CompileOutput> {
26+
const { output: unformated, errors, details } = compileTemplate(template);
27+
28+
const formatted = await formatJs(unformated);
29+
const highlighted = highlighter.codeToHtml(formatted, {
30+
lang: 'javascript',
31+
theme: 'github-dark',
32+
});
33+
34+
return { output: highlighted, errors, details };
35+
}
36+
37+
function compileTemplate(templateStr: string): CompileOutput {
2238
const constantPool = new ng.ConstantPool();
2339
const template = ng.parseTemplate(templateStr, 'template.html', {
2440
preserveWhitespaces: false,
2541
});
2642

2743
const CMP_NAME = 'TestCmp';
2844

29-
const out = ng.compileComponentFromMetadata(
30-
{
31-
name: CMP_NAME,
32-
isStandalone: true,
33-
selector: 'test-cmp',
34-
host: {
35-
attributes: {},
36-
listeners: {},
37-
properties: {},
38-
specialAttributes: {},
39-
},
40-
inputs: {},
41-
outputs: {},
42-
lifecycle: {
43-
usesOnChanges: false,
44-
},
45-
hostDirectives: null,
46-
declarations: [],
47-
declarationListEmitMode: ng.DeclarationListEmitMode.Direct,
48-
deps: [],
49-
animations: null,
50-
defer: {
51-
dependenciesFn: null,
52-
mode: ng.DeferBlockDepsEmitMode.PerComponent,
53-
},
54-
i18nUseExternalIds: false,
55-
interpolation: ng.DEFAULT_INTERPOLATION_CONFIG,
56-
isSignal: false,
57-
providers: null,
58-
queries: [],
59-
styles: [],
60-
template,
61-
encapsulation: ng.ViewEncapsulation.Emulated,
62-
exportAs: null,
63-
fullInheritance: false,
64-
changeDetection: null,
65-
relativeContextFilePath: 'template.html',
66-
type: {
67-
value: new ng.WrappedNodeExpr(CMP_NAME),
68-
type: new ng.WrappedNodeExpr(CMP_NAME),
69-
},
70-
typeArgumentCount: 0,
71-
typeSourceSpan: null!,
72-
usesInheritance: false,
73-
viewProviders: null,
74-
viewQueries: [],
45+
const meta: ng.R3ComponentMetadata<any> = {
46+
name: CMP_NAME,
47+
isStandalone: true,
48+
selector: 'test-cmp',
49+
host: {
50+
attributes: {},
51+
listeners: {},
52+
properties: {},
53+
specialAttributes: {},
54+
},
55+
inputs: {},
56+
outputs: {},
57+
lifecycle: {
58+
usesOnChanges: false,
7559
},
60+
hostDirectives: null,
61+
declarations: [],
62+
declarationListEmitMode: ng.DeclarationListEmitMode.Direct,
63+
deps: [],
64+
animations: null,
65+
defer: {
66+
dependenciesFn: null,
67+
mode: ng.DeferBlockDepsEmitMode.PerComponent,
68+
},
69+
i18nUseExternalIds: false,
70+
interpolation: ng.DEFAULT_INTERPOLATION_CONFIG,
71+
isSignal: false,
72+
providers: null,
73+
queries: [],
74+
styles: [],
75+
template,
76+
encapsulation: ng.ViewEncapsulation.Emulated,
77+
exportAs: null,
78+
fullInheritance: false,
79+
changeDetection: null,
80+
relativeContextFilePath: 'template.html',
81+
relativeTemplatePath: 'template.html',
82+
type: {
83+
value: new ng.WrappedNodeExpr(CMP_NAME),
84+
type: new ng.WrappedNodeExpr(CMP_NAME),
85+
},
86+
typeArgumentCount: 0,
87+
typeSourceSpan: null!,
88+
usesInheritance: false,
89+
viewProviders: null,
90+
viewQueries: [],
91+
};
92+
93+
const out = ng.compileComponentFromMetadata(
94+
meta,
7695
constantPool,
7796
ng.makeBindingParser(ng.DEFAULT_INTERPOLATION_CONFIG),
7897
);
7998

99+
const details = foo(meta, new ng.ConstantPool());
100+
80101
const printer = new Printer();
81102
let strExpression = out.expression.visitExpression(
82103
printer,
@@ -89,19 +110,86 @@ export function compileTemplate(templateStr: string): CompileOutput {
89110
strExpression += `\n\n${strStmt}`;
90111
}
91112

92-
return { output: strExpression, errors: template.errors };
113+
return { output: strExpression, errors: template.errors, details };
93114
}
94115

95-
export async function compileFormatAndHighlight(
96-
template: string,
97-
): Promise<CompileOutput> {
98-
const { output: unformated, errors } = compileTemplate(template);
116+
function foo(meta: ng.R3ComponentMetadata<any>, constantPool: ng.ConstantPool) {
117+
let allDeferrableDepsFn: ng.ReadVarExpr | null = null;
99118

100-
const formatted = await formatJs(unformated);
101-
const highlighted = highlighter.codeToHtml(formatted, {
102-
lang: 'javascript',
103-
theme: 'github-dark',
119+
const tpl = ng.ingestComponent(
120+
meta.name,
121+
meta.template.nodes,
122+
constantPool,
123+
meta.relativeContextFilePath,
124+
meta.i18nUseExternalIds,
125+
meta.defer,
126+
allDeferrableDepsFn,
127+
meta.relativeTemplatePath,
128+
/* enableDebugLocations */ false,
129+
);
130+
131+
const res: Map<string, any> = new Map();
132+
133+
res.set('initial state', {
134+
phase: 'Initial state',
135+
units: [...tpl.units].map((unit) => mapUnit(unit, tpl, constantPool)),
136+
});
137+
138+
for (const phase of ng.phases) {
139+
if (
140+
phase.kind === ng.CompilationJobKind.Tmpl ||
141+
phase.kind === ng.CompilationJobKind.Both
142+
) {
143+
// The type of `Phase` above ensures it is impossible to call a phase that doesn't support the
144+
// job kind.
145+
phase.fn(tpl as ng.CompilationJob & ng.ComponentCompilationJob);
146+
res.set(phase.fn.name, {
147+
phase: phase.fn.name,
148+
units: [...tpl.units].map((unit) => mapUnit(unit, tpl, constantPool)),
149+
});
150+
}
151+
}
152+
153+
res.set('Final state', {
154+
phase: 'Final state',
155+
units: [...tpl.units].map((unit) => mapUnit(unit, tpl, constantPool)),
104156
});
105157

106-
return { output: highlighted, errors };
158+
return res;
159+
}
160+
161+
function mapUnit(
162+
unit: ng.ViewCompilationUnit,
163+
tpl: ng.ComponentCompilationJob,
164+
constantPool: ng.ConstantPool,
165+
) {
166+
return {
167+
create: [...unit.create].map((create) => mapOp(create, tpl, constantPool)),
168+
update: [...unit.update].map((update) => mapOp(update, tpl, constantPool)),
169+
170+
// create: [...unit.create],
171+
// update: [...unit.update],
172+
ops: [...unit.ops()].map((op) => mapOp(op, tpl, constantPool)),
173+
};
174+
}
175+
176+
function mapOp(
177+
op: ng.CreateOp | ng.UpdateOp,
178+
tpl: ng.ComponentCompilationJob,
179+
constantPool: ng.ConstantPool,
180+
) {
181+
const obj = {
182+
kind: ng.OpKind[op.kind],
183+
//fn: null, // ng.emitTemplateFn(tpl, constantPool),
184+
};
185+
186+
if ('tag' in op) {
187+
Object.assign(obj, { tag: op.tag });
188+
}
189+
190+
if ('sourceSpan' in op) {
191+
Object.assign(obj, { sourceSpan: op.sourceSpan?.toString() });
192+
}
193+
194+
return obj;
107195
}

0 commit comments

Comments
 (0)