Libry Developers Blog

Libry開発チームによるプロダクト開発ブログ

フロントリニューアルにVue3を採用した話

f:id:libry:20201029115525j:plain
vue.js

初めまして。
株式会社Libryに9月に入社した新入社員エンジニアの長谷川です。
現在、社内ではフロントリニューアルプロジェクトというものが動いており、入社早々、プロジェクトに入ることになりそうです。

今回は、フロントリニューアルに使用するVue3.0についてお話ししようと思います。
Vue3.0はメジャーバージョンがついこの間リリース(2020/09/19)され、まだまだ記事が少ないため、皆さんの役に立つ記事になれば嬉しいです。

なぜフロントリニューアルにVue3.0を選ぶのか

そもそもなぜVueを採用するのかから話す必要があるかと思います。
Vueを選ぶ理由は大きく2つあります。

1. Progressive Framework

Progressiveとは「進歩的、進行性の」という意味の単語で、
ここでは、段階的に開発者が機能を選択できるフレームワークという意味です。

私たちは、チームのメンバーの人数やスキルという問題やアプリケーション固有の複雑といった問題を考慮してフレームワークを選定します。
ただ、これでめでたしめでたしとならないのがWebアプリケーションの特徴ですよね。。

今回のフロントリニューアルも現状のメンバーやスキル、それにアプリケーションの複雑さ
といった部分は今後状況が変化していく可能性が大いにあるのです。

このあたりの問題をクリアできるように"The Progressive Framework"という思想のもと
設計されたのがVue.jsであり、私たちがVue.jsを選んだ理由の1つです。

2. シンプルで書きやすい

Vueを使用したことがある方ならお分かりかと思いますが、とても使いやすいです。
また、学習コストもそれほどかからず実装することが可能です。
これはスピードを持って開発する必要のあるLibryのようなベンチャー企業にはとても大事な要素です。


上記はVueを選んだ理由を書きました。
それではVue3.0を選ぶ理由についてお話しします。

1. 圧倒的なパフォーマンスの向上


Vue3.0はVue2.0より圧倒的にスピードやパフォーマンスが向上しています。
特徴については後述しますが、パフォーマンスが向上して選択しない手はないですね。

2. TypeScriptの完全サポート


LibryのフロントリニューアルではTypeScriptが採用されています。
また、Vue3.0はTypeScriptで書かれています。
今までアプリケーション内で定義する必要があったTypeScriptの定義も書く必要がなくなります。


これらの理由により、私たちはVueそして、最新のVue3.0を選択しました。

Vue3.0の特徴


まずはVue3.0のリリースノートを見てみましょう。
https://github.com/vuejs/vue-next/releases/tag/v3.0.0
色々書かれていますね。
中でも面白いなと思ったのが、コードネームが「One Piece」となっていること。

Vueは実はメジャーバージョンにはアニメの名前が付けられているんですね。
そしてABCから順番に名付けられているというのが特徴です。

v3.0.0 One Piece
v2.6.0 Macross
v2.5.0 Level E
v2.4.0 Kill la Kill
v2.3.0 JoJo's Bizarre Adventure
v2.2.0 Initial D
v2.1.0 Hunter x Hunter
v2.0.0 Ghost in the Shell
v1.0.0 Evangelion
v0.12.0 Dragon Ball
v0.11.0 Cowboy Bebop
v0.10.0 Blade Runner
v0.9.0 Animatrix


参考URL:https://qiita.com/oz4you/items/7c3368efa687387293fa#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%81%A8%E3%82%B3%E3%83%BC%E3%83%89%E3%83%8D%E3%83%BC%E3%83%A0%E3%81%AE%E4%B8%80%E8%A6%A7%E8%A1%A8


(NはVue2系からVue3系に移行をスムーズにしやすくするためのバージョンv2.7.0につくのではと期待されています。)

1. 圧倒的なパフォーマンスの向上


まず、Vue3.0は2系に比べて、圧倒的にパフォーマンスが向上しています。
Vue3.0はVue2.0のコードを書き直して作られています。
要はゼロから再構築しているわけですね。

皆さんも作成したアプリケーションをイチから作り直したらコード綺麗になるのになぁ。
ここ書き直したいなぁ。と思ったことはありませんか。

そうなんです。ゼロから作られてコードがリファクタリングされているわけですね!
Vue製作者のEvan Youさんもコードを縮小することでパフォーマンスを向上しているわけですね。

また、リリースノートを見てみると"compiler-informed Virtual DOM"が新しく導入されたため、
圧倒的にパフォーマンスが向上したということが書かれています。

コンパイラーにかなり手を加えられていそうですね。

