Ionicコンポーネントの読み込みタイミングを深堀りする
本記事は Ionic Framework / Capacitor / Stencil Advent Calendar 2021 Advent Calendar 2021 の3日目の記事です。
Ionicの各コンポーネントはWeb Componentsでつくられてて、これはAngularやReactといったフレームワークと完全に分離しています。たとえば、 @ionic/angular
パッケージはこのWeb Components( @ionic/core
が中に入っているAngular向けのラッパーであり、ラッパーを通じてWeb Componentsを呼び出し、組み立てる仕組みです。
Ionicコンポーネントの遅延読み込みを理解する
そして、AngularやReactが遅延読み込みする機能をもっているのと同様に、Ionicコンポーネントもまた別機構として遅延読み込みする機構を持っています。 仕組みを解説した記事がありますので 、そこから当該部分を翻訳してご紹介します。
遅延読み込みを有効にすると、コンポーネントの1つがDOMに追加されると、プロキシコンポーネントがコンポーネントのコアロジックに要求し、非同期的にホスト要素にオンデマンドで接続します。ランタイムのアーキテクチャ全体が、プロパティの取得や設定、完全にハイドレイトされる前のイベントのキャッチ、さらにはロードされる前のコンポーネントのちらつきが起こらないために、Webコンポーネントを非同期にhydratedすることを可能にしていることは重要です。
さらに、Stencilはビルド時に各コンポーネントの静的な分析を行い、どのコンポーネントがすでに一緒に動作していて、一緒にバンドルされるべきかを把握しています。この分析により、不要なHTTPリクエストを避けるために、各バンドルリクエストには、必要とされることがわかっているコンポーネントがすでに含まれています。さらに、コンポーネントはモジュールをロードするためにブラウザのAPIを使用しているため、ランタイムやカスタム設定を追加することなく、ネイティブモジュールのプリロードを利用することができます。
では実際に遅延読み込みする様子を確認してみましょう。devツールを利用するとわかりやすいです。まず、起動時にmain.jsを読み込みます。手元のビルドファイルをみるとわかりますが、この中でWeb Componentsの定義をしています。
const X=[],xe=I.exclude||[],Fe=z.customElements ... $lazyBundleIds$=me[0],!xe.includes(Xe)&&!Fe.get(Xe)&&(X.push(Xe),Fe.define(Xe,Jr(ft,Le,1))
minifiyしたbundleなのでわかりにくいですが、 変数 Fe
にcustomElementsを代入して、 Fe.define
でWeb Componentsを定義しています。ここからわかるように、定義箇所と各コンポーネントのファイルは別箇所にあるので、Ionic/Angularでは customElements.whenDefined
で各コンポーネントのファイルの読み込みを検知することができません。
そして、各コンポーネントのファイルが読み込まれるのが、「コンポーネントがDOMツリーにはじめて出現したタイミング」です。たとえばこれは ion-searchbar
のものですが、DOM上に出現したタイミングで以下のようなファイルを読み込みます。
同時にスタイルをあてるために、headerにstyleが追加されます。
これはとてもよくできた仕組みで、読み込みが行われるのは初回のみで、また読み込み待ちの時にCSSが当たる、当たらないなどのカクツキが起きないように .hydrated
クラスを使うことで表示非表示をコントロールしています。
それでもカクつくのを乗り越える
とはいえ、どれだけ効率的な運用をしていても、遅延をゼロに抑えることはできません。以下をみてみてください。
一瞬すぎて要因は推測になりますが、 ion-searchbar
には connectedCallback
(Web Componentsのライフサイクルでコンポーネントが表示された時に実行されるメソッド) が設定されています。
コンポーネントがロードされると .hydrated
になってそのあとこのイベントが実行されたためかなと思っているのですが、とりあえず要因は置いておいて、カクツキが存在することは確かです。
では、これはどうやって乗り越えたらいいでしょうか。ヒントはまさに「コンポーネントの1つがDOMに追加されると」のところで、つまりは先にDOMに読み込んでやれば、ロードと connectedCallback
が先に実行されます。一番わかりやすい方法はこちらです。
<div style="display: none">
<ion-searchbar></ion-searchbar>
</div>
index.htmlに先に読み込むコンポーネントをdisplay:none;で置いておくことですね。 ただ、「先に読み込みたいけど特定シナリオでしか読み込まないので常に読み込みを行いたくない」場合もあると思います。その場合、それより前のコンポーネントで動的にDOMを組み立ててしまうのもひとつですよね。
ionViewDidEnter() {
const preloadArea: HTMLElement = document.getElementById('preload');
preloadArea.appendChild(document.createElement('ion-searchbar'));
}
おわりに
パフォーマンスチューニングにおいて、devツールでネットワークをチェックして、何をどの順番に読み込むかを確認することは重要です。もちろんこういったことを知らないままでも実装することは可能ですが、次のステップとして、ブラウザの気持ちになりながら実装するとより理解を深めることができるようになります。
それではまた。
Discussion