カテゴリー: FrontEnd

Vite 3を使ってみた

はじめに

Webpackよりビルドが速いと言われているフロントエンドビルドツール「Vite」のバージョン3が先日正式版が公開されました。これを機にViteについて調べて見たので紹介します。

なぜViteは早いのか

Native ESMの活用

Viteで一番わかりやすいことは、開発中のビルドが早いことだと思います。これは、Webpackのように、実装されたimportやexportを元に1つのJSファイルにバンドルするのではなく、ブラウザによって各モジュールをimportさせるようにしているため、バンドルの手間が省けているからです。
このような、ブラウザによるES Modulesの仕組みをNative ESMと呼びます。ES2015で定義された仕様で、モダンブラウザであれば対応しています。
実際に、プロジェクト生成直後にビルドされたコードを見てみます。

まず、以下がindex.htmlです。

<!DOCTYPE html>
<html lang="en">
  <head>
    <script type="module">
import RefreshRuntime from "/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>

    <script type="module" src="/@vite/client"></script>

    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

21行目で main.tsxが読み込まれているので、main.tsxを見てみます。ちなみに、 type="module"は、HTMLにModuleを埋め込む際、srcが示すパスがモジュールであることを明示するために必要なものです。

var _jsxFileName = "/home/kaiser/study/study-vite/study-vite-react/src/main.tsx";
import __vite__cjsImport0_react from "/node_modules/.vite/deps/react.js?v=7bc51448"; const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react;
import __vite__cjsImport1_reactDom_client from "/node_modules/.vite/deps/react-dom_client.js?v=39c8951a"; const ReactDOM = __vite__cjsImport1_reactDom_client.__esModule ? __vite__cjsImport1_reactDom_client.default : __vite__cjsImport1_reactDom_client;
import App from "/src/App.tsx";
import "/src/index.css";
import { jsxDEV as _jsxDEV } from "/@id/__x00__react/jsx-dev-runtime";
ReactDOM.createRoot(document.getElementById("root")).render(/* @__PURE__ */ _jsxDEV(React.StrictMode, {
  children: /* @__PURE__ */ _jsxDEV(App, {}, void 0, false, {
    fileName: _jsxFileName,
    lineNumber: 8,
    columnNumber: 5
  }, this)
}, void 0, false, {
  fileName: _jsxFileName,
  lineNumber: 7,
  columnNumber: 3
}, this));

拡張子はtsxとなっていますが、中身はJSにコンパイルされていました。このソースを見て分かるとおり、import文によって、各モジュールがインポートされています。
それでは、プロダクションビルドの場合はどうでしょうか。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
    <script type="module" crossorigin src="/assets/index.27ee3c8b.js"></script>
    <link rel="stylesheet" href="/assets/index.3fce1f81.css">
  </head>
  <body>
    <div id="root"></div>
    
  </body>
</html>

先程のように、body内のscriptタグではなく、head内のscriptタグによって、 index.27ee3c8b.jsが埋め込まれています。このJSファイルは先ほどとは異なり、従来どおりバンドルされたJSファイルです。
この理由は、importによる依存が多い場合、ブラウザはそれらを1つひとつHTTPリクエストするため、大量のリクエストが発生してしまい、パフォーマンスの問題があるためだそうです。

esbuildによる事前バンドル

先ほど、importによる依存が多い場合、大量のHTTPリクエストによるパフォーマンスの問題があることに触れました。これはローカルの開発においても起こりえます。例えば、複雑な依存関係を持つ外部ライブラリをimportする場合などです。
この問題に対応するため、Viteではesbuildによる事前バンドルが行われます。esbuildはyarn.lockにあるパッケージ単位でキャッシュされるため、一度事前バンドルがされれば、2回目以降は高速です。しかも、esbuild自体はGo言語で開発されたバイナリであるため、Node.jsベースのバンドラーよりも高速です。
yarn.lockにあるバージョンが更新されたタイミングで、キャッシュが更新される仕組みになっています。

また、ESM非対応のモジュールをESM対応させる目的もあります。例えば、Reactのindex.jsを見てみると、以下のようにCommon JSになっていることが分かります。

'use strict';

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}

ですが、先ほどのDevビルドをのソースでは、Reactがimport文でインポートされています。

import __vite__cjsImport0_react from "/node_modules/.vite/deps/react.js?v=7bc51448"; const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react;

このように、CommonJSなどのESM非対応モジュールも、esbuildによってESMに変換され、ブラウザから直接importできるようになっています。

Viteのセットアップ

ここからはViteを実際にセットアップする方法を紹介します。

Reactテンプレートでのセットアップ

yarn create vite コマンドを使うと、対話形式でプロジェクト名・テンプレートをしてViteプロジェクトを作成することが出来ます。

$ yarn create vite
yarn create v1.22.19
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Installed "create-vite@3.0.0" with binaries:
      - create-vite
      - cva
✔ Project name: … study-vite-react
✔ Select a framework: › react
✔ Select a variant: › react-ts

Scaffolding project in /study-vite/study-vite-react...

Done. Now run:

  cd study-vite-react
  yarn
  yarn dev

Done in 57.74s.

Vite 2の時のテンプレートはReact 17だったのですが、現在ではReact 18に対応しています。

viteのyarnコマンド

デフォルトではpackage.jsonで以下のように定義されています。

  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  • yarn dev
    開発サーバを起動します。デフォルトではlocalhost:5173で起動します。
  • yarn build
    プロダクション用のビルドを distディレクトリに生成します。
  • yarn preview
    distディレクトリ内のビルドをローカルでプレビューするためのサーバを起動します。

設定

Viteの設定の中で、今回調べた設定を2つ紹介します。

プロキシの設定

Viteにはnode-http-proxyが組み込まれているので、開発サーバのプロキシを設定できます。Webpackにも似たような機能はありますが、複数のターゲットを設定することができたり、ルールを柔軟に設定することが出来ます。
今回は、Github APIをリクエストするための設定を追加してみます。

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      "/api": {
        target: "https://api.github.com",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
    },
  },
});

この設定を追加すると http://localhost:5173/api/repos/octocat/hello-world/issues といったリクエストが出来るようになります。クロスオリジンとなる場合は changeOrigin: trueとしておきます。また、Githubの場合は、パスに /apiは含まれないため、 rewriteで除去します。

環境変数

CRAでは REACT_APP_というプレフィックスを付けると、アプリケーション内で環境変数を使えましたが、Viteでは  VITE_というプレフィックスを付けます。また、ソースコード内で使用するときは import.meta.env.VITE_HOGEというようにアクセスします。

TypeScriptでの補完が必要な場合は、次のように型を拡張することで対応できます。

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

interface ImportMetaEnv {
  readonly VITE_MY_VALUE: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

このファイルを src/env.d.tsに配置すれば、TypeScriptの補完を効かせることが出来ます。

静的アセット

Webpackの file-loaderと似たような機能で、画像ファイルなどの静的アセットを読み込むことが出来ます。

import reactLogo from "./assets/react.svg"; // 画像ファイル

function App() {

  return (
    <div className="App">
      <img src={reactLogo} className="logo react" alt="React logo" />
    </div>
  );
}

export default App;

import reactLogo from "./assets/react.svg"のようにimportすると、 reactLogoにファイルへの相対パスが入るため、imgタグのsrcに入れるだけで画像を表示させることが出来ます。

さいごに

開発ビルドについての速さの仕組みや様々な設定について理解することが出来ました。今後、プロダクションビルドでのビルドのスピードについても調べたいと思います。

おすすめ書籍

カイザー

シェア
執筆者:
カイザー
タグ: ReactVite

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

3週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前