以前、Vue3.0のオンライントークに参加させていただいた際、
デモで1つ紹介されていたのですが、Vue 3 Template Explorerを使ってhoistedの再利用に関する最適化の紹介もされていたのが
面白かったので共有します。

下記サイトで確認してもらえればと思うのですが、
hoistedをrender関数の外で生成することによりhoistedを再利用することが可能になり、DOMの再利用が行われていますね。

Vue 3 Template Explorer
上記などを諸々行った結果、Vueの最適化が行われているというわけです。

2. 大規模開発をしやすくするために導入された新しいAPI


Vueは今まで大規模開発には不向きなのではと言われてきました。(個人的にはそんなことはないのですが。)
そんなマイナスイメージを払拭するためにComposition APIが導入されました。

Composition APIとは

OptionsAPIとCompositionAPI

参考:code-organization


Vue3.0で一番注目されているものはこのComposition APIかなと思います。
フロントリニューアルではまだまだ知見が溜まっていないのであまり今回は詳しく書きませんが、
(また知見がたまればCompositionAPIだけで記事を書きたい。)
CompositionAPIのメリットとしては以下があります。

  • TypeScriptと併用して型推論を十分に使えるようになる。
  • 関数を切り分けることができるため、再利用性が高まる。
  • 上記を含めて可読性があがる。


等のメリットがあります。
今後は社内でもCompositionAPIを使用していくと思うので、知見をためて書き起こしていきたいと思います。

3. TypeScriptの完全サポート

Vue3.0を選んだ理由でも書きましたが、Vue3.0のコードベースはTypeScriptで記述されており、
自動的に生成、テスト、バンドルされた型定義が含まれているため、常に最新の状態に保たれます。

よってTypeScriptのための設定ファイルを入れる必要もなくなり、またTSXを完全にサポートされているわけですね。

今Vue3.0にした際のデメリット

既に運用しているものに関しては、いきなりVue3.0に上げてしまうとApp.vueや各コンポーネントの記述・記法が変わってしまっていたりと、
かなり改修が大変になってしまいます。

この後、Vue3.0にスムーズに移行できるようにVue2.7がリリースされる予定みたいなので、
そちらを待ってからVue2.7 → Vue3.0に移行するのがいいのかなと思います。
(※ 実際に私もVue3.0にアップデートした際に少し苦戦したので。。)

Vue3.0を構築してみる

今回私は新規プロジェクトを立ち上げる際の作成と
既存プロジェクト(フロントリニューアルのリポジトリはこっち)のVue2系をVue3.0にアップデートの
両方を経験したので両方とも書いてみようと思います。

新規開発方法

新規開発環境構築はとても簡単です。

まず、vue-cliが最新バージョンになっているか確認します。
(Vue3.0は、@vue/cli 4.5.0以上からサポートされています。)
参考:vue-cli 4.5.0

# vue -V
@vue/cli 4.5.7

もしバージョンが低い場合は、下記コマンドを実行しましょう。

# yarn global add @vue/cli

下記コマンドで新規プロジェクトを作成します。

# vue create プロジェクト名

Vue CLI v4.5.7
? Please pick a preset:
  Default ([Vue 2] babel, eslint)
❯ Default (Vue 3 Preview) ([Vue 3] babel, eslint)
  Manually select features

package.jsonを確認しましょう。

  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^3.0.0-0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0-0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^7.0.0-0"
  },
# yarn serve

これだけでVue3.0が作成できてしまいましたね。

既存プロジェクトをVue3.0に変更

では今回、私は元々作成してあったVue2.6プロジェクトからVue3.0にバージョンをあげる処理を行いましたので、
そちらの説明もしたいと思います。

既存のプロジェクトのリポジトリで下記コマンドを実行します。

# vue add vue-next

こちらのコマンドで、vue-cli-plugin-vue-next
インストールしています。

このプラグインは、Vue、Vuex、Vue RouterもVue3.0に合わせてアップデートされます。

  • src/main.ts

  • before

import "./registerServiceWorker";
import App from "./App.vue";
import Vue from "vue";
import router from "./router";
import store from "./store";


Vue.config.productionTip = false;
new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");
  • after
import "./registerServiceWorker";
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";

const app = createApp(App);
app.use(router);
app.use(store);
app.mount("#app");
})();
  • src/store/index.ts

  • before

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: {},
});
  • after
import { createStore } from "vuex";

const state = {};
const getters = {};
const actions = {};
const mutations = {};

export default createStore({
  state,
  getters,
  actions,
  mutations,
});
  • src/router/index.ts

  • before

import Home from "../views/Home.vue";
import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "home",
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue"),
  },
  ...
]

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes,
});

