LINEとしてVue.jsのスポンサリングを開始

花谷拓磨氏(以下、花谷):「How to development library to Vue 3」という内容で登壇いたします。よろしくお願いいたします。私はLINE株式会社のFront-end Dev9 Teamに所属している、フロントエンドエンジニアの花谷と申します。Vue.jsのコミュニティとは3年ほど前からつながりがありまして、詳しくはスライドを参照してほしいのですが、それに付随してOSS開発なども行っています。

最近LINEとしてVue.jsのスポンサリングを開始したのですが、その辺りの業務なども行っていたりして、何かとVue.jsコミュニティとつながりが深い人物となっています。

個人的なOSSでは、現在もっとも活発にメンテナンスしているパッケージはnuxt-basic-auth-moduleと、Nuxt.jsコミュニティのオフィシャルとなったdayjsモジュールの2つでして、どちらもNuxt.js用のパッケージでVue 2向けなんですが、今回は私自身、業務とかでも度々携わることがあるVue 3のライブラリ開発について紹介したいと思います。

業務で少しずつVue 3のパッケージを作っていくとなると、自分の現場に沿ったパッケージの作り方になるかなと思うんですけど、今回はあくまでもそれをOSSとして公開することを考えた場合について、話をしたいと思います。

本日のコンテンツです。本日はまず現状のVue向けのライブラリを取り巻く環境について紹介したあとに、新しくパッケージを作る場合どうするか。また、今Vue 2向けに提供しているパッケージを移行することを考えた場合にどうするかを簡単に話していきたいと思います。それでは、まずVue.jsのライブラリ開発を取り巻く環境について見ていきたいと思います。

Vue.jsのライブラリ開発を取り巻く環境

まず何よりも気になるのが、Vue 2とVue 3のシェアについてでしょう。これは現行のVue.jsの利用率を表したグラフです。新しくライブラリを作る場合、自分の現場だけでなく、より広く使ってもらうためにはユーザー全体の状況を知ることも大切です。Vue.jsはvueというパッケージ名のもと、2系と3系が同じパッケージ名で提供されていることから、厳密な計測を行うことが難しいので、今回はそれに付随する必須パッケージ「template-compiler」のダウンロード数を表示しています。

図のように、2020年9月のVue 3の正式リリース以降、徐々にVue 3向けの資産利用率も増えていますが、現状は依然としてVue 2向けの利用事例が多く存在することがわかります。現状を考慮すると、これからのためのVue 3と、後方互換性のためのVue 2。可能であれば両方サポートしたいところです。ただ一方で、両者にはいくつかの大きな違いがあります。

ライブラリ開発者の目線でいうと、大きなものだと古いブラウザのサポートが挙げられるかもしれません。これはちょうど1ヶ月ほど前に、再度Vue 3がIE11をサポートするかどうかという議論が行われた結果となります。

最新のRFCはこちらのURLから閲覧できますが、現状結論としてはVue 3はIE11に対応しない方向で進んでいまして、今後はVue 2側がその代わりバックポートのAPIを増やしていき、IE11に対応しないといけない開発者はVue 2を引き続き使い続けるということが書かれています。

このことから、モダンブラウザ向け専用のライブラリや、レガシーブラウザとの互換性を考慮したライブラリなどはVue 3に限定したり、Vue 2に限定したりといったことがあるかもしれません。

またViteの存在なども重要です。これまでVueのためのライブラリ開発、特にコンポーネントライブラリのようなSFC(シングルファイルコンポーネント)が重要なライブラリ開発では、Vue CLIやロールアップ、あるいはBiliなどのビルドツールがたくさんあったかなと思います。

ただVue 3からは、それらの選択肢より先に、優先的にViteがライブラリ開発の選択肢として挙げられるのかと思います。Viteにはplugin-vueが存在しますが、こちらはオフィシャルにロールアッププラグインをベースにしていて、それをVite向けにアレンジしてものであると明言されており、現状はこちらのほうがすでに活発に開発されている状態となっています。

