feature image

2021年2月2日 | ブログ記事

traQをVue 3に移行しました

こんにちは、19のSysAd班の翠(sappi_red)です。

この記事では10/17にリリースされたtraQ v3.3.0でVueのv2からv3に移行した話について話していきます。どんな手順で移行を進めたかやハマりどころ、どうするとよさそうみたいな話ですね。

traQで使われてるライブラリなど

まず、traQではVue CLIなど以下のVue関連のツールを利用して開発していました。

また、Vue関連のライブラリは以下のようなものを利用していました。

移行手順

2020/08/03から着手して2020/10/08にmasterにマージしたので2ヶ月ですが、途中空けてるのでおそらく実質的には2週間程度だと思います。

事前調査

上記のライブラリやツールの対応状況は

となっていて、着手した地点では対応しているのはコアなもののみでライブラリはほぼ全滅の状態でした。(着手した地点ではベータかアルファばっかりだったので当然ですね)

vue-codemodの手順にのっとって書き換える

vue-codemodというreact-codemodっぽい移行ツールが開発されていることを知っていたのでそこのREADMEの手順を参考にしつつ書き換えました。
このツールはその頃もTypeScriptへの対応がされていなかったので、実際に実行はせず手で書き換えました。現在もexperimentalみたいでTypeScriptの対応もしてないみたいです。

書き換えたものが動くかどうかのチェックをするために一旦ライブラリ部分をすべてコメントアウトしました。
表示はうまくいったのですが、ところどころ以下の点で動いてなかったので直しました。

大体手順のここの欄の部分ですね。

ライブラリ部分の対応

ここの「パッチを作成」というのはpatch-packageというnode_modulesにパッチをあてられるツールを利用しました。
それぞれのライブラリをクローンした上でVue3に書き換えた上でその変更を元にパッチを作成するという手順ですね。vue-slider-componentだけvue-property-decoratorで書かれていてあまり情報を集めていなかった箇所だったので少し手間取りました。

portal-vueから<teleport>への書き換え

portal-vueでは<portal>がレンダリングされてから<portal-target>がレンダリングされるという順番でも問題なかったのですが、<teleport>はレンダリングするときにtoで指定した要素が存在していることが前提になっています。なので、toに指定する要素は基本的には<body>直下にして、どうしてもVueで管理された部分に置く場合は、レンダリングされたかどうかでv-ifで分岐する必要があります。

<body>直下にする際にはVueのルート要素に指定したCSSなどが適用されないことに注意ですね。CSS変数をルート要素に指定していたので、モーダルにCSSが適用されないってバグが起きました。

バグ取り

ここで大体ほぼ動くようになったのですが、まだ少しバグがいくつか残っていたので、以下のところを直しました。

ここまででmasterにマージしました

Vue 3移行後の問題とか

上の真偽値の変更と.nativeなしでもlistenされる変更によるバグがそこそこ発生しました。

emits

Vue3で追加されたemitsというそのコンポーネントで発火するイベントを宣言できるものがあって、それを宣言するとTypeScriptで型がつくようになっています。これは宣伝ですがこれを書くとVeturでイベント名が補完されたりするようにしたので、そういう点でもうれしかったりします。

import { defineComponent } from 'vue'

export default defineComponent({
  emits: {
    foo: (arg1: number, arg2: string) => true
  },
  setup(props, context) {
    context.emit('foo', 1, 'aa')
    context.emit('bar') // error
    context.emit('foo', true) // error
  }
})

ここで複数のイベントを発火するコンポーネントが存在していて、その内の一つのイベントを発火するようなcomposable(ロジックを意味単位に分割した関数、Reactでいうhookみたいなもの)があるとします。下のような状況です。

import { defineComponent, SetupContext } from 'vue'

const useFoo = (context: SetupContext<{ foo: () => true }>) => {
  context.emit('foo')
}

export default defineComponent({
  emits: {
    foo: () => true,
    bar :() => true
  },
  setup(props, context) {
    useFoo(context)
  }
})

