WebpackとViteをプロジェクト導入して比較した話

みなさんこんにちは。suginokoです。
寒くなってきたので布団を着て仕事をしています。（[弊社](https://brewus.co.jp/)はフルリモートで仕事ができます。暖かくして仕事しましょう。）

## 前置き
今日は弊社Web開発における開発環境についての記事書いてみようかなと思います。
弊社の開発環境構築に普段はWebpackを使って環境構築しているのですが、

* Webpackで別に開発する必要なくない？もっと簡単に導入できそうなのあるのではないかな
* 開発環境の立ち上がりが遅すぎてムカつく。もっとはやく動かないもんかね

というのが根底にあり、調べてみることにしました。

[Vite](https://vitejs.dev/)というのは、私自身が前から[esbuild](https://github.com/evanw/esbuild)に注目していて、これWebpackの代わりになるのではないかなと思っていたところで、Viteが出てきた（詳しい話は後述します。中でesbuild使っている）ので比較してみようかなと思った次第です。
（ようやくテストではなくて実際のプロジェクトに導入できてうれしい。

速いってことはわかったけど、どれくらい速くなるとか、開発環境構築楽になるとかわからなかったので、比較して使いたいなと思った次第です。


## 普段のWebpackでの開発環境構築
プロジェクトでは普段Reactを使っているのでそれに合った環境構築をWebpackで行います。
そこにTypeScriptを入れる入れない、Redux使うなり他のモジュール入れるなりそれは開発環境作る人で様々ですが、おおよそは

* style系のloader、プラグイン
* devServer
* alias入れるとか入れないとか
* TypeScriptを入れるならts-loader入れるとか
* vendor.js分けるとか

まあ細かいところはもう少しありますが、大体上記の導入して整えていきます。
これらをViteでも同じように行って比較していきます。

## Viteでの環境構築
（TypeScript入れるほうの構築めっちゃ時間かかった・・・）
Webpackと同じ構成にするので、React + TypeScript + Sassくらいか。Redux（toolkit）もいれちゃったかもしれないけど、だいたいこんな感じで構成

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

const src = path.resolve(__dirname, 'src')
// const dist = path.resolve(__dirname, 'dist')


const ALIAS = {...}

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
  console.log('** mode **', mode)
  return {
    base: './',
    server: {
      port: 11080,
      open: true
    },
    plugins: [react()],
    publicDir: 'public', // 明示的に
    build: {
      sourcemap: mode == 'develop' ? true : false,
      minify: mode == 'production' ? 'terser' : false, // dev stg pro
      // outDir: dist,
      outDir: './dist',
    },
    // root: './src',
    difine: {
      global: 'window', // global指定しないと取得不可
    },
    resolve: {
      alias: ALIAS
    },
  }
})

```

Loaderとか書かなくていい。これが最高すぎる。
そもそもViteの場合、[取説](https://vitejs.dev/guide/#scaffolding-your-first-vite-project)にあるように、


```
# npm 6.x
npm init vite@latest my-vue-app --template react

# ts
npm init vite@latest my-vue-app --template react-ts

# npm 7+, extra double-dash is needed:
npm init vite@latest my-vue-app -- --template react

# ts
npm init vite@latest my-vue-app -- --template react-ts

# yarn
yarn create vite my-vue-app --template vue
```

するだけでReact（+TypeScript）の環境構築ができます。最高。

## 一応Webpack v5の構成
```Javascript
const webpack = require('webpack')
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// webpack5ではデフォルトでTerserPlugin入ったらしいがまだ必要らしい
const TerserPlugin = require('terser-webpack-plugin')

// OptimizeCSSAssetsPluginが消えたのでcss-minimizer-webpack-pluginに切り替える
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");


const mode = process.env.NODE_ENV || 'production'
const isDevserver = process.env.IS_DEVSERVER == 1
const port = process.env.PORT || '1111'
const src = path.resolve(__dirname, 'src')
const dist = path.resolve(__dirname, 'dist')

console.log('** mode **', mode)

