Webpackを利用しているReact.jsプロジェクトをViteへ移行してみた

  • ReactJS
  • 効率化

Vite(ヴィート)はビルドツールの1つで、同じ系統のツールで古株のWebpackがあります。

(Webに限った話ではないですが)開発を進めていると必然的にコード量が増えたり、またモジュールが増えてくると、ビルドの遅さが目立ってきます。Web開発時はローカルサーバーを立てつつ、ファイルを編集してブラウザで変更箇所のチェックをするといった作業は開発の常ですが、変更が即時に反映されないことがあると効率が落ちます。Vite利用することでこういった点の解消が期待できます。

Viteがよしなに取りはからってくれている背景では、esbuildの存在、そしてエコシステムの進歩があります。Viteは高速でよりストレスレスな開発体験を手軽に、そして、開発者がモダンなエコシステムを扱いやすいようにお膳立てしてくれた状態で提供してくれるナイスなツールです。

早速使ってみたいという方は、create-viteですぐにテンプレートから始められます。

既存のプロジェクトでも速さを体感したいぜ...という方も比較的容易に移行はできるかと思います。

この記事では、Webpackのv5を利用しているReact.jsのプロジェクトをViteのv2へ移行した時の作業内容を簡単にまとめておきます。

移行するプロジェクトの概要

移行しようとするプロジェクトの設定にもよって難易度は変わるかと思いますが、ここで取り上げるプロジェクトは大雑把に次のような感じです。

  • Webpack5, Babel, またそれらに関連するWebpackプラグイン
  • dotenv読み込む
  • ESLint
  • Prettier
  • TypeScript
  • React.js

Webpackを使ってビルド、バンドルしています。ローカルサーバーはwebpack-dev-serverを利用しています。どこにでもあるようなシンプルな構成です。

ここではpackage.jsonの内容は載せませんが、自分でWebpackの設定を定義しなければならない都合で、dependenciesがWebpack関連のプラグインで占められています。(devDependenciesの内容が縦に長くなり、プラグインのバージョン管理が面倒です...)

移行作業

既存プロジェクトなので、ESLint、Prettier、TypeScript類の設定ファイルは既にあり、必要に応じて読まれている方の方で変更していただければ良いです。自分の場合は特に変更はすることがないのでそのままでした。

またViteの設定ファイル(vite.config)についての内容を載せていますが、移行プロジェクトの状況に応じて読み替えていただければと思います。あくまで一例として参考になれば幸いです。

前置きは以上で、次にやることを順に並べます。

  1. Webpack関連のパッケージ、設定ファイルの削除
  2. Viteの導入と設定ファイルの作成
  3. index.htmlの更新
  4. モジュールインポートのエイリアス設定
  5. 型定義ファイルの設置
  6. vite-plugin-eslint導入
  7. vite-plugin-html導入
  8. dotenvファイルの読み込み
  9. npm-scriptsの整理
  10. 動作確認

それでは作業を進めていきましょう。

Webpack関連のパッケージ、設定ファイルの削除

webpackやwebpack-cli、webpack-dev-server並びにBabel関連のパッケージをアンインストールします。ここはプロジェクトによって様々だと思うので代表的なものを挙げてコマンドで示しておきます。

もし必要そうかな?というものがあれば、一旦残しといて、後でいらないと分かったらアンインストールするようにしてもOKだと思います。

yarn remove webpack webpack-cli webpack-dev-server
yarn remove copy-webpack-plugin css-loader css-minimizer-webpack-plugin dotenv-webpack eslint-webpack-plugin html-webpack-plugin mini-css-extract-plugin
yarn remove @babel/runtime @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react @babel/preset-typescript babel-loader

あとは、webpack.config.jsを削除します。

Viteの導入と設定ファイルの作成

# Vite導入
yarn add -D vite

# React.jsを扱うためにプラグインを併せて導入
yarn add -D @vitejs/plugin-react

# 設定ファイルをプロジェクトルートディレクトリに作成 (TypeScriptで書きたいので拡張子はtsとしておきます)
touch vite.config.ts

次にvite.config.tsファイルを編集します。

// pathモジュールで型エラーが出たら、yarn add -D @types/nodeで型定義を入れてください
import { resolve } from 'path'

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

