webpack から Vite への乗り換え
はじめに
こんにちは!株式会社POLでエンジニアをやっている @show_kanamaru です!
POL は「研究者の可能性を最大化するプラットフォームを創造する」をビジョンに、理系学生に特化した採用サービス、および研究開発者・技術者に特化した転職/採用サービスの2サービスを運営しています。
今回は、開発効率を向上させるために、webpack から Vite への乗り換えを検討しているため、その背景や移行の際に発生したエラーなどの紹介をしたいと思います!
背景
現在の LabBase のプロダクトはビルド/バンドルツールに webpack を使用しているのですが、開発環境での初回起動やホットリロード、リリース時のビルドの時間が長いという課題がありました。
webpack の性質上、ファイル数が多くなればなるほど時間がかかってしまうため、webpack の設定をいじるというよりは他のツールへの移行をしていく流れになりました。
そこで、今回は webpack から Vite への乗り換えを試してみたので紹介したいと思います!
Vite とは?
Vite は Vue.js 作者の Evan You 氏が開発したビルドツールです。
Vite の特徴はとにかく速いこと。
Vite はフランス語で「素早い」という意味で「ヴィート」と発音するそうです。(最初「ヴァイト」と発音していました)
従来のハンドルツールはアプリケーション全体を走査し、依存解決しバンドルしていたのに対し、Vite はネイティブ ES モジュールを利用することで、開発環境ではバンドルすることなく、ブラウザから直接モジュールを読み込むことができるため高速になります。
Vite 移行
Vite を 0 から導入するのは公式ドキュメントや Vite に関する記事を読めば簡単にできると思うので、細かな手順の説明は省略します。今回は、既存プロジェクトの一部に Vite を導入する際に出たエラーを中心に説明したいと思います。
Flow
LabBase を最初に開発したときは JavaScript を使用しており、型管理に Flow を使っていました。(現在 TypeScript に移行中です!)
Vite は Flow の構文を処理することができません。
これを解決するために、flow-remove-types を使用しました。
これは Flow の構文を使用している JavaScript を Flow なしの JavaScript に変換してくれるライブラリです。
js, jsx ファイルをフィルターして、flow-remove-types により Flow なしの JavaScript に変換してあげます。(本来は Flow の構文を使ってるファイルのみに実行してあげたい)
import removeTypes from 'flow-remove-types';
import { createFilter } from 'rollup-pluginutils'
export const flowPlugin = (): VitePlugin => {
const filter = createFilter(/\.(jsx?)$/, /node_modules/);
return {
enforce: 'pre',
name: 'flow-plugin',
transform(code: string, id: string) {
if (filter(id)) {
const transformed = removeTypes(code, {
all: false,
pretty: false,
ignoreUninitializedFields: false,
});
return {
code: transformed.toString(),
map: null,
};
}
},
};
};
また、Vite は esbuild を使用して依存関係の事前バンドルを行っているのですが、esbuild も .js ファイルに有効な js 構文のみが含まれていると想定しているため Flow を処理できません。よって、esbuild 用のプラグインも以下のように作成します。
import { readFile } from 'fs';
import removeTypes from 'flow-remove-types';
export const esbuildFlowPlugin = (): EsbuildPlugin => ({
name: 'esbuild-flow-plugin',
setup(build) {
build.onLoad({ filter: /\.(jsx?)$/ }, async ({ path }) => {
const src = await new Promise((resolve, reject) => {
readFile(path, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data.toString('utf-8'));
}
});
});
const transformed = removeTypes(src);
return {
contents: transformed.toString(),
loader: 'jsx',
};
});
},
});
これらを Vite の設定ファイルで読み込みます。
import { defineConfig } from 'vite';
import { flowPlugin, esbuildFlowPlugin } from './vitePluginFlow';
export default defineConfig() => {
return {
plugins: [
flowPlugin(),
],
optimizeDeps: {
esbuildOptions: {
plugins: [esbuildFlowPlugin()],
},
},
};
});
js -> jsx
LabBase では、JSX 記法を使用していても .js 拡張子にしていることがほとんどでした。しかし、.js ファイル内で JSX 記法を使用していると以下のようなエラーが発生します。
If you are using JSX, make sure to name the file with the .jsx or .tsx extension.
よって、JSX 記法を使用している場合は、拡張子を .jsx or .tsx にしてあげる必要があります。
react-intl
LabBase では、i18n 対応で react-intl を使用しているのですが、react-intl を使いやすくするための babel-plugin-react-intl-auto というプラグインも使用しています。
何も設定を追加せずに開発環境を起動すると、コンソールで以下のエラーが発生します。
Uncaught Error: [React Intl] An `id` must be provided to format a message.
babel-plugin-react-intl-auto は message ファイルの id を自動生成してくれるのですが、Vite ではこのプラグインが読み込まれず id が生成されていないのが原因でした。
Vite が babel-plugin-react-intl-auto を認識できるように vite.config.json を以下のように修正します。
export default defineConfig({
plugins: [
react(
{
babel: {
plugins: [
'react-intl-auto'
]
}
}
)]
})
※ 参考記事
環境変数
LabBase は企業側、学生側でディレクトリが分かれています。
- labbase_frontend
- app
- labbase_client (企業側)
- labbase_student (学生側)
今回は試しに企業側だけ Vite に乗り換えをしてみたので、環境変数の読み込みを工夫する必要がありました。
Vite では .env ファイルで定義した環境変数を import.meta.env で読み込むことができます。また、環境変数が誤ってクライアントに漏れてしまうことを防ぐために、VITE_ から始まる変数のみが公開されます。
ただ、既存の環境変数は process.env で読み込んでいるかつ、VITE_ 始まりの変数になっていないため 、Vite 環境でも既存の変数名を process.env で読み込めるようにしてあげます。
まず、Vite 用の env ファイルを新しく作成します。
VITE_SOME_KEY=123
次に、公式ドキュメントを参考にしながら新しく作った env ファイルを設定ファイルで読み込み、VITE_ の prefix を外して、process.env で読み込めるようにしてあげます。
import { defineConfig, loadEnv } from 'vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
const processEnvValues = {
'process.env': Object.entries(env).reduce((prev, [key, val]) => {
const newKey = key.replace('VITE_', '');
return {
...prev,
[newKey]: val,
};
}, {}),
};
return {
define: processEnvValues,
};
});
これで、アプリケーションのコードはいじることなく環境変数が読み込めるようになりました!
結果
上記のハマりポイントを乗り越え、無事 Vite 移行に成功しました!
最終的な設定ファイルはこちら
import * as path from 'path';
import react from '@vitejs/plugin-react';
import { defineConfig, loadEnv } from 'vite';
import svgr from 'vite-plugin-svgr';
import { flowPlugin, esbuildFlowPlugin } from './vitePluginFlow';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
const processEnvValues = {
'process.env': Object.entries(env).reduce((prev, [key, val]) => {
const newKey = key.replace('VITE_', '');
return {
...prev,
[newKey]: val,
};
}, {}),
};
return {
plugins: [
flowPlugin(),
react({
babel: {
plugins: ['react-intl-auto'],
},
}),
svgr(),
],
build: {
outDir: '../../build/labbase_student',
},
define: processEnvValues,
root: 'app/labbase_student',
optimizeDeps: {
esbuildOptions: {
plugins: [esbuildFlowPlugin()],
},
},
resolve: {
alias: {
labbase_client: path.resolve(__dirname, 'app/labbase_client'),
labbase_student: path.resolve(__dirname, 'app/labbase_student'),
},
},
server: {
port: 3000,
},
};
});
そして、Vite 化によってどれだけ高速化されたのかがこちら!
初回起動
約2分 → 約30秒(約1分半減!)
ホットリロード
約10秒 → 1秒かからず(約10秒減!)
ビルド時間
約5分 → 約1分半(約3分半減!)
おわりに
今回は webpack から Vite への乗り換えを試してみました。明らかにいろいろと速くなって開発効率が向上したので、他のプロジェクトは基本的に Vite を使用していきたいと思いました。また、webpack と比べて設定ファイルが非常にシンプルなのが良いですね!ただ、まだ最適な設定ができている訳ではないと思うので、これからもいろいろと試行錯誤していきたいと思います!
そして、株式会社POL ではエンジニア、デザイナー、プロダクトマネージャーを大募集してます!お話しだけでも構いませんのでお気軽にお声がけください!!!