module.exports = {
  // mode 必須
  mode: mode === 'production' ? mode : 'development',

  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    }
  },


  devtool: mode === 'production'
    ? false
    : 'eval-cheap-module-source-map'
    // : 'eval-cheap-module-source-map'
  ,

  entry: isDevserver
    ? [
        'react-hot-loader/patch',
        `webpack-dev-server/client?http://localhost:${ port }`,
        'webpack/hot/only-dev-server',
        `${ src }/main.dev.tsx`,
      ]
    : {
        'bundle': `${ src }/main.tsx`
      },
  
  output: {
    path: dist,
    filename: isDevserver ? '[name].js' : '[name].js?[hash]',
    // filename: isDevserver ? 'bundle.js' : '[name].js?[hash]', // bundle.jsにするとエラーになるv4ではいけたが。
    publicPath: '/',
    clean: true
  },

  module: {
    rules: [
      {
        test: /\.ts(x?)$/,
        exclude: [/node_modules/],
        use: {
          loader: 'ts-loader'
        }
      },
      {
        test: /\.scss|.css$/,
        use: [
          isDevserver ? 'style-loader' : MiniCssExtractPlugin.loader,
          { loader: 'css-loader', options: { url: false, sourceMap: true } },
          { loader: 'postcss-loader', options: { sourceMap: true } },
          { loader: 'sass-loader', options: { sourceMap: true } }
        ]
      }
    ]
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: `${ src }/index.html`,
      inject: 'body',
      filename: `index.html`
    }),
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
    new MiniCssExtractPlugin({ filename: '[name].css?[contenthash]' })
  ],

  optimization: {
    splitChunks: {
      name: 'vendor',
      chunks: 'all',
    },
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
          },
          output: {
            comments: false,
          }
        }
      })
    ]
  },

  devServer: {
    static: {
      directory: dist,
    },

    historyApiFallback: true,
    host: 'localhost',
    compress: true,
    port: port,
    hot: true,
    open: true
  },

  resolve: {
    alias: {...},
    extensions: ['.ts', '.tsx', '.js'],
  },

  performance: {
    hints: false
  },

  // ES5(IE11等)向けの指定（webpack 5以上で必要）
  target: isDevserver ? ["web"] : ["web", "es5"],
}
```
。。。長いな～～
この構成するだけでViteの倍くらい体感かかったかもです。

## 実験のディレクトリ構成（それぞれおおよそ同じ感じで）
```
node_modules
package.json
postcs![image (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696960/b29f9ca4-48ba-92e7-03f3-e42c32f847f3.png)
s.config.js
tsconfig.json
webpack.config.js(Viteではvite.config.js)
(+viteではenvファイルとか)
src
┗components(ここでは共通してApp.tsxのみ)
┗index.html
┗main.tsx
```

## 開発環境の起動について時間を比較してみる
とりあえずは現時点でのプロジェクトに使っているWebpackでの開発環境ではどれくらいで起動するのか計ってみました。

![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696960/e18e0643-7fee-7de1-183e-90804707abc3.png)
![image (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696960/998862cc-5186-04b4-3c65-581f7bec7f0a.png)

まあまあ大きい案件だからなのか、Webpack v4 だからなのかわからないけど、13954msやら20871msは遅すぎやしませんかね。
既に動いているプロジェクトだとあんまり意味がなかったので、実験的にViteと同じ構成を作ってWebpack v5も試してみます。（React + TypeScript + Sass）

とりあえず、テスト的にWebpack v5の開発環境（React+TypeScript+scssくらいでいいかな）作って、現状のほぼまっさらなViteと同じような構成で開発環境を起動してみましょう。

### Webpack v5
![image (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696960/b77c8681-1198-eaf1-e676-4a7f2bf9c8be.png)
6226ms

### Vite
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696960/470728e9-11ca-d0ae-972a-b50f39252f79.png)

1370ms

Webpack v5もだいぶ速くなったけど、Vite優勢ですね！(なんならViteのほうの構成のほうが少し多いのにこの速度。すばら。

## ビルド時の時間の比較

### Webpack v5
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696960/2ab12d5b-9fbe-fb8c-05d4-adde24ccf8dd.png)
12.44s

### Vite
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696960/11ac6825-55ca-c6e8-4626-cf5016afc5bc.png)
8.9s

少しViteの方がはやそう

React（+TypeScript）の案件ならViteでもいいかもしれないですね。

## Viteは何故速いのか
Webpackはbuildするときにモジュール全体をクロールしてビルドする必要があるので、全部の依存関係が解消されないとビルドされない。
アプリケーションが大きくなるほど遅くなるということでしょう。
（だから大きめの案件だと開発環境の起動が遅かったのですね）

[公式](https://vitejs.dev/guide/why.html#slow-server-start)にもあるのですが、Viteはこの問題を解消するために依存関係とソースコードのカテゴリで分割することで開発サーバーの起動時間を解消したようです。
モジュールとかは頻繁に変更されることはないので、事前バンドルしておいてページの読み込みを解消したらしいです。（これをキャッシュして使うっぽい。node_modules/.viteにキャッシュするらしい）その事前バンドルをGo製のesbuildを使っているとのこと。（これが爆速）
ソースコードは[ネイティブのESM](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)を使ってブラウザの要求に応じてバンドルせずそのまま提供しているようです。

本番buildではrollupを使っているそうです。（バージョン違うとbuild時にrollupのエラーも出たんで、ちゃんと最新バージョン使ったほうがいです）

…rollupはお試しなのかしら。esbuildを本番buildで使いたいけど、特定のコード分割とCSSの取り扱いの関係でまだ使ってないそうなので、将来的には本番buildもesbuildを使うのかもしれないです。

詳しいことは日本語ドキュメントもあるのでみてみてください。
[Vite](https://ja.vitejs.dev/guide/)

## TypeScript使うときだけやや面倒くさい
じゃあもうWebpackではなくてViteでいいじゃん！って思うんですけど、ややTypeScriptの扱いが面倒で、
TypeScriptはサポートされているんですけど、型チェックをしてくれません。

なので自分は２窓使って監視するようにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696960/b3191004-cb2d-c8cf-2b81-f21c8595bcd4.png)

```
tsc --watch
```
で一応型でエラーしたらわかるので、見つつ対応しています。
また、ブラウザで型チェックしてくれる[vite-plugin-checker](https://github.com/fi3ework/vite-plugin-checker)も入れてみました。
一応ブラウザでもエラー出るようになってくれたのですが、tsc --watchでどうもエラーが出る箇所が違うみたいで、微妙な感じです。

無いよりはいいかな・・・という感じ。

まあでもTypeScript入れないなら圧倒的にViteのほうがよさげに見えました。
（CSSのbuild設定もこれでは足りないっぽくて実験しながらまだいじくってます。ぶつぶつ）

## 最後に
年末最後に投稿できてよかったです。
しばらくVite使ってみようかなと思います。