こんにちは、LINEの京都開発室でフロントエンド開発を担当している柴坂浩行です。
みなさんは仕事中などにパソコンで音楽を聴くことはありますか?
LINE MUSIC にはスマホアプリだけではなくWeb版アプリがあります。
私たちが開発しているWeb版アプリでは、JSフレームワークとして Vue.js を使用しています。
これまでこのWeb版アプリには Vue2 が使われてきましたが、
2022年6月、私たちは Vue3 にアップグレードしたバージョンをリリースしました。
私たちのチームではWeb版アプリの機能開発と平行して、パフォーマンスの改善にも取り組んでいます。パフォーマンスといっても様々な側面がありますが、その中でもバンドルサイズの肥大化が大きな課題でした。アプリを構成する js と css のバンドルサイズは6MB。Sentry というモニタリングツールでWeb版アプリへのアクセスを計測すると、表示に2秒以上かかっているアクセスが約半数に達していました。
私たちはリファクタリングを進め、依存パッケージの整理を行うなどサイズの削減に努めてきました。そのなかで、このアプリが一番大きく依存している Vue.js について情報収集をしていたところ、Vue 3 へのアップグレードでバンドルサイズの縮小が期待できることを知りました。 今後の TypeScript 導入などに備えて Vue 3 へのマイグレーション自体は予定していたため、こちらを優先して実行に移すことになりました。
この記事では、どのようにマイグレーション作業を行ったのか、またどんな所に工夫したのかをふり返りながら書いていきます。また、実際に大きな改善が見られたパフォーマンス面において、その結果をあわせてご紹介します。
「移行ビルド」を使ったアップグレード
Vue3 にアップグレードするためは多くの変更を行う必要があります。
その変更を一度に正確に行うのは非常に困難です。
ありがたい事に Vue3 では「移行ビルド」と呼ばれる互換モードが @vue/compat というパッケージで提供されています。Vue.js をアップグレードすると同時に移行ビルドをインストールする事で、最低限の調整で Vue3 を動作させることができるようになります。また、そのために必要な変更はドキュメント (en) に掲載されているので、スムーズにマイグレーション作業を開始できます。
移行ビルドをいれると、修正する必要のある箇所をエラーや警告として表示されます。それらを解消していくことで、迷うことなくマイグレーション作業を進められます。
また Vue3 へのアップグレードにあわせて、使用している多くの依存パッケージもアップグレードする必要があります。今回は移行ビルドのおかげで、1つずつアップグレードをして動作を確認しながら作業を進めることができました。
Step 1 まず Vue 3 が動作する状態まで
- vue-cli、Vue.js のアップグレード
- @vue/compat のインストールと互換モードの有効化
- webpack-dev-server 設定のバージョン変更対応
- コンパイルエラーの修正
@vue/cli-service のアップグレードで webpack-dev-server のバージョンが v3 から v4 に変わるので、設定変更が必要な場合があります。
また依存パッケージのなかで動かなくなったものがあったので、こちらもあわせてアップグレードを行いました。メジャーバージョンの変更でこちらも大きな設定変更が必要になり時間がかかりました。随所でこういった対応が必要でした。
ここまで進めると一旦 Dev Server で動かせるようになります。
Step 2 主な仕様変更への対応
ここでマイグレーションドキュメントでは以下の2箇所の対応を求めています。
<transition>
のクラス名仕様変更の対応- 新しいグローバルマウントAPIへの対応
<transition>
のクラス名に関しては、ドキュメントにも記載がありますがエラーとして出てこないので忘れないように早めに変更しておきます。
もうひとつの、新しいグローバルマウントAPIはいくつものメソッドに変更があったり、削除されたものもあるため、それぞれに対して多くの変更をする必要があります。
また、このタイミングでは Vue I18n が動作しなくなったのでアップグレードを行いました。Vue I18n のマイグレーションも公式ドキュメントが用意されているので参考にして進められます。
ちなみに Vue3 では props のデフォルト値を設定する際に this が Vue インスタンスとして使えなくなっているので、以下のような使い方で Vue I18n のテキストを呼び出している場合は変更が必要です。
props: {
title: {
type: String,
default() {
return this.$t('title'); // It will not work
},
},
}
Step 3 主要パッケージのアップグレード
次に Vuex や Vue-router のアップグレードを行います。
はじめる前の印象としてはここが一番大変だと思っていたのですが、よほど特殊な事をしていない限り簡単にアップグレードできます。ドキュメントで指示があった以外の変更は必要ありませんでした。
Step 4 エラーの修正や依存パッケージのアップグレード
そして、ここからは残っているエラーや警告を1つずつ解消していきます。
ここまでで大きな変更が終わり、その都度依存パッケージのアップグレードも進んだので、ほとんどの作業が終わったような印象を受けるのではないでしょうか。しかし実際にはここからの作業に長い時間を費やすことになります。なぜなら原因を探らないといけないものが出てくるからです。ここまでの純粋な作業時間が約1日だったのに対して、エラーとテストの修正を合計して約6日かかりました。ちなみに実際には他のプロジェクトも進めていたため、期間としては全体で1ヶ月かかりました。
この段階になって、アップグレードが必要になる依存パッケージを見つけたり、ブラウザで実行するまではエラーを出さないものもあります。このマイグレーションでは「エラーが出たパッケージから順次アップグレードする」という方針でまったく問題ありませんでした。こう考えると気楽に作業を始められるのではないでしょうか。
はじめにターミナルで表示される警告を解消し(エラーはここまでで既に解消済)、その次にブラウザのコンソールの警告やエラーを解消するという流れで進めていきました。ブラウザエラーは当然ページやコンポーネントによって出てくるものが変わる、使用しているパッケージ単位で、使用されている場所を表示させて動作確認して問題を見つけます。
実行時に発生するエラーの中には、どの変更以降でエラーが発生するのかが分かりにくいものもあったので、こまめなコミットが大切です。そうすることで原因が判断しやすくなるので、どの段階にでも立ち戻れるようにしておいてとてもよかったと思います。
Step 5 テストの修正
一番最後にテストコードを修正しました。
このプロジェクトでは Vue Testing Library を使用しています。
こちらも Vue3 のためにアップグレードが必要でしたが、マイグレーションについての記事は用意されていませんでした。作業を行っていた2022年4月時点では、Vue3 向けのバージョンは next ブランチにあり、この中のソースコードやサンプルコードを読む事で情報収集を行いました。
特に大きな変更は Vuex と Vue-router の扱いです。
これまでは render 関数内でそれぞれのインスタンスが作成され、vue インスタンスとともに render 関数の3番目の引数で扱う事ができました。
https://github.com/testing-library/vue-testing-library/blob/96c0c2d/src/render.js#L30-L43
今回のバージョンではこの機能が廃止されたため、Vuex と Vue-router のインスタンスを自分で作成する必要があります。
https://github.com/testing-library/vue-testing-library/blob/03c53e1/src/render.js#L22-L26
私たちは render のラッパー関数を作ってユニットテストを書いていますが、これをどう変更して対応したのかを見ていきましょう。
Before
export const initAndRender = (TestComponent, options, configure) => {
return render(TestComponent, options, (vue, store, router) => {
/* common settings */
const addtionalResponse = configure ? configure(vue, store, router) : {};
return { ...(addtionalResponse ? addtionalResponse : {}) };
});
};
After
export const initAndRender = (TestComponent, options) => {
const global = options?.global || {};
const plugins = global?.plugins || [];
/* common settings */
// vuex
if ('store' in options) {
const storeInstance = createStore(options.store);
plugins.push(storeInstance);
delete options.store;
}
// vue-router
const router = createRouter({
history: createWebHistory(),
routes: [...(options.routes || [])],
});
plugins.push(router);
delete options.routes;
// merge options
global.plugins = plugins;
global.mocks = mocks;
options.global = global;
return { ...render(TestComponent, options), router };
};
ここでは一部を抜粋したコードを掲載していますが、render 関数の第3引数がなくなったこと、Vuex, vue-router のための記述が増えている所に大きな違いがあります。テストコード側の変更を少なくするため、store, routes の各オプションを小さな変更で引き続き使えるようにするとともに、Vuex や Vue-router のインスタンス生成をラッパー関数側で行うようにしました。
また、render 内で生成された Vue-router のインスタンスを使用していた箇所があったため、render の中身とともに router を返すことで、引き続きテストの中で router を扱えるようにしました。
元々 render のラッパー関数を用意していたことで、結果的に、大きな変更をこの中で吸収することができたのはよかったです。
これ以外にもテストコードの修正が必要になった箇所は多く、全てのテストが通るまで試行錯誤しながらテストコードの修正を行いました。
Step 6 仕上げ
仕上げに、これまで使ってきた移行ビルドを削除します。
これでエラーが出なければ、正式に Vue 3 で動作するウェブアプリケーションです!
開発完了後には、QAチームに依頼して全体的な動作を確認してもらうリグレッションテストを実施してもらいました。やはりいくつかのバグが残っていたため、こういったテストの実施は重要です。検出された全てのバグを修正して、正式にリリースとなりました。
改善結果 1 バンドルサイズ
さて、おまちかねのパフォーマンス改善結果です。
Vue3 と移行ビルドの導入前と直後、そして移行ビルドの削除のタイミングで、
私たちはバンドルサイズがどの程度変化するのかを記録しました。
まずは development モードでビルドしたときのバンドルサイズを比較しました。
グラフは左から、
- マイグレーション作業前
- [Step1] Vue3 と移行ビルドの導入直後
- [Step6] マイグレーション作業後(移行ビルド削除) の比較です。
中央はただ Vue3 にアップグレードしただけなのにバンドルサイズが半減していて、この結果をチームに報告すると非常に大きな驚きに包まれました。マイグレーション作業完了後、移行ビルドを削除して純粋な Vue 3 アプリケーションになるとバンドルサイズは更に減少しました。
次に production モードでのビルド結果です。
development モードでは js 内にあった css が外部化されているのでファイルの構成が若干異なります。
移行ビルドの導入中は Vue 2 のように振る舞うのか、マイグレーション作業前とほぼ変わりませんでした。一方で移行ビルドを削除して純粋な Vue 3 アプリケーションになると、バンドルサイズが半減しました。
改善結果 2 ページ表示速度とそのスコア
私たちは、Vue3 に加えて Vite の導入も進めました。 Vite は webpack に代わるフロントエンド開発環境で、高速に動作する Dev Server を有しています。 コンパイラも強力で、コンポーネントごとに細分化したファイルを出力することができます。アクセスしたページに必要なファイルのみを読み込むため、ページ読み込み速度をより一層改善することができます。
私たちは Vue 3 と Vite の導入を含むバージョンをリリースした際に、Google Lighthouse で過去のバージョンと比較することにしました。その結果、パフォーマンススコアが 44 から 65 へと大きく改善しました。
Vue2 と Vue3 の純粋な比較にはなりませんが、その効果は大きいものと思われます。
また、以下は Sentry で計測したトップページの表示に要する時間です。リリース以降にページ表示時間が実際に半減した様子がグラフにはっきりと現れました。今では75%のユーザーが2秒以内に表示出来るようになりました。
さいごに
私たちは Vue 3 をパフォーマンス改善の目的で導入しましたが、今後私たちは Composition API および TypeScript の導入を予定していて、さらに Vue 3 を活用できることを期待しています。今後ともリファクタリングや改善をしっかり取り組みながら、LINE MUSIC の Web版アプリがよりよいものになるよう開発を進めていきます。
LINE京都開発室では一緒に働いてくれるフロントエンドエンジニアを募集しています。
京都で働きたい、チームでのフロントエンド開発やプロダクト改善に興味のある方、
私たちと一緒に開発をしませんか?