See how Stencil fits into the entire Ionic Ecosystem ->
Stencil is part of the Ionic Ecosystem ->

Stencilを使ったWebコンポーネントの遅延読み込みの仕組み

Stencil Lazy Loading

1つのファイルにバンドルされた大規模なコンポーネントライブラリをアプリで読み込むと、パフォーマンス、特に起動時間が犠牲になることはよくあります。すべてのコンポーネントを簡単に利用できるように1つにまとめるか、分割して起動を速くすることを選択するか、その両方を手に入れるのは難しいことです。多くの人がそう感じているはずです。

ファーストビューを表示するために大量のJavaScriptをダウンロードして解析することは、多くのアプリが苦労しているところですが、多くの場合、「遅延読み込み」がこの問題の素晴らしい解決策となります。遅延読み込みとは、エンドユーザーが必要とするときにのみコンポーネントをロードすることを指す言葉で、アプリの初期化を高速化するためによく使われる手法です。

カスタムコンポーネントライブラリやデザインシステムの構築に使用されるWebコンポーネントコンパイラであるStencilは、もともと Ionic Framework のために作られました。そして、Ionicの課題は常に、Material DesignとiOSの両方に対応した大規模なUIコンポーネントのライブラリを簡単に配布しつつ、Progressive Web Appsのパフォーマンスを維持し、高速な起動を実現することでした。この問題を解決するために、Ionic は Web Components を使用するようになりました。Web Components は、多くの方法で配布や利用することができますが、それにはどれも長所も短所もあります。しかし、Web Componentsを使うだけでは、起動やファイルサイズの問題は解決しません。

Stencilの遅延読み込みが「どのように」機能するのか、1つのScriptが大きなライブラリへの単一のエントリーポイントとして機能するのか、という質問をよく受けます。しかしさらに、なぜIonicはこのアーキテクチャに移行したのか、ファイルサイズや起動時のペナルティなしに大規模なUIライブラリを簡単に提供できるのか、といったことも聞かれます。この記事では、それぞれの決定の背景にある理由を少し詳しく説明したいと思います。

*免責事項: この記事は、コンポーネントを遅延読み込みするための唯一無二の方法であると主張しているわけではありません。実際のところ、遅延読み込みには多くの方法があります。これは、Stencilのやり方であり、Stencilの技術を使ってIonicコンポーネント、開発者、ユーザーがどのような恩恵を受けているかを示しています。

ScriptタグとCDN

Webコンポーネントを使用する最も簡単な方法の1つは、そのコンポーネントが使用されるWebページにScriptタグを追加することです。Scriptが読み込まれると、自動的にカスタム要素が定義され、すでにページ上にあるカスタム要素と、それ以降にページ上で作成されたカスタム要素が ハイドレート されます。簡単でしょう?

単一のコンポーネントであれば、これは素晴らしい機能です。しかし、Ionicの場合、あるいは大規模なUIライブラリやデザインシステムの場合、すべてのコンポーネントを含む単一のスクリプトは、ファイルサイズやアプリの起動時間を許容するには大きすぎるという課題があります。

ファーストペイントではほんの一握りのコンポーネントしか使わないかもしれないのに、すべてのコンポーネントをすべて1つのファイルにまとめてしまうと、ユーザーがアプリの最初のページを見る前に、ライブラリ全体をダウンロードしなければならないというペナルティが発生します。これについては The cost of JavaScript in 2019 で徹底的に検証されており、時間をかけて読む価値があることは間違いありません。

*ただし、IonicとStencilのコンポーネントは、従来のバンドルラーにもインポートできることに注意してください。この記事では、スタンドアロンのScriptタグを使用した場合の遅延ロードの動作を確認しています。

ブラウザのネイティブ機能を使う

幸運なことに、ブラウザには、Ionic(Stencilを使用)が利用できる、非常に価値があり、あまり知られていないいくつかのAPIがすでに搭載されています。最大のものは、ネイティブの ESモジュールインポートダイナミックインポート です。

