Vue CLIからViteに移行した

2023/10/26に公開

自分が携わっているハコベル配車管理でVue CLIからViteに移行したので、
やったことや苦労したことを共有してきます。

フロントエンド構成と利用バージョン

ハコベル配車管理のUIは夏ごろにVue 2.7へのアップデートを行いました。

https://zenn.dev/hacobell_dev/articles/4552795a24952c

Vite移行前の主な利用ツール・ライブラリは以下の通りです。

  • 言語
    • JavaScript / TypeScript
  • UI関連
    • Vue / Vuex / Vue Router / Pug / Stylus / Element UI
  • Webフレームワーク
    • Fastify
  • バンドラー
    • Vue CLI
  • テスト
    • Jest / TestCafe
  • リンター・フォーマッタ
    • Prettier / ESLint

UIの構成としてはFastifyとVueがメインとなっています。
ページとアセットのルーティングにFastifyを利用しており、ページごとのエントリポイントとアセットをVue CLIでバンドルしていました。
また、ローカル環境ではアセットへのアクセスにnginx proxyを利用して、webpack-dev-serverと連携していました。

Ruby on Railsのモノリシック構成からフロントエンド・バックエンドでリポジトリを分ける際に
この構成となったのですが、背景や詳細は以下のリンクをご覧いただくことにして割愛させてください。

参考: Docker + Fastify + webpack-dev-server によるフロントエンド開発環境

今回のVite移行では影響範囲を小さくしたかったので上記の構成は基本的に変更せず、
Vue CLIからViteへの移行のみを行っています。

移行した理由

一番大きな移行理由は当時利用していたNode.js v16系のEOLでした。

Node.js v17からOpen SSLのバージョンがアップデートされており、
Vue CLI (≒Webpack) v4系からv5系へのアップデートが必要となっていました。

そこでv5系に移行する代わりとして、プレビュー・ビルドの高速化を期待してViteへの移行をトライしてみました。

参考: https://github.com/webpack/webpack/issues/14532

移行の流れ

Vue CLIからViteへの基本的な移行作業(dependencies更新など)は以下を参考にしました。

https://vueschool.io/articles/vuejs-tutorials/how-to-migrate-from-vue-cli-to-vite/

上のガイドに記載がなかったことや私達の環境で必要だった作業は以下の通りです。

  • JestからVitestへの移行
  • 静的ファイルへのエイリアス修正
    • ~@/images/xxx@/images/xxx に書き換え
  • (私達の環境で必要だった作業)
    • PugとStylusのインストール
      • Pug
        • yarn add -D pug @vue/language-plugin-pug
      • Stylus
        • yarn add -D stylus
    • FastifyのViewテンプレート・ロジックの修正
      • 色々な連携ツールがありますが、利用可能なものを探せず手直しした
      • server.origin設定でローカルのVite dev serverを指定する
    • nginx proxyの設定を変更し直接 Vite dev serverと通信するようにした

FastifyのViewテンプレート・ロジック修正のサンプルコードを書いておきます。

// FastifyのViewテンプレート(handlebarsを利用)
{{#if productionMode}}
  {{#each stylesheets}}
    <link rel="stylesheet" href="/assets/{{this}}" type="text/css" />
  {{/each}}
  {{#each scripts}}
    <script type="module" src="/assets/{{this}}"></script>
  {{/each}}
{{else}}
  <script type="module" src="http://localhost:{{viteDevServerPort}}/assets/@vite/client"></script>
  {{#each scripts}}
    <script type="module" src="http://localhost:{{../viteDevServerPort}}/assets/pages/{{this}}"></script>
  {{/each}}
{{/if}}
// Viewのパラメータ設定
// repはFastifyReply
rep.view(template, {
  title: (route.title || []).concat(DEFAULT_TITLE).join(' | '),
  // entriesには。ページごとのエントリポイントの名前を渡している
  // Vite dev serverの場合とパスが異なるので分岐している
  // manifestにはマニフェストJSONファイルを読み込んでパースしたデータが入っている
  scripts: productionMode
    ? entries.map(entry => (manifest[`pages/${entry}.ts`] ?? {})['file'])
    : entries.map(entry => `${entry}.ts`),
  productionMode,
  viteDevServerPort: process.env.VUE_PORT ?? '5173',
})

上のコードから分かるように、Vite dev serverを利用する場合は以下にアクセスします。

  • http://localhost:5173/assets/@vite/client
  • http://localhost:5173/assets/pages/{{filename}}.ts

そのため、vite.config.tsでoriginを以下のように設定しています。

server: {
  strictPort: true,
  origin: 'http://localhost:5173',
},

苦労したこと

JSX記法の書き直し

vite-plugin-vue2ではVue独自のJSX変換がサポートされていなかったため、
一部で利用されていたJSX記法をSFCとレンダー関数に書き直しました。
参考: https://github.com/vitejs/vite-plugin-vue2/issues/9

本番環境用ビルド時にCSS適用順が変わることへの対応

開発中は問題ないのですが、本番環境でビルドしたときに結合されたCSSファイルを利用するとスタイルの適用順が変わってしまいスタイルが崩れてしまう現象が起きていました。
(例えば、開発中は子コンポーネント側のCSSが適用されていたのに本番ビルド時に親コンポーネントのスタイルが適用されてしまうなど...)
関連していそうなissue: https://github.com/vitejs/vite/issues/4890

また、私達の環境が複数のTypeScriptファイルをエントリポイントにしているからか、ViteでのCSS分割がうまくいかない状況でした。
関連していそうなissue: https://github.com/vitejs/vite/issues/14192

解決策として、上のエントリポイントに関するissueでも言及されていた
vite-plugin-css-injected-by-jsを利用してCSS分割を実現しました。

モジュールの動的インポートエラー対応

ハコベル配車管理のUIは上記の通りFastifyでページのルーティングをしているので、
新しいバージョンをデプロイ後もページ遷移時にアセットを更新できるようになっています。
しかし一部ページでVue RouterのLazy Loadingを利用しており、デプロイ前後でその画面を開いていたユーザーがモジュール読み込みエラーになっていました。

Lazy Loadingを活用できるほうがパフォーマンスの最適化にはつながりますが、今回はいったん一部利用だったLazy Loadingを通常のルーティングに戻して対応しました。

まとめ

この記事ではVue CLIからViteへの移行の流れ、苦労した点を紹介しました。

工数はUI開発と並行したのもあり正確ではないですが1人月弱かかった感覚です。
Viteに移行した効果としてビルド時間の短縮、開発サーバー起動やHMRの反映が高速になって開発体験も大幅に改善できました。

Hacobell Developers Blog

Discussion