← 解説「AOTコンパイル — テンプレートは命令列に変換される」に戻る
サンプルコードで身につける: AOTコンパイル — テンプレートは命令列に変換される
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1バインディング記法と命令の対応表
コースの序盤で学んだテンプレート記法は、それぞれ決まった命令に変換されます。この対応を知ると「記法を覚える」から「変換先を知っている」に理解が一段深まります。
// ── テンプレート記法 → コンパイル後の命令(概念)──
//
// {{ title() }} → ɵɵtextInterpolate(ctx.title())
// テキストノードの中身を差し替える
//
// [src]="url()" → ɵɵproperty('src', ctx.url())
// DOMプロパティへ値を流し込む
//
// (click)="save()" → ɵɵlistener('click', ...)
// 作成モードで1回だけリスナーを登録する
//
// @if / @for → ɵɵtemplate(...) + コンテナ
// 中身を「下書き」として登録し、条件で実体化する
// (レガシー編で学んだ ng-template の機構がこれ)
2作成モードと更新モードのタイムライン
同じテンプレート関数が、初回と2回目以降で別のブロックを実行します。ボタンを3回押したときに何がどれだけ走るかを時系列で見ると、「2回目以降は差し替えだけ」が具体的に分かります。
// template: '<p>{{ count() }}</p>' の一生
//
// 初回表示:
// rf & 1(作成): p要素を作る / テキストノードを作る
// rf & 2(更新): count() の値 0 を書き込む
//
// count.set(1):
// rf & 2(更新)だけ: 前回値0 ≠ 今回1 → テキスト差し替え
//
// count.set(1) をもう一度:
// rf & 2(更新)だけ: 前回値1 = 今回1 → DOMに触らず終了
//
// p要素を「作る」処理は初回の1回しか実行されない。
// Virtual DOM のような毎回のツリー構築・比較は存在しない
3コンパイル済みコードを自分の目で見る
ビルド成果物を覗くと、ɵɵ付きの命令が本当に入っていることを確認できます。難読化を切ってビルドすると読みやすくなります。一度見ておくと「テンプレートはJSになる」が実感に変わります。
# 難読化なしでビルドすると成果物が読める
ng build --configuration development
# 成果物から自分のコンポーネントを探す
grep -o 'ɵɵelementStart([^)]*)' dist/my-app/browser/main-*.js | head
# ɵɵelementStart(0, "h1")
# ɵɵelementStart(2, "button", 0)
# ...
# テンプレートに書いた h1 や button が、
# 「要素を作る関数呼び出し」としてJSの中に並んでいる。
# index.html には相変わらず <app-root> しか無い
4AOTとJIT — コンパイルはいつ走るか
現在はAOT(ビルド時コンパイル)が標準ですが、古いプロジェクトや動的なテンプレート生成ではJIT(実行時コンパイル)に出会うことがあります。違いは「変換をどこでやるか」だけで、変換内容は同じです。
// ── AOT(現在の標準)──
// ビルド時: テンプレート → 命令列 に変換して同梱
// 実行時: 変換済みの関数を実行するだけ
// 利点: 起動が速い・バンドルが小さい(コンパイラ不要)
// テンプレートの誤りがビルド時に見つかる
//
// ── JIT(レガシー/特殊用途)──
// ビルド時: テンプレートを文字列のまま同梱
// 実行時: ブラウザ内でコンパイラが変換してから実行
// 痕跡: 古い設定の "aot": false や
// @angular/compiler の実行時import
//
// テンプレートの型チェック(strictTemplates)がビルドで効くのは
// AOTが「テンプレートもコンパイル対象」として扱うから
5ɵɵ入りスタックトレースの読み方
実行時エラーのスタックトレースにɵɵ関数が並んでいたら「テンプレート由来のエラー」というサインです。デバッグ編で学んだエラーの読み方に、内部機構の知識を足すとさらに絞り込めます。
// ERROR TypeError: Cannot read properties of undefined ('name')
// at UserCardComponent_Template ← テンプレート関数の中!
// at executeTemplate
// at refreshView ← 更新モードの実行中
// at detectChangesInternal ← 変更検知から呼ばれた
//
// 読み取れること:
// ・XxxComponent_Template → どのコンポーネントのテンプレートか
// ・refreshView 系が並ぶ → 更新モード(=変更検知中)のエラー。
// 初回表示は通ったが、ある更新のタイミングで値が壊れた
// ・{{ user.name }} で user が undefined になる瞬間がある、
// という仮説が立つ → @if (user(); as u) でガードする