従来のWeb開発では、ソースコードが、認識できないJavaScriptの塊に変換され、それを理解するために最も細かく調整されたソースマップが必要になることに慣れていました。最も一般的なものは、標準化された import キーワードをコールバックの深いネストに変換して、JavaScript の "モジュール" を ES5 環境(IE11 など)で読み込むために、しばしば非標準の CommonJS 形式に戻すことです。

ESモジュールがブラウザでネイティブにサポートされるようになったことで、import ステートメントをバンドルツールで別のものに変換する必要はなくなりました。むしろ、ブラウザは import を完全に理解し、バンドルツールのランタイムを使用せずにリクエストを実行できるようになりました。モジュールのバンドルロジックは、ランタイムではなくビルドタイムに限定することができるようになりました。これにより、ファイルサイズを削減し、モジュールのロードパフォーマンスを直接ブラウザの手に委ねることができます。

バンドルは貴重なツールであり、標準規格の進歩により、ほとんどの場合、バンドルのランタイムロジック(ユーザーのブラウザで実行される追加のJavaScript)は必要ないかもしれません。繰り返しになりますが、バンドルラーの ランタイム ロジックです。 ビルド時には、グループ化されるべきモジュールを1つのファイルにまとめることができるバンドルラーは不可欠です。しかし、バンドルされたファイルの出力は、純粋なESモジュールの形式にすることができ、ブラウザの高最適化されたスタックにネットワークリクエストや解析などを任せることができるようになりました。

Webコンポーネントライブラリの事前定義

大規模なコードベースがアプリの初期起動を遅らせている場合、当然ながら「コンポーネントを遅延読み込みすればいい」というシンプルな答えが返ってきます。しかし、コンポーネントの遅延読み込みには、バンドル、package.jsonやルーターの設定、各コンポーネント内に構築されたカスタムフレームワークロジックやラッパーなど、非常に複雑な要素が含まれていることが多く、Scriptタグを簡単に使用する能力がすぐに失われてしまいます。また、他の遅延読み込み・ソリューションでは、モジュールを正しくリクエストして読み込むために、ハードコードされたHTML属性値と、さらにカスタムされたランタイム・ロジックが必要になります。

Stencilでは、DOM内のすべてのコンポーネントを定義することを出力目標の一つとしていますが、重要なのは、コンポーネントのロジックを一切必要としないことで、定義済みのコンポーネントは、ロジックを持たない単なるプロキシコンポーネントです。Stencilの言葉で言えば、出力ターゲットは、開発者のソースコードを高度に最適化されたビルドに変換することができます。コンパイラにはいくつかの出力ターゲットがありますが、ここではデフォルトの遅延読み込みに焦点を当てます。

遅延読み込み出力ターゲットでは、コンポーネントの1つがDOMに追加されると、プロキシコンポーネントがコンポーネントのコアロジックを要求し、非同期的にホスト要素にオンデマンドで読み込みます。ランタイムのアーキテクチャ全体が、プロパティの取得や設定、完全にハイドレイトされる前のイベントのキャッチ、さらにはロードされる前のコンポーネントのフリッカーがないことの確認など、Webコンポーネントを非同期にハイドレイトすることを可能にしていることは重要です。

さらに、Stencilはビルド時に各コンポーネントの静的な分析を行い、どのコンポーネントがすでに一緒に動作していて、一緒にバンドルされるべきかを把握しています。このような知識があれば、不要なHTTPリクエストを避けるために、コンポーネントはすでに構築されており、各バンドルリクエストには、必要とされることがわかっているコンポーネントがすでに含まれています。さらに、コンポーネントはモジュールをロードするためにブラウザAPIを使用しているため、ランタイムやカスタム設定を追加することなく、ネイティブの モジュールプリロード を利用することができます。

ブラウザドリブンの遅延読み込み

従来のセットアップでは、開発者は通常、コンポーネントがエンドユーザーにどのように読み込まれるかをビルド時に推測し、バンドルラーの設定を通じて独自のエントリーモジュールを作成しなければなりません。Stencilのレイジーローディングでは、すべての作業がブラウザに直接任されます。ブラウザは、どの要素がDOMにあるかをすでに正確に知っており、すべてのエンドユーザーが好きなようにナビゲートできるので、最高の単一の真実の情報源となります。どのコンポーネントが読み込まれるべきかを判断するために、エンドユーザーが追加のJavaScriptランタイムをダウンロードしたり、そのためのすべてのロジックをダウンロードしたりする代わりに、ブラウザがどのコンポーネントが使用されているかを正確に教えてくれ、ブラウザ自身がリクエストを実行してくれます。