またViteについては、特定の挙動において一部ロールアップの資産と、挙動で競合することなどはありますが、それらを差し引いても、継続的にメンテナンスされているViteの存在は重要になるかと思います。そのため、現状ロールアップの古いバージョンを使っていたり、あるいはBiliなどのVue 2向けツールを使っている開発者は、今後Vue 3に対応するにあたって。ビルドに関する通信から刷新する必要があります。

そのような違いがあるところが、現在のVue向けのライブラリ開発の現状となっています。まとめると依然としてVue 2は支持されており、どちらもサポートできると理想であること。しかしその一方で、Vue 2とVue 3には大きな壁があることがわかると思います。

ではその環境を踏まえた上で、新しくVue向けのパッケージを作るときの選択肢について考えたいと思います。

Vue 3を中心と

新しくパッケージを作るにあたって、まずおすすめしたいのは、Vue 2のサポートをoptionalとすることです。こちらは、先ほどまでの主張とは変わる印象を受けるかもしれませんが、我々はエコシステムの開発側であり、パッケージを作る側がVue 2を中心として開発をしてしまうと、これからもなかなかVue 3移行が進まないのではないでしょうか。

そのため新しいパッケージを開発するときは、Vue 3を中心としつつ、optionalで対応していくのが重要になってくると考えています。ただ一方で、まだライブラリ開発の環境がイメージできない、あるいはVue 2ユーザーにもある程度サポートしておきたい、さらにはもっとプライベートな事情として自分の仕事のコードベースがVue 2であるからVue 2対応が必須である、といった状況が存在することは事実かと思います。

ただそこは、実はいくつかの情報を知ることで対処できます。1つ目はVue 2とVue 3のブリッジとなってくれるパッケージの存在を知ることです。VueDemiと呼ばれるものやVue 2-3と呼ばれるものが、これに該当します。どちらも単一コードベースのもと、Vue 2とVue 3両対応できるパッケージとなっています。

ここでは詳細の紹介は省きますが、主にVueDemiはプラグイン系機能のサポート。特にカスタムフックのようなものを作るときに便利なライブラリとなっていまして、Vue 2-3はコンポーネントライブラリの開発に特化されています。どちらも、最小限のコストでVue 2に対応できるだけでなく、Vue 2-3などの場合、ユーザー側での変換も可能になっています。

こういったユーザー側の努力によって、解決できる課題がかなり増えてきているため、基本はVue 3として対応しておきながら、ユーザー側の努力に任せるというのも選択肢の1つかと思います。やはり我々が、両対応するところにハードルに感じてしまって新しいライブラリが生まれないのは機会損失になりますから、できるだけVue 3を前提として開発を進めてしまうのも選択肢の1つではあるかなと思います。

開発環境のハードル

またもう1点のハードルとして、開発環境が挙げられます。Vue 3では今後、先ほど述べた通りViteが開発の中心になっていく可能性が大いにありますが、現状Viteの開発環境を作ったことがないという方であったりとか、ライブラリの開発にあたってViteを使うのはまだハードルが高いという方もいるのではないでしょうか。

またVue 2時代に先ほど言ったようなSFCの、シングルファイルコンポーネントの解釈のためのビルド設定などで消耗したことがある方は、なんとなくVue向けのパッケージの環境構築というもののコストを高く見積もってしまうことがあるかもしれません。今回はあまり本質的な解決ではないのですけど、今回のミートアップにあたって私のほうで簡単にViteとVue 3の設定を行ったライブラリのテンプレートのようなものを用意してみました。

ここにはViteがES Modulesネイティブであるから世に出てくるCJSの対応であったりとか、あるいはVue 3をbundleファイルに含まずにnode-modulesのものを利用する設定。あるいはTypeScriptの設定であったり、VueでTest Utilsの設定などが一通り入ったテンプレートとなっています。

これからVue 3向けのパッケージを作るにあたって、環境を知ることがコストであれば、ぜひ使ってみていただければと思います。困ったことがあれば、聞いていただければ何でもサポートします。

