この記事はBASE Advent Calendar 2020の2日目の記事です。
こんにちは、BASEのフロントエンドチーム エンジニアの加藤です。
先日、弊社松原のこちらのブログにて、「既存のVue.jsによる資産は積極的にメンテナンスしつつ、その時その時で総合的に判断して最適な技術を選定する」スタンスで我々は考えているということをお話しました。直後にVue.js 3.0の正式リリースがあり、アップデートに関して実際に取り組みを始めています。
現在イメージしている流れ
現在、BASEのサービスで使われているバージョンは2.6系で、そこからのアップデートということになります。 現在利用しているVue.jsに依存したUIライブラリやバリデーションライブラリも3系対応で大きくAPIが変わることが予想され、一気に全ての使用箇所でバージョンをアップデートすることは困難だと感じています。 そこで、次のような流れで進めていくことを現在計画しています。
- Deprecatedな機能の置き換え作業
- 社内UIライブラリのVue.js3系対応版の開発
- monorepo化とそれに際する依存関係の再整理
- packageごとにバージョンを使い分け、徐々に移行
Vue.jsを使用しているBASEのショップ向け管理画面では、こちらのブログにあるように、機能単位でエントリポイントが存在するMPAであるという前提があります。この単位でpackageを分割し、それぞれでVue.jsのバージョンを使い分けることで段階的なアップデートの実現を考えています。
今回は、現在進行中の「Deprecatedな機能の置き換え作業」についてお話します。
対象範囲
Vue.js 3のマイグレーションに関するドキュメントから参照します。 前述の通りpackageごとにアップデートするという予定ですが、事前に対応できるものがあればやっておきたいところです。今回対応するものは、この中で「置き換える機能がすでに2.6系に存在しているか、3にアップデートせずとも別の修正方針が存在する」もののみをターゲットにしました。
- filterの廃止
- Functional Componentの廃止
- slot属性,slot-scope属性の廃止 (2.6にてDeprecatedとなっていますが3で正式に廃止されます)
- EventEmitter系のAPI($on, $off, $once)の廃止に伴う対応
以上が一旦対応すべきものとして洗い出されました。
事前準備
弊社右京のこちらのブログで設定してもらったように、ESLintのeslint-plugin-vueプラグインで非推奨の機能を機械的に判定します。また、fixオプションで修正できるものは修正してしまい、それ以外の以下は手動で修正して回ります。
実際の修正
filterの廃止
どのように修正するか
メソッドに置き換える
対応するeslint-plugin-vueのルール
vue/no-deprecated-filter
注意点
ここでは単なるメソッドに置き換えます。場合によっては、算出プロパティでその処理を記述しておくほうが良さそうな実装も見かけましたが、対応を統一したいため一旦方針として今回はメソッドで実装しています。
金額表示を変換するfilterがよく使われていましたが、これらはMixin経由でVueのprototypeメソッドとしても登録されているのでそちらに置き換えました。
Functional Componentの廃止
どのように修正するか
通常のコンポーネントに書き換える
対応するeslint-plugin-vueのルール
vue/no-deprecated-functional-template
注意点
関数型コンポーネントはインスタンスを持たない(thisのコンテキストが無い) ため、render関数内にcontextというオブジェクトが渡され、そこからpropsなどを参照するといったコードになっています。注意しながらこれらを普通のSFCに書き換えました。
実はこの対応で1つバグを出してしまったので、詳しくお話します。
我々はVeeVelidateというバリデーションライブラリをサービス全体で利用しています。このライブラリでは各コンポーネントに独自のバリデータスコープがあり、親子コンポーネントでのバリデーション結果を共有するために、$validatorという変数を親からinjectオプションを利用して注入することでバリデーションスコープを共有する、ということをおこなっています。
たとえばこういったparentコンポーネントがあった場合、
<template> <child> <grandchild /> <grandchild /> <grandchild /> </child> </template>
childコンポーネント内でinjectオプションを利用することで、grandchildコンポーネントにて$validatorを参照することが出来るようになり、親から子のバリデーションを実行することもできるようになります。 今回のバグはこういったケースでFunctionalコンポーネントかそうでないかで挙動が変わってしまった、というものです。
たとえば、上記のgrandchildコンポーネントがFunctionalコンポーネントであった場合を考えてみます。Functionalコンポーネントにはインスタンスがないため、親のスコープですぐに評価されます。このときの親のスコープというのが、ややこしいのですがchildではなくparentコンポーネントになります。生成されたvnodeがスロットコンテンツとしてparentに渡されるわけです。 これは、childがレンダリングされる前に、grandchildコンポーネントがすでに実行されている、ということです。ですから、childコンポーネントでinjectしようにもできない、ということが発生します。 ということで、grandchildがFunctionalコンポーネントだった当時は実装者がparentコンポーネントで$validatorをinjectして事なきを得ました。
これを知らずにそのままFunctionalコンポーネントをそのまま通常のコンポーネントに直してしまうと、当然意図しない動作になります。通常のコンポーネントは、親コンテキストですぐにはレンダリングされず、childのレンダリングサイクル中にレンダリングされます。つまりchildコンポーネントに必要なものをinjectする必要があるということです。
こちらのissueコメントがこの現象を詳しく説明しています。
slot属性,slot-scope属性の廃止
どのように修正するか
それぞれ、slot属性ではなくv-slotディレクティブに、slot-scope属性ではなくプロパティを受け取るv-slotディレクティブに書き換える
対応するeslint-plugin-vueのルール
vue/no-deprecated-slot-attribute vue/no-deprecated-slot-scope-attribute
注意点
eslint --fix
にて一部自動で修正できますが、v-slotディレクティブはtemplateタグにしか利用できないので、templateタグ以外にslot属性が使われているものは自動修正できません。これが修正としては一番数が多く面倒でした。
1つハマったポイントなのですが、slot属性で渡す要素内でthisを参照したときに、slot属性であれば動くものが、v-slotディレクティブでは動かない、という事象がありました。事前にこれらをすべて修正してからslot属性を修正する必要があります。こちらはvue/this-in-template
のルールにてチェックできますので慌てて追加しました。元々thisの使用は禁止されていたはずなので、いい機会でした。
まとめ
Deprecatedな機能の置き換えに関しては、以下を残り対応する予定です。
- Event Emitter系のAPI($on, $off, $once)の廃止に伴う対応
実は今回お話したものもまだすべては直しきれていないので、引き続き対応を続けていきます。 この作業が終了次第、前述の流れに沿って移行していく予定です。
また、2021 1Qに作業される予定の2.7は、3系移行のための支援となる機能が追加されることが予定されています。まず2.7に移行することでよりソフトランディングなアップデートになるのではないでしょうか。タイミングによっては既存の2.6系を2.7にアップデートしてから3系に進めることも考えてもよいと思っています。
明日はProduct Dev SREチームの相原さんです。お楽しみに〜