Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Migrating to Vue 2.7 for safe and improve development efficiency / 安全に開発効率を上げるための Vue_2.7 移行

watsuyo_2
May 25, 2023
250

Migrating to Vue 2.7 for safe and improve development efficiency / 安全に開発効率を上げるための Vue_2.7 移行

At iCARE, Inc., I presented the following content regarding our plan to migrate our products developed with Vue 2.6 to Vue 3, with an intermediate step through Vue 2.7.

1. Why did we choose Vue 2.7 as an intermediate step instead of directly migrating to Vue 3?
2. The benefits and reduced risks achieved by transitioning through Vue 2.7.
3. Necessary considerations to achieve version migration without compromising development speed in a fast-paced environment.
4. The estimated cost of migrating from Vue 2.6 to Vue 2.7.
5. The cost-effectiveness analysis of the migration.
6. Reflections and lessons learned from the migration process.

株式会社 iCARE では、Vue 2.6 で開発しているプロダクトを、 Vue 2.7 を経由してから Vue 3 に移行する計画を立てました。
以下の内容を発表します。

なぜ移行先は Vue 3 でなく、Vue 2.7 を選んだのか
Vue 2.7 を経由することによって軽減される負担とリスク
開発スピードを落とせない環境下で、バージョン移行を実現するために必要なこと
Vue 2.6 から Vue 2.7 への移行コストはどれくらいなのか
移行の費用対効果
移行の感想と反省

watsuyo_2

May 25, 2023
Tweet