新しくパッケージを作ることについての話は以上ですが、より多くの開発者にあなたのライブラリの価値を届けるためには、やはり両対応が必要となります。かつ、これからのことを考えながらも今Vue 2向けのパッケージを開発している場合、Vue 3向けに追加でサポートすることもあるかと思います。

次のセクションでは実際の移行経験などをもとに、両立するための手順とヒントを紹介します。ここからはLINE社内での事例紹介も含みます。

重要なのはbreaking changesへの理解

両対応にあたってまず何よりも重要なのは、breaking changesへの理解です。例えばVue.jsにはVue 2とVue 3で、いくつかの大きな破壊的な変更やフィーチャー追加が存在します。そのため、まずはオフィシャルのマイグレーションガイドを読むことによって、問題となる箇所がないかを十分に理解することが重要となります。

例えばディレクティブパッケージの開発者であった場合、Vue 3で実装されたFragmentsのニューフィーチャーについては、知っておく必要があります。

これまでVue.jsでは、コンポーネントのrootには1つのDOMしか存在しないという前提がありましたが、Vue 3からはそうではありません。そのためディレクティブの開発者はDirectiveがカスタムコンポーネントにアタッチされている場合、そのrootのchildrenが複数ある場合を考慮しないといけません。そういったことは、マイグレーションガイドを読むことによって理解できます。

またコンポーネントライブラリの開発者になると、さらに覚えることが多くあります。例えば有名なところでいうと、inheritAttrsという、Vue 2の象徴的な挙動でもあるコンポーネントにクラスや属性などを渡した場合に、それが引き継がれる設定が、Vue 3ではデフォルトでオフになっていたり。またRender FunctionがコンポーネントのRender関数に依存せず、VueのパッケージからインポートできるようになったことによってAPIが変わっていたりと、さまざまな違いがあります。

こういったところを理解しない場合、簡単に移行することは難しいため、まずは自分の開発で使っている機能のマイグレーションガイドを読み通すことをおすすめします。またこのときに現状のVue 2の挙動を十分に記録しておくことによって、スナップショットでもいいかなとは思いますが、十分に記録しておくことによって、あとからVue 3との挙動の違いがないかを確認できるため、今の挙動について理解しておくことも大切です。

どれだけ楽にbreaking changesへの対応ができるか

breaking changesについて理解したあとは、それらの対応をどれだけ楽ができるかを確認します。先ほど申し上げたようなVueDemiといったツールを利用すると、自分たちで細かな差異の挙動を吸収することなく、ライブラリに任せられます。

例えばVueDemiは、Composition APIをベースとしてロジックを書くことによってVue 2向けにPortされた@vue/composition-apiのパッケージと、Vue 3のパッケージの依存先をシームレスに切り替えられるパッケージになっています。実体としては非常に薄いものになっているので、あまり挙動に対して変な手を入れることもなく円滑な移行が可能となっています。

またVueDemiの開発者であるAnthonyさんは、Vue.jsのコアチームのメンバーでもあるため、その点でも安定性に大きく信頼を寄せられるかと思います。

実際のVueDemiの動作イメージです。VueDemiについては、基本的に我々はパッケージをVue 3側のライブラリからVueDemiパッケージを読むことで利用します。例えばreactiveを読む場合、Vueのパッケージではなく、このようにVueDemiのパッケージから読み込みます。

こうすることによって、内部のnode_modulesの中にあるVueパッケージのバージョンを確認して、Vue 2であったりVue 3であったりという依存を柔軟に切り替えるツールとなっています。なのでライブラリの開発者はふだんどおりVue 3のコードで書くだけで、Vue 2、Vue 3にユーザー側がスイッチすることによって、対応できるようになっています。

こうした場合、我々はComposition APIを書くだけで十分な提供ができるため、例えば今プラグインとして提供しているものをComposition APIとして書き直すだけで、我々の仕事は完了します。このようなコンバーターで解決できるようなものがあれば、できるだけ使っていくといいと思います。

