RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

フロントエンド最新ビルドツールを調べてみた

はじめに

こんにちは。フロントエンドチームの岡山です。

私の担当するプロジェクトでは現在Vue2を使っており、webpack(vue-cli)を使ってビルドを行っています。

webpack自体は非常に有用なツールではありますが、あえて不満を挙げるならビルドが遅いことでしょう。

キャッシュや処理の並列化など、高速化のためにビルド設定の最適化を行ってはいますがそれでも遅いです。 小さいプロジェクトでは気にならなくても、大きくなるとともにこの問題が顕在化し、無視できなくなるかもしれません。

この記事では高速なビルドを可能にする新興勢力をいくつかご紹介します。

まずはwebpack

比較対象がないと評価しにくいので、最初にwebpack5 + ts-loaderを使います。

React + TypeScript + Material UIで作られたサンプルプロジェクトをビルドしてみます。

TypeScriptの型チェックはfork-ts-checker-webpack-pluginを使って別プロセスで行うようにします。

// webpack.config.js

const path = require('path')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')

const loaders = {
  ts: 'ts-loader'
}

const loaderOptions = {
  ts: {
    transpileOnly: true
  }
}

module.exports = (env = {}) => ({
  mode: process.env.NODE_ENV,
  entry: './src/index.tsx',
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: {
          loader: loaders[env.loader],
          options: loaderOptions[env.loader]
        },
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  plugins: [
    env.tscheck == 'yes' ? new ForkTsCheckerWebpackPlugin() : false
  ].filter(Boolean),
})
// package.json

"scripts": {
    "build:webpack-ts": "webpack --progress --env loader=ts",
    "build:webpack-ts-tscheck": "webpack --progress --env loader=ts tscheck=yes",
}

結果は以下の通りでした。 後で紹介するesbuildとswcは型チェックを行わないため、型チェックを行わない場合も計測しておきました。

型チェック time
あり ~10s
なし ~4.5s

esbuild

esbuildはGoで実装されたJavaScript/TypeScriptのビルドツールです。 トランスパイルからバンドル、ミニファイまでやってくれます。そして圧倒的な速さを誇ります。

公式ドキュメントによるとその速さはwebpackやRollupの数十倍。 これまでビルドに1分かかっていた場合、これが1秒くらいになる計算です。圧倒的です。

また、esbuildにはプラグインが利用可能なため、ビルドプロセスの様々な部分に処理を追加することが可能です。 しかし既存のプラグインはまだ充実していないため、必要に応じて自作する必要があるかもしれません。 詳しくはこちら を参照ください。

それでは実際にesbuildを使って同じプロジェクトをビルドしてみます。

$ npm i --save-dev esbuild
// build.js
const { build } = require("esbuild");

build({
  define: { "process.env.NODE_ENV": process.env.NODE_ENV },
  target: "es2015",
  platform: "browser",
  entryPoints: ["src/index.tsx"],
  outdir: "dist",
  bundle: true,
  minify: !process.env.NODE_ENV,
  sourcemap: process.env.NODE_ENV
})
.catch(() => process.exit(1))
// package.json
"scripts": {
    "build:esbuild": "set NODE_ENV=\"development\" && node ./build.js",
}

ビルドにかかった時間は~0.8sほどになりました。

速いです。数十倍とまでは行きませんでしたが、4.5秒と1秒以下は体感でも結構違います。

とまぁ速いのはわかりましたが、プロダクトへの導入には懸念があります。 公式も言っている通り、現状v1.0.0に到達していないesbuildはproduction-readyではありません。

そこでwebpackプロジェクトにも比較的気軽に導入できる、esbuild-loaderを使ってみることにします。 これはts-loaderやbabel-loaderの代替となるローダーです。webpackでのビルドでもesbuildの一部をお手軽に利用することができます。

$ npm i --save-dev esbuild-loader
// webpack.config.js
...
const loaders = {
  esbuild: 'esbuild-loader'
}

const loaderOptions = {
  esbuild: {
    loader: 'tsx',
    target: 'es2015'
  },
}
...
// package.json
"scripts": {
    "build:webpack-esbuild": "webpack --progress --env loader=esbuild",
}

ts-loaderからesbuild-loaderを使うようにするとビルド時間は~4.2sほどでした。