Transcript

  1. 自己紹介 GitHub: watsuyo Twitter: watsuyo_2 仕事: 株式会社 iCARE 2020年10月〜 Carely

    のフロントエンドを Vue.js や TypeScript で実装 8月からは、PjM としてプロジェクト管理も 学び: 東京都立産業技術大学院大学・情報アーキテクチャ コース 2021年4月〜 趣味: 野球観戦 焼肉 寿司 ラーメン watsuyo 2022/10/16, VueFes 2022 5 / 36
  2. 「話すこと」 と 「前提」 話すこと Vue 3 と Vue 2.7 違い

    Vue 2.6 から Vue 2.7 へ移行した話 開発しているプロダクトを Vue 3 には上げられないけど、少しでも新しい Vue の機能を使いたい人が、Vue 2.7 に移行したくなる話 Vue 2.7 移行の助けになる話 前提 ※ SPA ではなく、ルーティングが Ruby on Rails に依存した MPA である 外部ライブラリに対する依存度の影響で、Vue 3 移行へのコストが高い Vue 2.7 移行前までは、Vue 2.6 + @vue/composition-api を利用し、defineComponent の setup() を使った実装をしていた Vue 2.7 に移行したあとも、defineComponent の setup() を使用している ※ Carely の開発において watsuyo 2022/10/16, VueFes 2022 7 / 36
  3. Vue 2.7 がリリースされるまでの流れ 2020年に Vue 3 がリリースされたときから、Vue 2.7 のリリースは予告されていた 「Vuejs

    Amsterdam 2022」の Evan You さんの講演で、Vue 2.7 の概要とリリース日が発表された 2022/05/31 に、 v2.7.0-alpha.1 がリリースされた Carely においては、v2.7.0-alpha から、継続的に Vue 2.7 移行の手順を模索し、v2.7 の正式リリースまでに Vue.2.7 移行を進めた 2022/07/01 に、Vue 2.7 が正式リリースされた Carely は、2022/09/01 に Vue 2.7 へ移行完了 watsuyo 2022/10/16, VueFes 2022 9 / 36
  4. Vue 2.7 の概要 概要 Vue 2、最後のマイナーバージョン Vue 2 を使用しているプロダクトにとって Vue

    3 移行のためのバージョンである 2023年末で EOL を迎える バックポート機能 Composition API <script setup> CSS v-bind 参考: Vue 2.7 "Naruto" Released watsuyo 2022/10/16, VueFes 2022 11 / 36
  5. Vue 3との違い リアクティビティ Vue2.7 の Composition API では、レガシーブラウザとの互換性を保つために、Vue 2.6 以前と同様に、Getter

    / Setter がベースのリアクティビ ティが使用されている 基本的には、Vue 2 のリアクティビティシステムと同じである(v2 ドキュメント) Vue3 では、ES6 から導入された Proxy をベースのリアクティビティシステムが使用されている 参考: Vue 2.7 "Naruto" Released watsuyo 2022/10/16, VueFes 2022 13 / 36
  6. Vue 3との違い バックポートされない機能 createApp() <script setup> 直下での await template 内での

    TypeScript 構文のサポート Reactivity transform expose オプション <setup script> で、 defineExpose() は使える 参考: Vue 2.7 "Naruto" Released watsuyo 2022/10/16, VueFes 2022 14 / 36
  7. Vue 2.7 を経由するメリット Vue 3 の機能がバックポートされるため、段階的に移行できる また、移行の負担とリスクが軽減される Composition API [変更]、Vite

    [新規]、Volar [変更]、context.root [廃止] を Vue 3 に先立って、対応・導入できる 動作確認のコストを削減できる 影響範囲の切り分けがしやすい 安全にバージョン移行できる確率が高まる Vue 2.7 と Vue 3 のように、2つのマイルストーンを置くことで、 難しい issue を分割し、エンジニアの負担を軽減できる watsuyo 2022/10/16, VueFes 2022 16 / 36
  8. Vue 2.7 を経由する注意点 @vue/composition-api との一貫性が保証されていない @vue/composition-api は、すでににメンテナスモードに入っており、2022年末で EOL になる予定 Vue

    3 の機能を網羅しているわけではない v-model:value も使えないし、<script setup> も完全には使えない 1. Breaking change vue 2.7.8 with vue-composition-api 1.7.0 #12752↩︎ 2. CHANGELOG.md#270-2022-07-01↩︎ [1] [2] watsuyo 2022/10/16, VueFes 2022 17 / 36
  9. 段階的に Vue のバージョアップが必要な理由 Vue 2 の 2023 年末に EOL を迎える

    長期的には Vue や 外部パッケージ に セキュリティリスクが含まれてしまった場合、急な対応が難しくなる 2023 年末は、残り約 14 ヶ月 🚨 watsuyo 2022/10/16, VueFes 2022 19 / 36
  10. Vue 2.7 移行のつまずきポイント Vue Instance 参照方法の変更 emit を関数へ渡すときの型の注意点 Error: Avoid

    using variables that start with _ or $ in setup() watsuyo 2022/10/16, VueFes 2022 21 / 36
  11. Vue Instance 参照方法の変更 vuejs/composition-api/src/runtimeContext.ts vuejs/vue/src/v3/apiSetup.ts @vue/composition-api(左) で @deprecated なプロパティは、Vue 2.7(右)

    から削除されている root が無くなったことで、Vue Instance プロパティに依存するメソ ッドが、SetupContext から呼び出せなくなった 1. vuejs/composition-api/src/runtimeContext.ts#L148-L176↩︎ 2. vuejs/vue/src/v3/apiSetup.ts#L20-L26↩︎ [1] export interface SetupContext<E extends EmitsOptions = {}> { attrs: Data slots: Slots emit: EmitFn<E> /** * @deprecated not available in Vue 2 */ expose: (exposed?: Record<string, any>) => void /** * @deprecated not available in Vue 3 */ readonly parent: ComponentInstance | null /** * @deprecated not available in Vue 3 */ readonly root: ComponentInstance /** * @deprecated not available in Vue 3 */ readonly listeners: { [key in string]?: Function } /** * @deprecated not available in Vue 3 */ readonly refs: { [key: string]: Vue | Element | Vue[] | Element[] } } [2] export interface SetupContext<E extends EmitsOptions = {}> { attrs: Data /** * Equivalent of `this.$listeners`, which is Vue 2 only. */ listeners: Record<string, Function | Function[]> slots: Slots emit: EmitFn<E> expose(exposed?: Record<string, any>): void } watsuyo 2022/10/16, VueFes 2022 23 / 36
  12. Vue Instance 参照方法の変更 vuejs/core/packages/runtime-core/src/component.ts Vue 3 の場合も、Vue 2.7 同様に、@deprecated なプロパティは

    削 除されている また、listeners も削除されている 1. vuejs/core/packages/runtime-core/src/component.ts#L182-L187↩︎ [1] export interface SetupContext<E = EmitsOptions> { attrs: Data slots: Slots emit: EmitFn<E> expose: (exposed?: Record<string, any>) => void } watsuyo 2022/10/16, VueFes 2022 24 / 36
  13. Vue Instance 参照方法の変更 useGetProxy.ts index.vue getCurrentInstance() から Vue Instance を取得し、proxy

    経由 で root をを取得する独自のユーティリティ関数を実装した Vue コンポーネントからは getProxy() を呼び出すことで、利用でき る また、現状の実装に依存しているプロパティだけを呼び出すため、 型で制御している しかし、getCurrentInstance() は、Public API ではなく、ライ ブラリ開発者が利用するもので、本来の使い方ではないため、利用 は推奨されていない また、Vue 3 の今後のアップデートでは、警告が出るようになり、 互換性がなくなる可能性が指摘されている 1. Evan You さんのコメント↩︎ import Vue, { getCurrentInstance } from 'vue' export type VueProxyType = Pick<Vue, '$buefy' | '$apollo' | '$el' | '$refs'> const vueInstance = { root: { proxy: Vue as unknown as VueProxyType, }, } type VueInstanceType = typeof vueInstance /** * internal vm の proxy を返す */ export const getProxy = (): VueProxyType => { const instance = (getCurrentInstance() as unknown as VueInstanceType)?.root || getCurrentInstance() return instance?.proxy } import { getProxy } from '@/environment/useGetProxy' export default defineComponent({ setup(_, { emit, slots}) { const root = getProxy() const { $buefy, $apollo, $el, $refs } = root } }) [1] watsuyo 2022/10/16, VueFes 2022 25 / 36
  14. emit を関数へ渡すときの型の注意点 Vue 2.6 以前 Vue 2.7 以降 SetupContext から取得できる

    emit 型が Vue 2.7 で変更された defineComponent の emits: [] フィールドで指定する event 名が T に入る どの event に対応した emit が受け取れるのかを明示する必要がある emit: (event: string, ...args: any[]) => void emit: (event: T, ...args: any[]) => void watsuyo 2022/10/16, VueFes 2022 27 / 36
  15. emit を関数へ渡すときの型の注意点 emit-function-type.ts event の型を捕捉するため、EmitFunctionType という ジェネリック 型を定義する 例えば、func() の引数に

    emit を渡したい場合は、 EmitFunctionType<'close'> のように型定義する func.ts EmitFunctionType を利用して、event 名を含む emit しか受け付けないように する export type EmitFunctionType<T extends string> = ( event: T, ...args: any[] ) => void export const func = ( emit: EmitFunctionType<'close'> ) => { ... emit('close') ... } watsuyo 2022/10/16, VueFes 2022 28 / 36
  16. Error: Avoid using variables that start with _ or $

    in setup() watsuyo 2022/10/16, VueFes 2022 29 / 36
  17. Error: Avoid using variables that start with _ or $

    in setup() Carely では、一部のコンポーネントで、_ 始まりの変数が定義されていた 以前から、Vue 内部のプロパティで、_ や $ が予約語となっており、データバインディングでは使用できないとされていた しかし、Vue2.6 で開発を行っているときには、問題となっていなかった Vue 2.7 にアップデートしたタイミングで、v-modal を使ってバインディングしている箇所でフォームに input した値が変数に保存されな い問題が発生した 1. Evan You さんのコメント↩︎ [1] watsuyo 2022/10/16, VueFes 2022 30 / 36
  18. Error: Avoid using variables that start with _ or $

    in setup() Vue 2.6 + @vue/composition-api と Vue 3 では、_inputValue へ入力 した値が保存される Vue 2.7 だけ、変数の頭文字を $ か _ にしていると、_inputValue へ 入力した値が保存されない Vue.js 側で、 console に警告が出るように実装されている CodeSandbox Vue3 Vue2.6 + @vue/composition-api Vue2.7 <template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png" /> <div> <input v-model="_inputValue" /> <p>{{ _inputValue }}</p> </div> </div> </template> <script lang="ts"> // Vue 2.6 + @vue/composition-api import { defineComponent, ref } from "@vue/composition-api"; // Vue 2.7 | Vue 3 import { defineComponent, ref } from "vue"; export default defineComponent({ name: "App", setup() { const _inputValue = ref(""); return { _inputValue, }; }, }); </script> watsuyo 2022/10/16, VueFes 2022 31 / 36
  19. Error: Avoid using variables that start with _ or $

    in setup() vuejs/vue/src/core/util/lang.ts vuejs/vue/src/v3/apiSetup.ts 1. vuejs/vue/src/core/util/lang.ts#L9-L15↩︎ 2. vuejs/vue/src/v3/apiSetup.ts#L62-L66↩︎ [1] /** * Check if a string starts with $ or _ */ export function isReserved(str: string): boolean { const c = (str + '').charCodeAt(0) return c === 0x24 || c === 0x5f } [2] ... if (!isReserved(key)) { proxyWithRefUnwrap(vm, setupResult, key) } else if (__DEV__) { warn(`Avoid using variables that start with _ or $ in setup().`) } ... watsuyo 2022/10/16, VueFes 2022 32 / 36
  20. 番外編: Vue 2.7 移行の振り返り 今後の大規模リリースに向けて、KPT 法による振り返りを 開催 参加者: @watsuyo、 @ozu_syo

    さん、 @oreo2991 さ ん 今後のライブラリや言語のバージョンアップなどによる大規模リリ ースへの知見として、振り返りを行った watsuyo 2022/10/16, VueFes 2022 34 / 36
  21. 番外編: これから Vue 2.7 移行を行う方へ 感想、質問、Vue 2.7 移行の意見交換は👇 Twitter: watsuyo_2

    👆 #vuefes 🍣 🍖 🍜 - コミットごとに手順書を書くことで、大量の差分があるコミットでも、どんな事をしたコミットなのかを明確にする - フロントエンドエンジニアメンバーに動作確認をお願いし、分担して行う - QA チームと連携して、動作確認を開発者とQAE のどちらがやるのかを話し合う - リリース後に各作業ブランチに取り込んだ後に動作確認してもらうことをアナウンスや事前告知を行う - Vue のコードを読む(composition-api と vue 2.7 の差分を) - 型定義を読めば気づけた仕様変更があったので、早めに読み始める - 事前にリリースできるものを分割して、先に出しておくことでリスク軽減させる - もう少し早く着手できたらよかった - PR は何度も作り直して、できるだけアップデートに関係のない変更は先にリリースしておく - 特に型エラーや、Lint エラーは事前に潰しておくと良い - CI で diff しかチェックしていない job は、設定ファイルをコメントアウトして置くのもあり - 後でコメントアウトを戻すのを忘れずに - GitHub 以外に、Twitter や Reddit 、各ライブラリのコミュニティ (Slack や Discord) に情報も確認する watsuyo 2022/10/16, VueFes 2022 36 / 36