lacolaco's marginalia

Angular: Rustで書かれる新しいコンパイラ ngp(Angular Preprocessor)の解説

Angular開発チームから新しい計画が発表された。TypeScript 7への対応のためにAngularコンパイラをRustで書き直すというものだ。

はたしてこの移行計画は何を意味するのか、フレームワークを利用する開発者にどのような影響があるのか。公式ブログの内容を踏まえつつ補足する。詳細は原文を読むことをおすすめする。

Angularコンパイラの役割

「Angularコンパイラ」とはそもそもどのような役割をもっているかをおさらいしておこう。

Angularで書かれたアプリケーションは、TypeScriptのコードをそのままJavaScriptへ変換するだけでは動かない。コンポーネントに付けられた @Component や @Directive などのデコレータを読み取り、HTMLテンプレートとクラスを結び付け、ブラウザ上で効率よく実行できるコードへ変換する必要がある。このAngular固有の処理を担うのがAngularコンパイラだ。

現在のAngularコンパイラである ngc は、TypeScriptコンパイラの上に構築されている。通常のTypeScriptの型チェックやJavaScriptへの変換に加え、Angularのメタデータ解析、テンプレートのコンパイルなどを同じパイプラインの中で行う。

特に重要なのがテンプレートの型チェックだ。たとえばテンプレート内で存在しないプロパティを参照したり、イベントハンドラーへ誤った型の値を渡したりした場合、Angularコンパイラはビルド時に問題を検出できる。TypeScriptコード上でテンプレートは文字列に過ぎないが、コンパイルによってTypeScriptコードが生成され、型チェックの対象になる。

単純化すれば、AngularコンパイラはHTMLテンプレートを解析してTypeScriptコードを生成するためのツールだ。そのコード生成によって本来は文字列でしかないHTMLテンプレートに型チェックが可能となり、実行時にも高速に動作するコードへと最適化ができる。つまり、現在のAngularの開発者体験、アプリケーションのユーザー体験を支える中核的なツールである。

なぜ ngp が必要なのか

ngc はTypeScriptコンパイラの内部APIと密接に統合されており、TypeScript 7でコンパイラがGoによるネイティブ実装へ移行したことにより、現在のngcのアーキテクチャから移行しなければならなくなった。

これまでのアーキテクチャの問題は、TypeScriptからJavaScriptへ変換するコンパイラとAngular固有のコード生成・変換を行うパイプラインがひとつに結びついていることだ。本来は異なる2つの責務であり、これを分離したうえで統合できれば、TypeScriptコンパイラの実装に依存しないAngular独自のパイプラインを構築できる。

今回発表された ngp(Angular Preprocessor)はその名のとおり、コンポーネントやディレクティブなどAngular固有の扱いが必要なコードを先に処理するものだ。そして、実行用のTypeScriptコードと、型チェック用のTypeScriptコードを生成する。後続のTypeScriptコンパイラやesbuildなどは、その出力を通常のTypeScriptとして扱えるようになる。モノリシックなひとつのコンパイラではなく、複数のツールを統合したビルドツールチェインとして再構築するわけだ。

https://blog.angular.dev/an-update-on-angulars-typescript-7-powered-compiler-9619a35e2b0a
https://blog.angular.dev/an-update-on-angulars-typescript-7-powered-compiler-9619a35e2b0a

なぜRustなのか

ngp はTypeScriptコンパイラから分離されたため、実装上の制約としてTypeScriptコンパイラと同じ言語を選ぶ必要がなくなった。TypeScript/JavaScriptである必要もないし、Goである必要もない。

この分野にはいくつかOSSのツールがあるが、中でもVoidZeroによって開発されている Oxc(Oxidation Compiler)が代表的だ。Rustで書かれたJavaScriptのパースとコンパイルのためのネイティブツールチェインで、パースやASTの走査、意味解析、コード変換といった処理のための成熟したAPIを備えている。Angularが新たにngpを開発するにあたり、エコシステムとの相互運用性の観点からも、すでに信頼できるライブラリがあるRustとOxcを実装基盤として採用することが選ばれた。

フレームワーク利用者への影響

ngcからngpへ移行することにより、フレームワーク利用者にどのような影響があるだろうか。だいたい想像がつくが、ほとんどの場合では何もないだろう。そもそもngc自体がAngular CLIのビルドパイプラインの内部モジュールに過ぎず、直接利用しているユーザーはごく少数のはずだ。ng buildコマンドの裏側で呼び出される処理が変わったところで、利用者の側でわかるのはちょっと速くなったかも?という程度だろう。

Googleには社内に数千単位のAngularプロジェクトが存在すると言われている。そしてそのCI環境によって、Angularは現実のアプリケーションのユースケースに沿ってテストされている。もしngpへの移行が典型的なプロジェクトで破壊的変更を起こすなら、間違いなくGoogleのCIが先に落ちる。エッジケースでは振る舞いが変わる可能性はあるが、私の見立てでは、ngpの振る舞いの心配よりもTypeScript 7へのアップグレードに伴う変更の影響を気にしたほうがいいだろうと思う。

一方で、Angularライブラリのプロジェクトについてはngpの動きに注目しておいたほうがいいだろう。Ivyコンパイラへの移行のときもそうだったが、ライブラリに同梱するファイルについてはコンパイラの影響を大きく受ける。ngcでビルドされたライブラリはngpでも読める後方互換性は保つだろうが、逆にngpでビルドしたライブラリはngcで使えなくなる可能性は高い。両方をサポートするビルドが可能になるのかどうかまだわからないが、公式からの移行計画をチェックしておく必要がある。

まとめ

ngpはまだプロトタイプ段階だが、TypeScript 7への移行のブロッカーとなっている以上はそれほど次の展開は遠くないだろう。独自実装でフルスクラッチするのではなく、Oxcを利用する選択をしたのも、相互運用性や信頼性の観点もあるだろうが、それなりに急いでいるというのもあるのではなかろうか。

とはいえ、そもそもAngular CLIはすでに内部でViteを使っており、テスト基盤はVitestになっており、さんざんVoidZeroのツール群にはお世話になっている。そこにOxcも加わったところで何も驚くことはなく、むしろ彼らのツールの進化がAngularにも恩恵をもたらすことが増えるだろう。落ち着いて次のアナウンスに期待しておこう。