要素がどのようにDOMに現れるかは実際には重要ではありません。HTMLの中にすでにあったものでも、Angular IvyやReact.createElement('ion-toggle', null)、jQueryの$("body").append("<ion-toggle></ion-toggle>")、あるいはdocument.createElement('ion-toggle')のような標準的なWeb APIでレンダリングされたものでも同じです。

要素がDOMでどのようにレンダリングされたかは問題ではありません。いずれにしても、StencilはWeb標準やネイティブブラウザのAPIを使用して自分自身を遅延読み込みする方法を知っています。また、JavaScriptのランタイムを増やすのではなく、ロジックのほとんどをブラウザに委ねることで、コンポーネント自体のファイルサイズも最適化されています。

少し違う話になりますが、私が話を聞いて気に入っているのは、学生たちがIonicだけでウェブ開発を教えているという話です。フレームワークもビルドシステムもありません。ただ、プレーンなHTMLと1つのスクリプトタグを書くだけで、Ionicの100以上のコンポーネント すべてにアクセスし、それらをオンデマンドで遅延読み込みさせることができます。これらはすべて、Stencilのアーキテクチャによって容易になりました。

また、ESモジュールやダイナミックインポートを理解していないブラウザのために、Stencilは必要なポリフィルを備えたES5ビルドを自動的に生成し、それを必要とするブラウザ(IE11など)でのみダウンロードされるようになっています。

コンポーネントをつくることに注力して、それ以外はロボットに任せよう。

オーサリング、エクスポート、ポリフィル、バンドル、パッケージング、配布、利用などはすべて複雑な作業であり、特にエンドユーザーにとってパフォーマンスを維持しようとする場合はそうです。ツールは改良され、フレームワークは更新され、バンドラーは変更され、新しいAPIは古いAPIを陳腐化させます。これは、ランタイムフレームワークが動作方法を変更する場合に特に当てはまります。

特定のランタイムAPIにハードコードされた何百ものコンポーネントはすべて、もう一度書き直さなければなりません。「しなければならないこと」、「絶対にしてはいけないこと」、「してはいけないこと」、「ベストプラクティス」などの長いリストは日々変化していますが、結局のところ、私たちは皆、アプリのコンポーネントを楽しく作ろうとしているだけなのです。

このような理由から、StencilがIonicの迅速な移行を支援するために作られた原動力の一つとなっています。Ionicのコア開発者は、何百万ものアプリで使用される見栄えの良いコンポーネントを作成し、維持することに専念しています。Stencilのおかげで、コア開発者はコンポーネントの作成に時間を割くことができ、ロボットは他のすべてのものの最適化や新しい標準への適応に集中することができます。

結論

各フレームワークやバンドルラーは、モジュールを遅延読み込みするための独自の方法を持っており、独自の設定方法を持っています。これは、イノベーションや新しいアイデアにとって素晴らしいことなので、各ツールが独自の方法で行うことを歓迎します。

しかし、IonicがStencilの遅延読み込み機能を使用することで、実際にはブラウザのネイティブAPIを使用しているだけなので、Ionicは各フレームワークのカスタムビルドシステム、ツール、変更から解放されます。これにより、あらゆるフレームワーク、あらゆるバージョンのフレームワーク、またはフレームワークなしにUIライブラリを提供するというIonicの目標を達成しつつ、Web標準の上に座るコンポーネントを生成することができます。また、コア開発者がコンポーネントのオーサリングとメンテナンスに集中できる一方で、コンパイラは最適化とバンドルの作業に集中できます。

コンポーネントを簡単に配布するための複雑な作業をビルドタイムツールに移行し、開発者にとってはコンポーネントのオーサリングと共有を簡単に、エンドユーザーにとっては迅速に行うことができるようにするという目標に対して、Stencilがどのように取り組んでいるのか、その詳細を説明する一助となれば幸いです。