const ROOT_DIR_PATH = resolve(__dirname, 'src')
const PUBLIC_ASSETS_DIR_PATH = resolve(__dirname, 'static')
const OUTPUT_DIR_PATH = resolve(__dirname, 'dist')

export default defineConfig(() => {
  return {
    root: ROOT_DIR_PATH,
    publicDir: PUBLIC_ASSETS_DIR_PATH,
    build: {
      outDir: OUTPUT_DIR_PATH,
      emptyOutDir: true,
    },
    plugins: [
      react(),
    ],
  }
})

設定ファイルのオプションについては、公式ドキュメントに詳しく載っていますが、上記の設定について簡単に触れておきます。

rootオプションはプロジェクトのルートディレクトリ(index.htmlが置かれているパス)を入れています。create-viteでプロジェクトを新規で始めた方ならよく分かると思いますが、index.htmlがルートディレクトリに配置されています。それが標準になっています。このプロジェクトではsrcディレクトリに格納してあるのでパスを指定しています。(もし、この例以外のパスの場合は読み替えて設定してみてください)

publicDirオプションは静的アセット(e.g. robots.txt等)を格納しておくディレクトリのパスを指定しています。標準だとpublicディレクトリですが、分かりやすいようにstaticディレクトリとしました。ここは既存プロジェクトの設定に適宜合わせてください。

build.outDirオプションはビルド結果の出力ディレクトリを指定します。build.emptyOutDirオプションはビルド時にbuild.outDirオプションで指定したディレクトリを空にします。ドキュメントに書いてありますが、build.outDirオプションで指定したディレクトリが、プロジェクトルート(rootオプション)外の場合は警告が出ます。上の設定ファイルではプロジェクトルート外なのでbuild.outDirオプションを明示しています。これにより警告を解消できます。

pluginsオプションでViteでReactを扱うためのプラグインを指定します。

他にも前述した公式ドキュメントにオプションはいろいろあるので、適宜設定してみてください。

index.htmlの更新

既存のindex.htmlファイルにも変更が必要です。

ここは公式ドキュメントのプロジェクトルートの項目で触れられていますので、説明は省略します。

既存のindex.htmlに併せて直してみてください。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="description" content="React App" />
    <title>React App</title>
    <!-- faviconファイルのパスを指定し直す -->
    <link rel="icon" type="image/svg+xml" href="/assets/favicon.svg" />
  </head>
  <body>
    <div id="app"></div>
    <!-- 次のような形式でindex.tsx(エントリポイントとなるJSXまたはTSXファイル)のパスを指定し直す -->
    <script type="module" src="/index.tsx"></script>
  </body>
</html>

モジュールインポートのエイリアス設定

これはWebpackでもresolve.aliasで設定できる内容です。これをやらないとimport時のパス指定が面倒になるのでぜひやっておきましょう。

今回は既存プロジェクトでの移行作業が前提なので、tsconfigでcompilerOptions.pathsは既に設定されているものとします。それに合うようにvite.config.tsに設定します。

import { resolve } from 'path'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

const ROOT_DIR_PATH = resolve(__dirname, 'src')
const PUBLIC_ASSETS_DIR_PATH = resolve(__dirname, 'static')
const OUTPUT_DIR_PATH = resolve(__dirname, 'dist')

export default defineConfig(() => {
  return {
    root: ROOT_DIR_PATH,
    publicDir: PUBLIC_ASSETS_DIR_PATH,
    build: {
      outDir: OUTPUT_DIR_PATH,
      emptyOutDir: true,
    },
    // ここから
    resolve: {
      alias: [{ find: '~', replacement: ROOT_DIR_PATH }],
    },
    // ここまで
    plugins: [
      react(),
    ],
  }
})

設定内容は適宜既存のものに合わせてください。

型定義ファイルの設置

/// <reference types="vite/client" />

型定義ファイルが既にあれば、そちらに追記すれば良いです。まだ無ければcreate-viteで作られるテンプレートに倣って、vite-env.d.tsの名前でsrcディレクトリ以下に配置しましょう。

配置場所はsrc/types/vite-env.d.tsとしました。

