Ionic 4はパフォーマンスを意識した構成となっており、様々な要素からパフォーマンスチューニングを行うことができます。本記事では、Ionic 4のStarter Template(@ionic/angular 4.5.0)をベースとしてパフォーマンスチューニングを行います。
この内容は7月7日のIonic Meetupでの登壇内容です。
https://ionic-jp.connpass.com/event/133896/
Note:
Eager Loading
は、ModuleをLazy Loadingしないことです。
結論
初期表示について
- ModuleのEager Loadingにより高速化します
- Ionic ComponentsのPreLoadにより高速化します
- Service WorkerのPreFetchは影響がありません
ページ遷移について
- ModuleをPreLoadによって高速化します
- Ionic ComponentsのPreLoadによって高速化します
- Service WorkerのPreFetchにより高速化の可能性あり
未検証
- rel="preload"によるIonIconの先読み
測定手法
サーバ環境
- サーバは高機能無料ホスティングサービス「Netlify」
- Netlifyのサブドメイン
- Asset optimizationは
Disable
- HTTP/2 Server Pushは未使用
測定
合成モニタリング
株式会社Spelldata 様にご協力いただき、 Catchpoint
を使って測定を行いました。Netilyのサーバに近いサンフランシスコを計測ノードに設定。
- 48時間計測(2019年6月19日12:00から48時間)
- 計測値 サンフランシスコ
- n=576 × 3ドメイン
- iPhone7 - 4G Emulate
- 単位:
ms
- https://qiita.com/rdlabo/private/d7b4bcbc0886f0175926 で検証結果をまとめています。
簡易計測
記事執筆用に、Google Chromeの標準機能についているAudits(Lighthouse)で測定後、 trace
より Performance
を確認し、そのtime及びレンダリング結果を利用しています。レンダリングがブロックされているなど明らかに差がある場合はこちらを利用しています。
仮説(高速化手法)
1. ModuleをEager Loadingすると速くなる
Angularでは、Module単位でLazy Loadingすることができます。
これにより初期bundleサイズを小さくすることができますので、規模の大きいアプリケーションの高速化に有効です。
反面、JavaScriptファイルを複数呼び出すことになりますので、初期表示に必要なModuleをLazy Loadingすると呼び出しが増えるので遅延している可能性があります。
2. Ionic ComponentsのPreLoad
Ionic ComponentsはすべてLazy Loadingで呼び出すようになっており、呼び出しのタイミングはHTMLファイルもしくはHTMLテンプレートがparseされたタイミングとなります。
ですので、 display: none;
で表示せず先にDOMを構成しておけば、PreLoadすることができます。
<section style="display: none;">
<ion-tabs><ion-tab-bar><ion-tab-button></ion-tab-button></ion-tab-bar></ion-tabs>
<ion-header><ion-toolbar><ion-title></ion-title></ion-toolbar></ion-header>
<ion-content></ion-content>
<ion-card><ion-card-header></ion-card-header><ion-card-content></ion-card-content></ion-card>
<ion-list><ion-list-header><ion-item></ion-item></ion-list-header></ion-list>
<ion-label></ion-label>
</section>
このことによって高速化する可能性があります。
3. Service WorkerのPreFetch
@angular/pwa
を利用することによって、デフォルトでJavaScriptファイルをPreFetchすることができます。
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/*.css",
"/*.js"
]
}
...
このことによって高速化する可能性があります。
検証
初期表示の検証
Starter Templateの Tabs
を使って検証しました。ソースコードは https://github.com/ionic-jp/tabs-compare で公開しています。
1. ModuleをEagerLoadingにする
効果がありました。
初期テンプレートのままだと、 Tabs
の初期表示は2つのModuleをLazy Loadingする必要があります。
`app.module.ts` => `tabs.module.ts` => `tab1.module.ts`
これをすべて app.module.ts
に統合して tabs.module.ts
、 tab1.module.ts
を削除しました。
ドメイン | Mdn名前解決 | Mdn レンダリング開始 |
---|---|---|
Default | 7.00 | 697.00 |
EagerLoading | 7.00 | 683.50(-13.5) |
レンダリング開始時間にわずかながら差を確認することができます。また、スピードインデックスにおいては、99パーセンタイルまですべてEagerLoadingがDefaultを速度で上回っていました。
2. Ionic ComponentsのPreLoad
効果がありました。
計測上、Defaultを EagerLoading & PreLoad
と比較していますが、EagerLoadingのみの時よりも高速化していることがわかります。
ドメイン | Mdn名前解決 | Mdn レンダリング開始 |
---|---|---|
Default | 7.00 | 697.00 |
EagerLoading | 7.00 | 683.50(-13.5) |
EagerLoading & PreLoad | 7.00 | 637.50(-59.5) |
スピードインデックスにおいては、99パーセンタイルまですべてEagerLoading & PreLoadがDefault、EagerLoadingを速度で上回っていました。
3. Service WorkerのPreFetch
https://github.com/ionic-jp/tabs-compare/blob/total/src/ngsw-config.json#L10-L12
効果はありませんでした
Service WorkerのPreFetchは、 First meaningful Paint
終了後に実行されます。そのため、Service Workerを有効にしても初期表示への影響はありません。
ページ遷移の検証
1. ModuleをPreLoadする
https://github.com/ionic-jp/blank-preload/blob/preload/src/app/app-routing.module.ts#L12
効果がありました。
遷移先のページをEagerLoadingにすることにより、ページ遷移時の待機時間が削減されます。ただし、冒頭に書いた通りLazy Loadingしないことによりbundleサイズが大きくなるので、初期表示が遅くなる可能性があります。
そのため、 preloadingStrategy
を PreloadAllModules
に設定しておき、遷移前にLoadを完了しておく必要があります。
@NgModule({
imports: [
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
],
exports: [RouterModule]
})
ただし、 PreloadAllModules
は事前にLoadするModuleを選択することができませんので、Moduleが多い場合はPreloadするModuleを指定する方が確実にページ遷移の待ち時間を削減することができます。
詳細は Preloading modules in Ionic v4 を参照ください。
2. Ionic ComponentsのPreLoad
https://github.com/ionic-jp/blank-preload/blob/preload/src/app/home/home.page.ts#L12-L21
効果がありました。
しかし、Ionic ComponentsのLoad中は ion-router-outlet
が遷移をブロックしますので、 home.html
テンプレートに直接書くと /home
の表示が遅くなってしまいます。ですので、TSで非同期にDOMを構築して、PreLoadを行います。
ionViewDidEnter() {
const preloadArea: HTMLElement = document.getElementById('preload');
preloadArea.appendChild(document.createElement('ion-card'));
preloadArea.appendChild(document.createElement('ion-card-header'));
preloadArea.appendChild(document.createElement('ion-card-subtitle'));
preloadArea.appendChild(document.createElement('ion-card-title'));
preloadArea.appendChild(document.createElement('ion-card-content'));
preloadArea.appendChild(document.createElement('ion-back-button'));
}
上のgifアニメーションがPreloadなし、下がPreloadありとなります。わかりやすいように遷移アニメーションは無効にしています。
デモ:https://blank-default.netlify.com
デモ:https://blank-preload.netlify.com
上のgifアニメーションでは、buttonクリック後、2つのJavaScriptファイルを読み込むまで待機しているのに対し、下ではLoad済みなのでクリック直後に遷移していることがわかります。
また、遷移後のIonBackButtonの表示が遅延していないことも確認できます。
3. Service WorkerのPreFetch
効果はある場合があります
しかし、下り30Mbps環境で22000ms程度が必要になります。そのため、ユーザが長時間アプリを操作する場合は、PreFetchによる高速化を期待することができます。
デモ:https://blank-sw.netlify.com/
すべてのファイルのロードに時間がかかるので、オフラインキャッシュの副次的な効果程度に留めておくのがいいでしょう。
未検証だが効果がある可能性の高い施策
rel="preload"によるIconの先読み
ionIcon
は、Web ComponentsのRender後に該当するSVGファイルを呼び出し、表示します。
参考コード:https://github.com/ionic-team/ionicons/blob/master/src/components/icon/icon.tsx#L206
そのため、Render以前に利用するSVGファイルを呼び出すると高速化に効果がある可能性があります。
(アイコンが表示されないため、Visually Loadedされない)
当初そのように思い、利用する ionIcon
もすべて display:none
を使ってpreLoadを試しましたが、高速化しませんでした。
デモ:https://tabs-bad-lazy.netlify.com
SVGファイルの呼び出し完了までブロックが発生しているようで異なるアプローチが必要であり、 rel="preload"
はそのひとつとして有力です。
iOS/Androidの判定をどうするかといった問題がありますので未検証のままですが、高速化の際にご留意いただけましたら幸いです。
ionImg
についても同様です。
まとめ
パフォーマンスチューニングには様々なアプローチがありますが、フロントエンドのエンジニアリングのみで行う場合は「ファイルのリクエストを操作する」、「レンダリングブロックを排除する」のふたつが大きなウェイトを占めることになります。
Developerツールをみながら様々な仮説を立て、検証いただければと思います。
なお、検証結果で差がわずかな場合は、自分自身のネットワーク環境に依存しながら「都合のいい結果」を得ている可能性がありますので、そのような思い込みを排除するためにツールを使った定点観測はとても有用です。
今回、株式会社Spelldata様にご協力いただき利用した Catchpoint は、SPA/PWAを正しく計測できるすばらしいツールですので、ぜひ計測を行う際はご利用をご検討ください。
それでは、また。