ただ、そうではない場合もあるかと思います。具体的には、かなり表面的なAPIの違いに制御されるような場合です。そういった場合には、どのようにパッケージを切り出すかという戦略について知っておくといいかと思います。

具体的には、ゼロベースで新しいパッケージとして構成し直すか、Monorepoを使うかという話が重要になってきます。ゼロベースでパッケージを構築し直した場合、これまでのVue 1からVue 2のときにもありましたが、今Vue 2.xというパッケージとして提供されているものは、基本的にゼロベースでパッケージを構築し直したものとなります。

ゼロから構築することによって、完全に別のコードベースになるため、先ほど言及したようなツールチェインをどのように共有するか。Vue CLIやBiliといった環境とViteの環境とどのように共存するかや、破壊的変更のことを意識することなく、新しく開発することはできます。また別のリポジトリなどとして切り出すことによって、同名パッケージのバージョン違いも楽にメンテナンスできるため、@nextとして段階的にインストールするような戦略を取ることもできます。

実際、Vue.jsのコア自体がまだ@nextでインストールする時代ですから、それに合わせて@nextとして提供するというのがやりやすいのが、この方式の特徴かと思います。ただコアとなるようなライブラリのロジックがかなり厚い場合は、もちろん多重管理となってしまうので、そこは弱みとなるかなと思います。

一方でMonorepoとして提供する場合は、その逆の良さと悪さがあります。コンポーネントなどの開発ツール支援というところをそれぞれ1つのリポジトリ内で管理しないといけないというデメリットはありますが、その一方で、コアロジックの共有ができるというメリットがあります。

また一応LernaやYarn Workspaceなどのパッケージを利用することによって、放出しないことで各パッケージ内で依存を閉じさせることもできるので、そういった面でも、ある程度楽をしつつペインも少ないといった手法になります。ただ弱みとしては、特にLernaなどの場合は、パッケージ室内に同一のパッケージ名を持てないため、どうしても別名パッケージとしてパブリッシュする必要が出てきます。

そうした場合、将来的にそのパッケージ名のデフォルトをVue 3にしたい場合、どうするかといったことは考えなければなりません。

ただMonorepoを使う例は、社内でも豊富にありまして、例えばDirectiveを大量に提供しているようなパッケージがLINE社にはありますが、そういった場合はコアパッケージとしてDirective Hookを、本体を書いてしまって各パッケージではその名前をアタッチし直すだけといった処理を書くようなことがよくあります。

こういった手法を行うと、かなり低いコストでVue 2とVue 3に対応できるため、特にインターナルで両方のコストがある場合、あるいは両用の必要がある場合にはコストを低く実現できるかと思います。

また余談でありますが、どちらの方法を取る場合でも、vue-codemodについて理解しておくと、より円滑です。どちらの場合もコードが分割される以上、どうしても両方のコードベースに手を入れる必要はありますが、vue-codemodと呼ばれる、Vueのオフィシャルによって提供されているコード変換ツールを利用すると、シームレスにVue 2のコードをVue 3に移行できます。

まだexperimentalであるため、完全にすべてが動作する状態ではありませんが、知っておくことによって今後楽になるかもしれません。

Vue 2からVue 3への変更を取りやめた失敗事例

では最後に簡単になりますが、Vue 2向けライブラリのVue 3対応を取りやめたという、少し苦いケースについて紹介したいと思います。ネガティブな話にはなってしまいますが、学びとして共有できればなと思います。

対象となるパッケージは、私が個人で管理しているvue-fixed-headerというパッケージとなります。こちらはposition stickyの挙動をIE11でも実現しつつ、アニメーションに強い、優位性をもつ固定のstickyヘッダーライブラリとなっています。このパッケージは少し見づらいですが、Render Functionを中心に作られていまして、かつchildrenに依存しているという。