export default router;
  • after
import Home from "../views/Home.vue";
import { RouteRecordRaw, createRouter, createWebHistory } from "vue-router";

const routes: Array<RouteRecordRaw> = [
  {
    path: "/",
    name: "home",
    component: Home,
  },
  ...
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

export default router;

ここで自分の環境で立ち上げてみるとびっくりするほどエラーが出ました。。笑 うげっ!? image

1つ1つ解消していきます。

まず、TypeScriptが統合されたことにより、Vue.extendが不要になったため、削除

  • before
export default Vue.extend({
})
  • after
export default ({
})

これでProperty 'extend' does not existエラーは解消されました。

次に、ドキュメントのGlobal APIを確認しながら、 エラーを解消していきます。

image 抜粋資料:https://v3.vuejs.org/guide/migration/global-api.html#a-new-global-api-createapp

Vue2系 Global APIの記述をVue3系 Instance APIに変更する。

Global API こちらの内容をもとに下記記述になっているか確認

  • Vue.config → app.config
  • Vue.config.productionTip → 削除
  • Vue.config.ignoredElements → app.config.isCustomElement
  • Vue.component → app.component
  • Vue.directive → app.directive
  • Vue.mixin → app.mixin
  • Vue.use → app.use

大体エラー部分は取り切ったかな?と思ったのですが、エラーが。。
Vue3.0から不要になってpackage.jsonから削除したvue-template-compilerを
一生懸命tests/unit/example.spec.tsで取り出そうとして、
Cannot find module 'vue-template-compiler'が出るのです。。

image

これがなかなか分からず、先輩エンジニアに助けてもらいました。。 下記ファイルを変更して、"vue-jest": "^5.0.0-0"を導入すると簡単に直りました。 エラー部分を断片的に見てしまっていたため、unit-testの設定がおかしいことに気づかなかったことに関しては、 まだまだだなと思います。

  • jest.config.js

  • transformプロパティの追加

transform: {
  "^.+\\.vue$": "vue-jest",
}
  • src/shims-vue.d.ts

  • before

declare module "*.vue" {
  import { ComponentOptions } from "vue";
  const component: ComponentOptions;
  export default component;
}
  • after
declare module "*.vue" {
  import type { DefineComponent } from "vue";
  const component: DefineComponent;
  export default component;
}
  • tests/unit/example.spec.ts

  • after

import HelloWorld from "@/components/HelloWorld.vue";
import { shallowMount } from "@vue/test-utils";

test("HelloWorld.vue", () => {
  const msg = "new message";
  const wrapper = shallowMount(HelloWorld, {
    propsData: { msg },
  });

  expect(wrapper.text()).toMatch(msg);
});

以上が既存プロジェクトをVue3.0に変更する方法でした。

Tips

Vue3.0を学んでいくとこれ便利だなと思った追加機能がいくつかあったので、ご紹介いたします。

・ Provide / inject

通常、親コンポーネントから子コンポーネントにデータを渡す必要がある場合は、propsを使用します。
下記画像のような深いコンポーネント構造になった場合、propsで順番にデータを渡すため、かなり煩雑な形になっていました。
今まではこれらを解決したのがVuexでした。
ただ、Vuexはグローバル変数として呼び出されてしまいコンソール上でデータを叩けるという問題点等もありました。
Vue3.0では、Provide / injectをすることにより、 下記画像のように親コンポーネントでproviderした物を子や孫コンポーネントでinjectして受け取ることができるようになります。 かなり画期的ですね! ぶっちゃけこれを使うとグローバル変数として呼び出すVuexがほとんど必要なくなると思ってます。
参考:https://v3.vuejs.org/guide/component-provide-inject.html#provide-inject

components_provide

・ v-model(双方向バインド) のvalue名を変更できるように

v-modelを使ったことはありますでしょうか。 今までv-modelは、呼び出し元にvalueでしか値を渡すことができませんでした。

<ChildComponent v-model="pageTitle" />

<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

どちらも同義
v-modelは:valueと@inputで構成されているが、
このv-modelのvalueは今まで変えることができなかった。

それが下記の通り、指定した名前で定義することが

<ChildComponent v-model:title="pageTitle" />

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

v-modelの後ろに指定したいkey名を入れることにより、今までvalueだった部分を指定したいkeyに変更できるように

どちらも可読性を上げるためにとても有効な新しいTipsでした。

まとめ

まだまだメジャーバージョンがリリースされたばかりのため、
対応できていないパッケージがあったりとまだまだこれからですが、
今後パッケージも随時対応していくにつれて、私ももっと勉強していく必要があるなと思いました。