若干速くなった...でしょうか。おそらくesbuild-loaderの導入を検討している方のほとんどはビルド時間の高速化を目的としていると思います。一概には言えませんが、esbuild-loaderの導入だけでは大きな改善は難しいかもしれません。

swc

swcNext.js v11にも組み込まれたJavaScript/TypeScriptトランスパイラです。

こちらはRustで実装されています。swc単体ではバンドラーとしての機能は持っておらず、バンドルするにはspack等を利用する必要があります。

esbuildと同じようにswcにもwebpackで使用可能なローダーswc-loaderがあったのでこちらを使ってみました。

$ npm i --save-dev @swc/core swc-loader
// webpack.config.js
...
const loaders = {
  swc: 'swc-loader'
}

const loaderOptions = {
  swc: {
    sync: true,
    jsc: {
      parser: {
        syntax: "typescript",
        tsx : true
      }
    }
  }
}
...
// package.json
"scripts": {
    "build:webpack-swc": "webpack --progress --env loader=swc",
}

ビルドにかかった時間は~4.1sほどでした。esbuild-loaderと同じような感じですね。

Snowpack

SnowpackもwebpackやParcelなどのバンドラーの代替手段として登場しました。 webpackなどの従来のビルドツールは1つのファイルを保存するたびに、アプリケーション全体を再構築してバンドルする必要がありました。

ここでSnowpackは(開発時には)もはやバンドルをしないというアプローチをとりました。

JavaScriptのESMを活用することで開発中はバンドルせずに各ファイルを都度読み込みます。これにより、大規模なプロジェクトでも高速に開発サーバを起動することが可能になります。

各ファイルは一度だけビルドされ、キャッシュされます。ファイルが変更されるとそのファイルのみビルドするため、差分更新も一瞬で完了します。

また、Snowpackには先ほどご紹介したesbuildが組み込まれているため、本番ビルド時にesbuildを使ってバンドルすることができます。 ただし、esbuildは先述の通り成熟していないという理由でwebpackプラグインを使用することが推奨されています(Rollupプラグインもあります)。

開発者としては頻繁に行われる開発時のビルド時間が短縮されると特にうれしいため、開発者体験の向上とプロダクトへの導入しやすさの両方を実現していると言えます。

$ npm install --save-dev snowpack @snowpack/plugin-webpack
// snowpack.config.js

module.exports = {
    mount: {
      public: { url: '/', static: true },
      src: '/dist'
    },
    plugins: [
       [
          '@snowpack/plugin-webpack',
       ]
    ]
}
// package.json

"scripts": {
    "start": "snowpack dev",
    "build": "snowpack build"
}

実際にビルドしてみると、開発サーバーが~1sほどで起動します。噂通りの速さですね。差分更新なんかは一瞬です。

Vite

ViteもSnowpackと非常に似た目的を持ったNo-bundleツールです。 依存関係の事前バンドルにはesbuildを、本番ビルドではRollupを使用します。

$ npm install --save-dev vite @vitejs/plugin-react-refresh
// vite.config.ts

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

export default defineConfig({
  root: './',
  plugins: [reactRefresh()],
  resolve: {
    alias: {
      '@/': path.join(__dirname, './src/'),
    },
  },
})
// package.json

"scripts": {
    "start": "vite",
    "build": "vite build"
}

Snowpackと同様、開発サーバーが~1sほどで起動しました。

まとめ

esbuildはトランスパイルもバンドルも高速に動作します。この速さに慣れたらwebpackには戻りたくなくなります。 が、様々なプラグインを使ったある程度の規模のwebpackプロジェクトをesbuildに置き換えするのは現実的に難しい点もあるでしょう。 swcも高速ではありますが、プロダクトで採用するにはspackが発展途上過ぎるなという印象を感じました。ただ、Next.jsやDenoでは内部で使われているため、開発者は意識しなくてもその恩恵を受けられるようになっています。

SnowpackとViteは高速な開発ビルドによる開発者体験の向上と、安定したwebpackやRollupによる本番ビルドを兼ね備えたツールです。 私が所属するチームではVue2を使っているので、Vue3に上げるタイミングに合わせてViteの導入もできればなーと思っています。

ここでは紹介しきれなかったものもあるので、皆さんもぜひ様々なビルドツールを調べてお試しになってみてはいかがでしょうか。


◆TECH PLAY
techplay.jp

◆connpass
rakus.connpass.com

Copyright © RAKUS Co., Ltd. All rights reserved.