エムオーテックス株式会社が運営するテックブログです。

これまでの View Engine と Ivy について

はじめに

こんにちは、LANSCOPE クラウド版 フロントエンド開発チームの南です。
LANSCOPE クラウド版の管理コンソールは、TypeScript + Angularで実装しています。
つい先日、クラウド版はバージョン4.5.0.0をリリースしましたが、そのリリースにAngular 12へのバージョンアップも含まれています。

Angular 8から新世代のビューエンジンとしてIvyが導入されましたが、先日公開されたバージョン13でIvyへの移行が完了したようです。

今回はそのビューエンジンについてご紹介していきます。
Ivyがホットだったのは数年前の話になるので、今更感があり若干気が引けますが、フロントエンドやAngularなどに興味を持っていただけたらと思います。

Ivyについて

Ivy とはAngularの第三世代のビューエンジンとなります。
それまではRenderer、Renderer2と呼ばれていました。
ビューエンジンが何をするものかというと、HTML で書かれたコードを実際のDOMとして生成します。

このDOMを生成するまでの過程がIvyとそれまでのビューエンジンとで変わってきます。

Renderer2の場合

出典:Angularの次世代ビューエンジン「Ivy」とは何か? 3つの特徴と新たにできるようになること - ログミーTech

Renderer2の場合、テンプレート(HTML)が解析されて、テンプレートデータとなります。
テンプレートデータは抽象構文木のような形になっているようです。
そして、このテンプレートデータが実行時に変換されてDOMが生成されます。
コンパイル段階では、DOMを作る関数の生成まで行うことをAoT compileと呼び、実行時にはその関数を呼び出してDOMを作るだけ、というのが Renderer2のアーキテクチャになっています。
(実行時にブラウザでコンパイルを行うのをJIT Compileと呼びます)

出典:Angularの次世代ビューエンジン「Ivy」とは何か? 3つの特徴と新たにできるようになること - ログミーTech

具体的に上のようなHTMLを書くと、下のような人には少し判斷しづらい viewDef(... から始まるテンプレートデータに変換されます。

Ivy の場合

Ivyの場合、DOMが生成されるまでの過程が一部省略され、テンプレートからテンプレートインストラクションというものに変換され、それがDOMとなります。

出典:Angularの次世代ビューエンジン「Ivy」とは何か? 3つの特徴と新たにできるようになること - ログミーTech

1番変わるのは生成されるコードの性質です。
生成されるコードが人間にも読みやすい構造になっています。
elementStartやtextのようにDOMの要素を作ったり、テキストノードを作ったりと、処理内容が理解しやすいようなコード(テンプレートインストラクション)が生成されるようになっています。

また、Aot Compileの場合、このテンプレートインストラクションを生成するところまでをコンパイル段階で行い、実行時にはこのテンプレートインストラクションが実行され、DOMが生成されます。

Ivy のレンダリング手法

Ivyでのレンダリング手法はincremental domと呼ばれています。
よく、仮想DOMと比較されているため、違いにも触れながら書いていきます。

以下がincremental domにおけるレンダリングの流れとなります。

  1. 仮想DOMと同様、DOMの木構造を模したPOJOを持っておく(仮にこれを「状態オブジェクト」とする)
  2. DOMを更新する必要が生じた時、レンダリング関数を呼び出す
  3. レンダリング関数は、incremental node functionと呼ばれる関数を呼び出す
  4. incremental node functionは、状態オブジェクトを参照し、更新の必要がある部分を判断し、DOM APIでそれを適用する

例えば、以下のようなレンダリングする関数があったとします。

function render(state) {
  elementOpen('div');
    text(`Hello ${state.name}`);
  elementClose('div');
}

この、elementOpen、text、elementCloseがincremental node functionにあたります。
最初にレンダリングを行う際、以下のような状態オブジェクトが生成されたとします。

{
  "name": "div",
  "children": ["Hello world"]
}

この状態からchildren["Hello japan"]に変更されたとすると、レンダリング関数renderが呼ばれ、elementOpen、text...とincremental node functionが呼ばれていきます。
textが呼ばれた際、引数で渡された"Hello japan"と比較して更新する必要があるため、DOM APIで実行されるという流れです。

この流れにおいて、仮想DOMとの違いは以下になります。

  • 更新比較のために新しい状態オブジェクトが生成されない
  • レンダリング関数内で呼び出されたincremental node functionがインクリメンタルにDOMの更新を行う

したがって、新しい状態オブジェクトが作成されないことでレンダリング時のメモリ割り当て量が削減されます。

Ivy の利点

最後にIvyの利点についてまとめます。

  • バンドルサイズの縮小
    バンドルされる際、使われていないコードはデッドコードとして削除され、バンドルサイズが削減されるようになりました。
    これまではTreeShakingできなかった部分まで削除することができるようになったということです。
  • ビルド時間の短縮
    変更したファイルだけがコンパイルされるインクリメンタルビルドにより、ビルド時間が短縮されるようになりました。
  • 生成されるコードの可読性向上
    デバッグ時やスタックトレースなどでコードが読みやすくなったようです。

最後に

LANSCOPE クラウド版で Angular 8 に対応したときは、まだ Ivy を有効にしていませんでしたが、Angular 12 と比較するとたしかにビルド時間が短縮された実感がありました。

また、今回の内容はAngularフレームワークの裏側の話であり、Angularのサードパーティライブラリを開発しない限りは、Ivyについて詳しく知っていなくても開発にはほぼ支障はないですが、知っておいて損はないと思います。

本ブログでのフロントエンドに関する投稿は初めてで、少々突飛な内容だったかと思いますが、次回以降は開発で工夫したことや役立ちそうなことを投稿できたらと思います。
今後もぜひご覧ください。