このときにuseFoo(context)の行でエラーが発生してしまっていました(vuejs/vue-next#2362)。
useFooに渡されているcontextの型がSetupContext<{ foo: () => true, bar: () => true }>なのに対して、useFooのとる引数はSetupContext<{ foo: () => true }>です。型だけ比較すると代入できないような気がしますが、意味を考えるとこれは代入できるべきです。foobar以外のものが発火するのは困りますがどちらか一方しか発火しないのは問題ないですね。
これを解決するためにSetupContext<{ foo: () => true, bar: () => true }>がそのまま({ emit: ((name: 'foo') => void) | ((name: 'bar') => void) })ではなく{ emit: ((name: 'foo') => void) & ((name: 'bar') => void) }として扱われるようにUnionToIntersectionというちょっと変わった型関数を利用しています
中でUnionToIntersectionが使われてるので代入できるようになってるはずですが、おそらく問題は外の箇所で下のようにSetupContextが定義されてた点です。

type SetupContext<E> = {
  emit: EmitFn<E>;
};

このように定義されてたとき、TypeScriptはEの型だけで代入可能かどうかの判定を行っているような挙動をしていました(ホバーした際にSetupContext<~>の形で表示されているあたりとか)。なので、下のように型を実際に評価せざるをえない形にしてみたところ代入できるようにはなりました。(大抵の場合であれば型引数同士の代入可能性だけを評価すれば問題ないみたいな話なのでしょうか…よくわかってません)

type SetupContext<E> = E extends any
  ? {
    emit: EmitFn<E>;
  }
  : never

移行するなら

時期

移行のドキュメントも以下のように整ってきてコアのライブラリ以外を利用していない場合は今すぐでもそこまで引っかからずに移行できると思います。

ただし、今下記のバグがあるので少し待ったほうが良いかもしれません。

これらは3.1.0では直りそうな予感がしてます。

All of our official libraries and tools now support Vue 3, but most of them are still in beta status and distributed under the next dist tag on NPM. We are planning to stabilize and switch all projects to use the latest dist tag by end of 2020.
https://github.com/vuejs/vue-next#supporting-libraries

年始にコアのライブラリはstableのバージョンが出ていて(vuexはまだRCではありますが)、コンポーネントライブラリなどが対応するのは早くて4~6月ごろだと思います。主要なライブラリのVue3対応状況はvuejs/awesome-vue#3544にまとまっています。

移行で利用できるツールやライブラリ

今移行するなら上記で利用していたツールやライブラリ以外に、以下のツールやライブラリを利用してみるとよさそうです。ただぼく自身はcomposition-apiしか利用していないのでどの程度完成しているかはわからないです。

まとめ

そこまで大きな書き換えは伴わなかったので比較的簡単に移行することができました。
ただ、ネイティブと同じイベント名でイベントを発火させていると、バグが多発するので予めemitsで発火させるイベントを指定しておくほうがよかったかもしれません。
ただ、traQの場合は素早いフィードバックが得られるのと基本身内向けであることから、報告が来たら直すという形でも十分対応できました。
もっと大きいプロジェクトの場合は、静的解析して検出したほうがよさそうです。

ところでこの記事11月くらいに書いて3ヶ月くらい眠らせちゃってました…
traQもv3.6.4になってていろいろ機能増えてたりします。

sappi_red icon
この記事を書いた人
sappi_red

19B。SysAd班。 JavaScript書いたりTypeScript書いたりGo書いたりRust書いたり…

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2021年5月16日
CPCTFを支えたインフラ
mazrean icon mazrean
2021年4月2日
traQの検索機能が謎のエラーを吐いた話
toki icon toki
2021年3月24日
traQのメンション・チャンネルリンク機能について【新歓ブログリレー16日目】
reyu icon reyu
2020年5月1日
爆☆誕 traQ-S【新歓ブログリレー2020 54日目】
spa icon spa
2020年4月3日
猫でもわかる(諸説)OAuth 2.0【新歓ブログリレー2020 26日目】
mazrean icon mazrean
2019年12月4日
部内製チャットサービス「traQ」UIのこれまで 【AdC2019 35日目】
spa icon spa
記事一覧 タグ一覧 Google アナリティクスについて