vite-plugin-eslint導入

この作業はESLintを導入しているプロジェクトの場合だけでOKです。

yarn add -D vite-plugin-eslint

そして、vite.config.tsを編集します。

import { resolve } from 'path'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// vite-plugin-eslintプラグインをimport
import eslintPlugin from 'vite-plugin-eslint'

const ROOT_DIR_PATH = resolve(__dirname, 'src')
const PUBLIC_ASSETS_DIR_PATH = resolve(__dirname, 'static')
const OUTPUT_DIR_PATH = resolve(__dirname, 'dist')

export default defineConfig(() => {
  return {
    root: ROOT_DIR_PATH,
    publicDir: PUBLIC_ASSETS_DIR_PATH,
    build: {
      outDir: OUTPUT_DIR_PATH,
      emptyOutDir: true,
    },
    resolve: {
      alias: [{ find: '~', replacement: ROOT_DIR_PATH }],
    },
    plugins: [
      react(),
      // プラグインの設定
      eslintPlugin(),
    ],
  }
})

プラグインのオプションについては、以下を参照してください。

🌍 https://github.com/gxmari007/vite-plugin-eslint

vite-plugin-html導入

この作業も任意です。ビルドしたHTMLファイルをminifyするために利用します。(また、このプラグインでビルド時にHTMLファイルに変数を埋め込むことも可能です)

yarn add -D vite-plugin-html

※ 以下の設定方法はvite-plugin-htmlv2での書き方です。新しいバージョンだと異なった書き方になっていますので、ドキュメントを参照してください。

import { resolve } from 'path'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import eslintPlugin from 'vite-plugin-eslint'
// vite-plugin-htmlプラグインをimport
import { minifyHtml } from 'vite-plugin-html'

const ROOT_DIR_PATH = resolve(__dirname, 'src')
const PUBLIC_ASSETS_DIR_PATH = resolve(__dirname, 'static')
const OUTPUT_DIR_PATH = resolve(__dirname, 'dist')

export default defineConfig(() => {
  return {
    root: ROOT_DIR_PATH,
    publicDir: PUBLIC_ASSETS_DIR_PATH,
    build: {
      outDir: OUTPUT_DIR_PATH,
      emptyOutDir: true,
    },
    resolve: {
      alias: [{ find: '~', replacement: ROOT_DIR_PATH }],
    },
    plugins: [
      react(),
      eslintPlugin(),
      // プラグインの設定
      minifyHtml(),
    ],
  }
})

冒頭で触れたビルド時にHTMLに変数を埋め込む方法は、injectHtml()を利用することで実現できます。

vite.config.tspluginsオプションでinjectHtml({ injectData: { HOGE: '埋め込む内容' } })という内容で設定し、HTMLファイル側で置き換えたい場所に<%= HOGE %>と書けば良いです。

dotenvファイルの読み込み

.envファイル(以下、dotenvファイル)の読み込みはViteは標準で対応しています。

ファイル名のパターンは.env.local.env.[mode]といったものです。

ビルドコマンド実行時に指定できます。デフォルトだと開発デバッグ時はdevelopmentモード、ビルドコマンド時はproductionモードに設定されます。仮にプロジェクトでステージング環境用に設定を変えたいといった場合は、ステージング用のdotenvファイルを用意し、ビルドコマンド実行時にモードを明示することで実現できます。このあたりは公式の「環境変数とモード」のドキュメントに詳しく載っています。

ビルド時にdotenvファイル中の変数をクライアント側で展開するには、変数名の接頭辞をVITE_にします。これはvite.config.tsで変更できるので、PUBLIC_といった接頭辞にしても構いません。

前置きが長くなりましたが、vite.config.tsを変えましょう。

import { resolve } from 'path'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import eslintPlugin from 'vite-plugin-eslint'
import { minifyHtml } from 'vite-plugin-html'

const ROOT_DIR_PATH = resolve(__dirname, 'src')
const PUBLIC_ASSETS_DIR_PATH = resolve(__dirname, 'static')
const OUTPUT_DIR_PATH = resolve(__dirname, 'dist')
// dotenvファイルがあるディレクトリへのパス
const ENV_DIR_PATH = __dirname

