74. AOTコンパイル — テンプレートは命令列に変換される
このセクションでは、Angularで書いたコードがどう変換されてブラウザで動いているのか、という土台を掘り下げます。出発点はこの事実です: ブラウザはAngularのテンプレートを一切見ていません。ビルド時にAngularコンパイラ(ngtsc)が @Component のテンプレートを解析し、「DOMを組み立てるJavaScript命令の列」であるテンプレート関数に変換して、クラスの静的プロパティ(ɵcmp)として埋め込みます。これがAOT(Ahead-of-Time)コンパイルで、実行時のブラウザにはコンパイラを届けないので、その分軽く・速く・エラーはビルド時に見つかります。
変換されたテンプレート関数は2部構成です。作成モード(初回に1回だけ走る)は elementStart や text といった「要素を作る」命令、更新モード(変更検知のたびに走る)は「値を差し替える」命令だけを持ちます。つまり「箱を作る仕事」と「中身を入れ替える仕事」がコンパイル時点で分離されていて、2回目以降の描画では作成命令は一切走りません。Reactのように毎回仮想ツリーを作って前回と比較する方式(Virtual DOM)とは、ここが根本的に違います。
もう1つの設計思想が局所性です。各コンポーネントは他のコンポーネントと独立にコンパイルされ、そのテンプレートが実際に使う命令関数だけがバンドルに残ります(tree-shaking)。レガシーのセクションで「@if も *ngIf も同じ機構に落ちる」と学びましたが、その行き先がこの命令列の世界です。スタックトレースに ɵɵ 付きの関数名が出てきたら、「いまテンプレート由来のコードの中にいる」と読めるようになります。
サンプルコード(フレームワーク環境が必要なため表示のみ)
// 書いたコード
@Component({ template: '<h1>{{ title() }}</h1>' })
export class AppComponent {
title = signal('こんにちは');
}
// ビルド後の概念図: クラスにテンプレート関数が埋め込まれる
// AppComponent.ɵcmp = defineComponent({
// template: (rf, ctx) => {
// if (rf & 1) { // 作成モード: 初回だけ
// ɵɵelementStart(0, 'h1'); // 0番スロットに h1 を作る
// ɵɵtext(1); // 1番スロットにテキストノード
// ɵɵelementEnd();
// }
// if (rf & 2) { // 更新モード: 変更検知のたび
// ɵɵadvance(1);
// ɵɵtextInterpolate(ctx.title()); // 値の差し替えだけ
// }
// },
// });
学んだ概念を、素朴な例から実務寄りの例まで5つの具体例で確認します。
公式ドキュメントで詳しく ↗