こんにちは、TechFeedの白石です。
この度、TechFeed10.1をリリースしました!
・Web版… https://techfeed.io
・iOS版…App Store
・Android版…Google Play
今回のリリースで目標としたのは、起動時間の改善です。TechFeedは、モバイルアプリもWebアプリもすべてHTML/JavaScriptで開発しており(Apache Cordovaを利用)、一般的なWebサイト/アプリの改善テクニックが利用できます。
今回は、Webサイトのパフォーマンス診断ツールとして著名なPageSpeed Insight (Google Chromeの開発者ツールには「Audits」という機能名で組み込まれています)を用いてパフォーマンスを計測し、そのスコアを向上させることを目標として改善を行いました。
その結果、現在のパフォーマンススコアは以下のようになっています(モバイル環境は3Gネットワーク・CPU 1/4速をシミュレートした結果。)。実行した環境によって結果は大きく異なるため、弊社の環境で検査した結果であることをあらかじめお断りしておきます。
・デスクトップ…100点
・モバイル…58点
デスクトップで100点取れた!のは嬉しいのですが、
残念なのはモバイル環境における点数。それでも、改善の前後で比較すると、パフォーマンスの項目が10点ほどアップしているのがわかります。
58点という点数は、PageSpeed Insightの説明によると「Low: ページは最適化されておらず、最適化すべき点がかなり多くあります。ページのスコアは 0~59 の範囲になります。」とのことなので、く、悔しい。。。
とはいえ、大規模なSPA(Angular + Ionicアプリ)のパフォーマンス改善に思い切り取り組んだ事例として参考になるかと思うので、私たちが行った施策と、残っている課題について記しておきます。
Angularモジュールの分割
今回行った施策は多岐にわたります。以下にその一部を紹介しますと、
・JavaScriptバンドルサイズの縮小。moment.jsやRxJS、lodashと言ったライブラリを削るのが主です。これらを削る手法についてはググるとたくさん出てくるので割愛します。
・静的ファイルの長期間キャッシュ
・起動時に行っていた不要な処理の除去
・DBサーバーのスペックアップ
・…
などですが、今回、起動速度改善のために行った変更の中で技術的にホットなトピックとしては、Angularモジュールへの分割と遅延ロードを使用するようにしたことです。
Angularによるモジュール遅延ロードのサポートは素晴らしく、こんなことができます。
・URL(ルーティング)と紐付いた遅延ロードの仕組み。ユーザーがあるURLにアクセスしたとして、そのURLで必要とされるモジュールのみが自動的にロードされる。
・Angular CLIを使うと、ほぼ設定いらずで、モジュール単位にJavaScriptファイルの分割が行われる。TypeScriptに記述したルーティング設定から、自動的にバンドルを分割していくというAngular CLIの黒魔術っぷりにはかなり恐怖を感じますが😅
・特定のモジュールに対するプリロードも可能。ほぼ確実に、起動時に読み込まれる可能性のあるモジュールについては、ルーティング設定で「preload: true」といった記述を行うだけで、該当モジュールがプリロードされる。
・全モジュールに対するプリロードも可能。「preloadAllModules」というフラグをONにするだけで、すべてのモジュールファイルを自動的にプリロードします。これにより、起動時は必要なモジュールのみ読み込ませつつ、その他のモジュールも可能な限り早いタイミングであらかじめ読み込んでおき、遅延ロードによるストレス(リンククリックしたら長いこと待たされる、など)もなくすことができます。
こうした改善により、生成されるJavaScriptファイルの中身は大幅に変わりました。以下が、モジュール分割前のmain.jsのコードです。いろいろミスってて、 node_modules
以下のライブラリまで main.js
に巻き込んじゃってましたね…
次に示すのが、 node_modules
以下のライブラリを分け、モジュール分割した後のバンドルファイルです。main.jsに含まれていたコードが、多数のJavaScriptファイルに分割されているのをご確認いただけると思います。main.jsだけで言うなら、9.78MB→1.57MBと、84%もファイルサイズが削れました!🎉( vendor.js
を含めてみても、かなりのファイルサイズ縮小になっていることは見ておわかりかと思います)
モジュール分割作業の実際
TechFeedをモジュールに分割していくという作業は、具体的には以下のようなものでした。
・サブパス( /settings
など)ごとにモジュール定義を行っていく。モジュール定義とは具体的には、 @NgModule
というデコレーターを付与したクラスを作っていくことと、ルーティング定義を行っていくこと。
・モジュールごとに、管理するコンポーネントを定義していく( @NgModule({declarations})
)。これにより、コンポーネントをモジュールごとに分割していける。
・親モジュールのルーティング定義で、遅延ロードの設定を行う。具体的には loadChildren: 'モジュールファイル名(拡張子なし)#モジュール名'
という形式で定義を行う(すると、Angular CLIが自動的にモジュール単位でのファイル分割を行い、遅延ロードを行うように設定してくれる。黒魔術!)
{
path: 'entries',
loadChildren: './entries/entries.module#EntriesModule',
},
この手順を、モジュール分割したいサブパスのぶんだけ繰り返していくことになるのですが、ことはそう単純ではありません。
TechFeedのクライアントアプリは現在に至るまで、2年以上の月日をかけて成長してきた巨大なモノリシック構造のアプリケーションです。コンポーネント間の関連が、知らぬ間に複雑に絡み合っており、(TypeScriptの) import
文によってバンドルがまとめられてしまうのを一つ一つ解きほぐすという作業が必要でした(困難な作業を完遂してくれたざっきーには感謝!)。
今後の課題
とはいえ、TechFeedのパフォーマンス改善はまだ道半ばです。
例えば、ServiceWorkerによるオフラインキャッシュという難事業に、 日本有数のAngularエキスパートである きゃないさんが取り組んでます。その成果は近日公開(予定)!乞うご期待✨
そして最も問題なのは、TechFeed起動時におけるメインスレッド上の仕事を減らさなくちゃいけないこと。
JSのファイルサイズをこれ以上減らせないところまで削ってしまっている現状、この課題を解決するには、JSのプロファイリングとパフォーマンスデバッグが必要です。
が、、難しすぎて手に余っております…!誰か、JavaScriptのパフォチュー詳しい方、手伝ってくれませんか?(ってことでエンジニア募集要項は最下部に😊)
今後ともTechFeedをよろしくお願いします。
株式会社テックフィード エンジニア募集
・募集職種: Webエンジニア(特にTypeScriptに関心のある方)。弊社はWeb技術でフルスタックを開発していますので、全領域でご活躍いただけます。
・勤務地: フルリモートワーク。インターネットに繋がってさえいれば、どこで働いていただこうと問題ありません。Slack, GitHub, ZenHubを活用して、フルリモートワークを前提としたアジャイルプロセスを組み立てて実践しています。オフィスを持たないまま上場することも目標の1つです😊
・勤務形態: 正社員/契約社員/業務委託/副業など、ご希望に合わせた働き方が可能です。また、社員として働きつつの副業もOKです。
・応募方法: recruit@techfeed.co.jp まで、お気軽にご連絡ください。簡単な自己紹介か、プロフィールのわかるWebページのURL、あと差し支えなければTwitterやGitHubのIDなどがわかればOKです。まずは軽くお茶かお食事でも…というくらいの気軽さで、こちらもご連絡させていただきます。