アドバイザー/lite開発チーム フロントエンドエンジニアの小柳(@mascii_k)です。
はじめに
弊社サービス「ビザスクlite」では、Vue 2.6 向けのプラグイン @vue/composition-api
を 2020/02 から導入しています。
ビザスクliteでの導入実績とノウハウ蓄積の結果、社内で利用している管理画面のフロントエンド(Vue 2.6)環境にも @vue/composition-api
を導入することになりましたが、当時の判断で古いバージョンのものを導入してしまいました。
しくじりの経緯
2020/09 当時の最新バージョンであった @vue/composition-api
1.0.0-beta.14 を導入しようとしたところ、vue
本体のバージョンが 2.6.10 (当時の最新バージョンは 2.6.12)だったため、コンパイル時に型に関するエラーが発生することがわかりました。
そのため、vue
本体のバージョンを 2.6.12 に予めバージョンアップしておくことを考えましたが、当時は組織内において vue
のバージョンアップに対する不安感がありました。
少し古いバージョンの @vue/composition-api
0.6.6 であれば vue
のバージョンが 2.6.10 のまま導入できることがわかり、これを導入することにしました。
古いバージョンの導入後に判明した問題
次第に 0.6.6 では import { nextTick } from '@vue/composition-api'
で nextTick()
を import できなかったり、最新バージョンとの間に多くの機能差や破壊的変更があることがわかりました。
特に 1.0.0-beta.7 での破壊的変更 "template auto ref unwrapping are now applied shallowly" によって、ref()
や computed()
などが返す Ref
のアンラップの挙動が変更となったことはバージョンアップ時の懸念点となっていました。実際、この破壊的変更の前でしか動作しないコードをレビューで見たことがありました。
以下は "template auto ref unwrapping are now applied shallowly" の影響を受けるコードと動作の例です:
<template> <div> <h2>@vue/composition-api@0.6.6</h2> <div> {{ count }} * 2 = {{ multiples.double }} </div> <div> {{ count }} * 3 = {{ multiples.triple }} </div> </div> </template> <script lang="ts"> import { defineComponent, ref, computed } from '@vue/composition-api'; export default defineComponent({ name: 'App', setup() { const count = ref(123); return { count, multiples: { double: computed(() => count.value * 2), triple: computed(() => count.value * 3), }, }; }, }); </script>
0.6.6 での動作 | 1.4.9 での動作 |
---|---|
バージョンアップに着手
私が "template auto ref unwrapping are now applied shallowly" の影響調査を担当することにし、vue
本体のバージョンアップと @vue/composition-api
のその他の破壊的変更の影響調査は社内の別のエンジニアさんに担当していただきました。
vue-tsc を活用して型検査することを考える
1年ほど前には vue-tsc1 と reviewdog を用いて TypeScript の Strict Mode が有効でない環境に Strict Mode を漸進的に導入する試みを紹介しておりました。vue-tsc では .ts
ファイルだけでなく、.vue
ファイルの <template>
ブロックと <script>
ブロックも検査できます。
今回は .vue
ファイルの <template>
ブロックを検査する方向ではなく、<script>
ブロックを検査し "template auto ref unwrapping are now applied shallowly" の影響を受けるファイルを抽出する方向で考えました。実際に用いた手法を以下に示します:
.vue
ファイルの抽出手順
npm install -g vue-tsc@0.34.15
で vue-tsc をインストールするnpm install @vue/composition-api@latest
で最新の@vue/composition-api
にバージョンアップする- 検査用のコードを
defineComponent
を用いている.vue
ファイルに埋め込むため、VSCode の正規表現モードで以下の置き換えを実施する:
置換対象(正規表現):export default defineComponent\(([\s\S\n]+)</script>
置換内容:const __component__ = defineComponent($1 export default __component__; import { reactive as _reactive } from '@vue/composition-api'; const shallowUnwrapped = __component__.data!; const deepUnwrapped = _reactive(shallowUnwrapped); const typeCheck: typeof shallowUnwrapped extends typeof deepUnwrapped ? 1 : 0 = 1; </script>
置換結果の例:<script lang="ts"> import { defineComponent, ref, computed } from '@vue/composition-api'; -export default defineComponent({ +const __component__ = defineComponent({ name: 'App', setup() { const count = ref(123); return { count, multiples: { double: computed(() => count.value * 2), triple: computed(() => count.value * 3), }, }; }, }); + +export default __component__; + +import { reactive as _reactive } from '@vue/composition-api'; +const shallowUnwrapped = __component__.data!; +const deepUnwrapped = _reactive(shallowUnwrapped); +const typeCheck: typeof shallowUnwrapped extends typeof deepUnwrapped ? 1 : 0 = 1; </script>
- vue-tsc で型検査を実施する
vue-tsc --noEmit --allowJs | grep "TS2322: Type '1' is not assignable to type '0'."
なぜこれで抽出できるか?
1.0.0-beta.7 以降では __component__.data!
は setup
関数の戻り値を shallow unwrap ref した型を持っていて、さらに reactive()
に通すことで deep unwrap ref した型を得ることができます2。
Conditional Types の extends
で 2 つの型の構造を比較し、構造が異なる場合は const
宣言の型エラーを意図的に発生させることで "template auto ref unwrapping are now applied shallowly" の影響を受けるファイルを抽出できます。
抽出の結果
コマンド vue-tsc --noEmit --allowJs | grep "TS2322: Type '1' is not assignable to type '0'."
の結果は以下のようになりました:
src/modules/*****-******/organisms/****************.vue(47,7): error TS2322: Type '1' is not assignable to type '0'. src/modules/*****-******/organisms/***************.vue(54,7): error TS2322: Type '1' is not assignable to type '0'. src/modules/*****-******/templates/*****************.vue(102,7): error TS2322: Type '1' is not assignable to type '0'. src/modules/*****-******/templates/***********************.vue(113,7): error TS2322: Type '1' is not assignable to type '0'. src/modules/*******/organisms/*****************.vue(150,7): error TS2322: Type '1' is not assignable to type '0'. src/modules/*******/organisms/******************.vue(144,7): error TS2322: Type '1' is not assignable to type '0'. src/modules/*******/organisms/*****************.vue(153,7): error TS2322: Type '1' is not assignable to type '0'. src/modules/*****-******/organisms/*************.vue(220,7): error TS2322: Type '1' is not assignable to type '0'. src/modules/*******/organisms/**************************.vue(273,7): error TS2322: Type '1' is not assignable to type '0'. src/modules/*******/pages/*************.vue(309,7): error TS2322: Type '1' is not assignable to type '0'. src/modules/*******/organisms/********************.vue(311,7): error TS2322: Type '1' is not assignable to type '0'.
@vue/composition-api
に依存している .vue
ファイルは約 200 ファイルありましたが、11 ファイルまで絞り込みができました。
上記 11 個の .vue
ファイルの <template>
ブロック上で "template auto ref unwrapping are now applied shallowly" の影響を受けている箇所を目視確認し、漏れなく確実に修正できました。
まとめ
今回のような検査が必要となったのは vue
本体のバージョンアップの機運があったにもかかわらず、バージョンアップしなかったことが起因しています。
当時からバージョンアップのリスク評価を正しくできていれば、組織内の不安感も解消できていたはずですので、今回の反省を活かしていきたいと思っています。
-
vue-tsc@0.3.0 よりも上のバージョンだと弊社の環境においては型検査できませんでしたvue-tsc@0.34.15 (2022/05/16 時点での最新版)で型検査が可能となっていました↩ -
reactive
は、ref のリアクティビティを維持しながら、全ての深さの ref をアンラップします - https://v3.ja.vuejs.org/api/basic-reactivity.html#reactive↩