childrenに依存してRender Functionによってchildrenの中身を書き換えるというパッケージとなっています。実際のコードがこのようなかたちでDOMをvue-fixed-headerというコンポーネントに置き替えることによって、実DOM上に悪影響を及ぼすことなくクラスだけをアタッチするという挙動を実現しているパッケージとなっています。なので、使い心地としてはDirectiveに近いようなものとなっています。

ただこういった場合、Render Functionで挙動を実現しているため、まずFragmentに対しての対応を行う必要がありますし、その上でRender Functionがインポートされているのか。あるいは引数として渡ってきているVue 2のスタイルなのか。この差異も吸収しないといけないといった問題が出てきました。どちらも対応するのは難しくない課題ではありますが、Vue 3がどうしても依存関係に入ってしまうなどの問題が出てきて、しばらく課題を保留としていました。

その中で、先ほど挙げたようなMonorepo戦略や別リポジトリといったボキャブラリが増えてきましたが、ちょうど対応を検討していた時期にIE11のサポート終了が入ってしまい、結果としてvue-fixed-headerのコアバリューであるIE11の対応もできるようなパッケージはVue 2に対してのみ効力が発揮されるようなものとなりました。

これによって、このパッケージについては今後はVue 2向けとして提供していくこととなりました。

少し苦いエピソードではありますが、Render Functionなどに依存している場合はインポートの設定が必要であったり、あるいはDirectiveと同じようにFragmentの対応が必要であったりと、なかなか頭を悩ませる部分が多くありましたので、そういった破壊的変更を事前に知ることによって、コスト感を見積もれたところは良い点であった一方、少しそういったところで、大きく工数がかかるというところは危惧していくべきです。

Vue 3に目を向けてVue 3向けのパッケージを開発し続けること

少し長くなってしまいましたが、最後に簡単にまとめたいと思います。基本的には古いものをサポートするところを気にし過ぎるよりも、Vue 3に目を向けて、Vue 3向けのパッケージを開発し続けること。そしてユーザー側にVue 2の対応をお願いできる要素があるということを知っておくといいと思います。

ただどちらもサポートする場合は、いくつかの大きなbreaking changesについて知ったあとに、それをどれだけツールでカバーできるかを知っておくといいかと思います。ただそれでも両対応が難しいものは現実に存在しますので、そこは無理し過ぎないことも大切かと思います。持続可能な状態が一番ですので、ライブラリ開発のハードルを下げてメンテナンスのハードルを下げるところを一番にできるといいかと思います。

以上で発表を終了したいと思います。ありがとうございました。

司会者:花谷さん、ありがとうございました。それでは質問がいくつか届いているので、そちらにちょっと回答をいただきたいなと思います。最初は、「現状ではNuxtを使うとVue 2扱いになるのかな?」的な質問があります。

花谷:ちょっと簡単に回答しますと、冒頭で「Nuxtのパッケージを開発しているためVue 2にも向いている」という話をしたかなと思いますが、これについてはNuxtのエコシステムについて、やはりまだNuxt本体はVue 2が主流でして、それによって、Vue 2をサポートしないといけないことから、私が開発しているパッケージはどうしてもVue 2を向いてしまうというような意図でしした。

もしアプリケーションコードの話で言うと、Nuxt側にもComposition APIで記述できるパッケージ自体はありますので、そういったものは有効活用できるのではないのかなと思います。

司会者:なるほど。ありがとうございます。次の質問が「NuxtにComposition APIだけ追加しても他のライブラリは2系になるなら衝突気になる」という質問です。

花谷:一応Nuxt.jsだと、今回の登壇内容から逸れちゃうので、簡単に回答して終わりにしたいと思うので、詳しい話はTwitterで返しますという感じにしたいと思いますが、Nuxt.jsについては、どのみちコアがまだ今Vue 2でアプリケーション開発している限りはVue 2なので、Vue 2がデフォルトになるかなと思います。

かつ、Nuxt3についてのプレゼンテーションはこのあと行われる予定なので、ぜひそちらを見てください、という回答でよろしいでしょうか。

司会者:はい。ありがとうございます。それでは花谷さん本当にありがとうございました。