export default defineConfig(() => {
  return {
    root: ROOT_DIR_PATH,
    publicDir: PUBLIC_ASSETS_DIR_PATH,
    // dotenvファイルがあるディレクトリへのパスを指定
    envDir: ENV_DIR_PATH,
    // クライアント側へ展開する変数名の接頭辞を「PUBLIC_」にする
    envPrefix: 'PUBLIC_',
    build: {
      outDir: OUTPUT_DIR_PATH,
      emptyOutDir: true,
    },
    resolve: {
      alias: [{ find: '~', replacement: ROOT_DIR_PATH }],
    },
    plugins: [
      react(),
      eslintPlugin(),
      minifyHtml(),
    ],
  }
})

envPrefixオプションはお好みで設定してください。デフォルトのVITE_で良いなら未指定で結構です。

envDirオプションはrootオプションでsrcディレクトリを指定しているので変更します。srcディレクトリの一階層上にしています。(vite.config.tsがある階層と同じになるかと)

イメージしやすいようにディレクトリツリーを示しておきます。なんとなく構成が分かれば良いので、最小限の情報に絞っています。

.
├── .env.example
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── README.md
├── package.json
├── src
│   ├── assets
│   │   └── favicon.svg
│   ├── components
│   │   └── App.tsx
│   ├── index.html
│   ├── index.tsx
│   └── types
│       └── vite-env.d.ts
├── static
│   └── robots.txt
├── tsconfig.json
├── vite.config.ts
└── yarn.lock

話は環境変数に戻ります。Viteではprocess.envではなく、import.meta.envで読み込んだ環境変数にアクセスできます。これはViteのお作法なので既存コード中でprocess.envで参照している箇所は直しましょう。

TypeScriptで開発しているのであれば、型定義ファイル(vite-env.d.ts)に以下のように独自定義を追加できます。

今後仕様変更等でうまいこといかなくなってしまったという場合は公式の「環境変数とモード」のドキュメントをご覧ください。

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly PUBLIC_CUSTOM_ENV_VALUE: string
  // 接頭辞を標準のVITE_のままなら次のように書けます。
  // readonly VITE_CUSTOM_ENV_VALUE: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

上記の例だと、コード中からimport.meta.env.PUBLIC_CUSTOM_ENV_VALUEと書くとエディタが補完してくれるようになるかと思います。

npm-scriptsの整理

ざっと最低限(と思われる)の設定はできたかと思います。お疲れ様でした。

最後はnpm-scriptsからWebpackを使っていた頃のコマンドを除き、Viteを使ったコマンドに加えましょう。

package.jsonを開いて、以下のような感じで書きます。ここは移行対象のプロジェクトによってさまざまであるので、必要最低限のものを掲載します。また、適宜コマンド名も読み替えてください。

{
  "scripts": {
    "start": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  }
}

動作確認

あとはちゃんとビルドできるか、ビルドしたもので動くかどうか等をしてください。問題がないことを祈ります。🙏

プロジェクト固有な設定があったりした場合は、必要に応じてドキュメントでオプションを探してみたり、プラグインを探してみてください。大掛かりなプロジェクトですと、なかなかに大変かもしれません。

問題がなければ、無事、Viteによる高速かつモダンな開発環境ができたかと思います。🎉

移行してみての感想

実際にViteでビルドをしてみると噂に違わず高速でした。

あと、dependenciesを占めていたWebpackのプラグイン類を一掃できた点もよかったです。利用パッケージのバージョンメンテのことを考えるとできれば抑えておきたいですからね。

設定ファイル(vite.config)が標準でTypeScriptで書けることもポイントが高いです。補完が効くのでオプションを探しやすいです。

一部Viteのお作法に則らないいけないことはありますが、それはWebpackの時ほど大変ではないでしょう。今後、Viteがどうアップグレードしていくかは分かりませんが、なんとなく今後も長く付き合っていけそうなツールな気がします。

長くなりました!以上です。 👋

この記事を共有

アバター

K.Utsunomiya
Dirty Thirty
主にWebフロントエンド技術と気になった音楽について投稿していきます。
最近ハマっていることは、クロスバイクで走ることとジムでの運動です。
詳しいプロフィール