webpackとesbuild-loaderで爆速なVue開発環境を構築しよう
はじめに
フロントエンドのライブラリ・フレームワークの移り変わりって激しいですよね。
webpack5
がリリースされてもう半年と少しくらい経ってしまいました。
最近では新たなフロントエンドのビルドツールに、esbuild や vite などのビルドツールもますます盛り上がって来ています。
そういう中、webpack
が今後どうなっていくのか少し不安というのがあります。今回は、esbuild
から派生した、esbuild-loader を利用することで、webpack
でも爆速な Vue
のビルド環境が構築できるよということを書きますので、そんな不安を少しでも取り除ければいいなと思います。
また、今回は vite
を利用しなくても、esbuild-loader
と webpack
だけでも快適なビルド環境を構築できるということを主眼に置いています。
記事の対象
Node.js
についてある程度理解している、npm
などの環境構築が問題なくできる, webpack
の環境構築やなにかしらフロントエンドの環境構築をしたことがある方を対象としています。
準備
以下、必要なモジュールなどをインストールしていきます。
npm i -D typescript ts-node @types/node webpack-cli webpack @types/webpack
これで webpack
のコンフィグファイルが TypeScript
で記述されてても起動できるようになりました。
以下で、必要な loader
などをインストールしていきます。
npm i -D esbuild-loader fork-ts-checker-webpack-plugin html-webpack-plugin postcss-loader sass-loader css-loader thread-loader vue-loader vue-style-loader node-sass autoprefixer cssnano
今回は以下の package.json
の dependencies
と devDependencies
に記載されているnpmモジュールを利用します。(2021/06/11時点)
{
"dependencies": {
"vue": "^2.6.14"
},
"devDependencies": {
"@types/webpack": "^5.28.0",
"autoprefixer": "^10.2.6",
"css-loader": "^5.2.6",
"cssnano": "^3.10.0",
"esbuild-loader": "^2.13.1",
"fork-ts-checker-webpack-plugin": "^6.2.10",
"html-webpack-plugin": "^5.3.1",
"node-sass": "^6.0.0",
"path": "^0.12.7",
"postcss": "^8.3.2",
"sass-loader": "^12.1.0",
"thread-loader": "^3.0.4",
"ts-node": "^10.0.0",
"typescript": "^4.3.2",
"vue-loader": "^15.9.7",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.6.14",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.2"
}
}
今回メインとなっているnpmモジュールは、vue-loader, fork-ts-checker-webpack-plugin, esbuild-loader, thlead-loader なのでそれ以外はそこまで重要ではないです。
利用するnpmモジュールについて
esbuild-loader
先ほども説明しましたが、esbuild
が提供しているAPIを利用して作成された、loader
です。トランスフォーム処理をフックして、フックした際に取得したコードのトランスパイルに esbuild
が利用されているので、よく TypeScript
のトランスパイルに利用される ts-loader
よりは確実に早いです。また、esbuild
のビルド速度については esbuild
のドキュメントに記載されています。
fork-ts-checker-webpack-plugin
webpack
を利用する際に、TypeScript
のトランスパイルによく利用されている、ts-loader
の型チェックの部分のみを切り出したプラグインです。このプラグインを利用することで、
esbuild
, esbuild-loader
の弱点である、型チェックが行えないという問題を解決することができます。
thread-loader
基本的に、webpack
を利用する際には、ファイルの拡張子ごとに loader
を分けるなど、細かい設定をすることが多々あります。その際に、この thread-loader
を使うことによって、各々の拡張子に対応する loader ごとにスレッドを分割し、マルチスレッドで処理を行うことできます。
thread-loader
に関しては、webpack
の公式ドキュメントでも紹介されています。
vue-loader
今回は、vue-loader の threadMode
機能 と loaders
機能を利用して、快適なビルド環境を実現します。その他の設定も利用しますが、大筋ではないので、無視していただいても構いません。
webpackのバンドル設定
今回は、範囲を限定して、vue2
で、js
もしくは、ts
を利用する場合のバンドル設定を記載します。おそらく見立てでは、vue-next
にも応用可能なので、ぜひお試しください。
webpack.config.ts
の内容を以下に記載します。
わかりづらい箇所には適宜コメントをしています。
const webpack = require('webpack');
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
const cssNanoPlugin = require('cssnano');
const autoPrefixer = require('autoprefixer');
const forkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const threadLoader = require('thread-loader');
const vueLoaderPlugin = require('vue-loader/lib/plugin')
// thread-loader用の設定オプション
const vueWorkerPoolOptions = {
workers: 1,
workerParallelJobs: 50,
workerNodeArgs: ['--max-old-space-size=1024'],
poolTimeout: 1000,
name: 'vue-loader-pool'
}
const cssWorkerPoolOptions = {
workers: 1,
workerParallelJobs: 50,
workerNodeArgs: ['--max-old-space-size=1024'],
poolTimeout: 1000,
name: 'css-loader-pool'
}
// thread-loaderのpre-warmupを行うことで実行時の遅延を防ぐ
threadLoader.warmup(vueWorkerPoolOptions, [
'vue-loader',
'vue-style-loader'
]);
threadLoader.warmup(cssWorkerPoolOptions, [
'css-loader',
'sass-loader'
]);
const IS_DEV = process.env.NODE_ENV === 'development';
const postCssPlugins = !IS_DEV
? [
cssNanoPlugin({ preset: 'default' }),
autoPrefixer({ grid: true })
]
: [autoPrefixer({ grid: true })]
module.exports = () => {
const configs = {
// ForkTsCheckerWebpackPluginが自動的にtsconfig.jsonを見つけるために必要
context: __dirname,
mode: process.env.NODE_ENV,
devtool: '',
entry: path.resolve(__dirname, 'src/index.ts'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
vue: 'vue/dist/vue.js',
},
extensions: ['.vue', '.js', '.ts', '.tsx'],
},
module: {
rules: [
{
test: /\.vue$/,
use: [
{
loader: 'thread-loader',
options: vueWorkerPoolOptions,
},
// vue-loaderによってvueファイルに定義されているjs, ts, sassなどが解釈される。
// その後、それぞれのトランスフォーム処理が各ファイルによって定義したloaderに渡されます。
// 事前にvue用の定義を各ファイルに設定することも可能です。
{
loader: 'vue-loader',
options: {
postcss: { plugins: postCssPlugins },
loaders: {
sass: [
{ loader: 'vue-style-loader' },
{ loader: 'css-loader' },
{
loader: 'sass-loader',
options: {
sassOptions: { indentedSyntax: true }
},
},
],
},
// vue-loader上の各loaderの処理にもthread-loaderを適用するための設定。
threadMode: true,
},
},
],
},
{
test: /\.(css|scss|sass)$/,
use: [
{
loader: 'thread-loader',
options: cssWorkerPoolOptions,
},
{ loader: 'css-loader' },
{
loader: 'sass-loader',
options: {
sassOptions: { indentedSyntax: true }
},
},
],
},
{
test: /\.js$/,
// ここでvue関連のファイルをexcludeしないとエラーになります。
// ts-loaderのappendToSuffixオプションとほぼ同じ理由です。
exclude: /node_modules|\vue\/dist|\vue-loader/,
loader: 'esbuild-loader',
options: {
loader: 'js',
target: 'esnext',
},
},
{
test: /\.ts$/,
// ここでvue関連のファイルをexcludeしないとエラーになります。
// ts-loaderのappendToSuffixオプションとほぼ同じ理由です。
exclude: /node_modules|\vue\/dist|\vue-loader/,
loader: 'esbuild-loader',
options: {
loader: 'ts',
target: 'esnext',
},
},
{
test: /\.tsx$/,
// ここでvue関連をexcludeしないとエラーになる。
// ts-loaderのappendToSuffixオプションとほぼ同じ理由。
exclude: /node_modules|\vue\/dist|\vue-loader/,
loader: 'esbuild-loader',
options: {
// loaderはstring型なのでtsとtsxファイルの設定を別々に定義する必要があります。
loader: 'tsx',
target: 'esnext',
},
},
],
},
plugins: [
new vueLoaderPlugin(),
new forkTsCheckerWebpackPlugin(),
new htmlWebpackPlugin({
filename: 'index.html',
template: 'src/html/index.html',
}),
],
// 圧縮設定
optimization: {
minimize: !IS_DEV
},
}
// webpack5以降からはdevtoolsを個別に後から設定する必要があります。
if (IS_DEV) {
configs.devtool = 'eval-source-map';
}
return configs;
}
npmスクリプトも以下のように設定することによって、便利になりそうですね。
{
"scripts": {
"watch": "NODE_ENV=\"development\" webpack --progress --mode development --watch --hot",
"dev": "NODE_ENV=\"development\" && webpack --progress --mode development",
"prod": "NODE_ENV=\"production\" && webpack --progress --mode production"
},
}
注意点
-
esbuild-loader
はes2015
以降のトランスパイルにしか対応していません。 -
IE11
などの対応は、esbuild-loader
では行えないので、babel-loader
を別途かませる必要があります。
おわりに
なにか間違いや、質問などありましたらコメントにお願いします。
Discussion
esbuild
とwebpack
の組み合わせの知見ありがとうございます!大変参考になりました!本筋とはやや異なる質問になりますが、気になったのでコメント致します。
記事の環境では
vue-loader
は^15.9.7
を使用しているようですが、15系は14系までとは異なり、SFC内で検出された<style lang="scss">
などのブロックを、scss
ファイルを import しているかのように振る舞うよう、仕様が変わった認識です。 (参考)そのため、15系の
vue-loader
のoptions
にはloaders
は存在せず、そちらに対してthread-loader
を適用する必要はないと思うんですが如何でしょうか?(
vue-loader
周りの私の認識もかなりあやふやなので、間違いがあればご指摘いただけると助かります)ご質問に対する、曖昧な回答は持ち合わせているのですが、回答の内容がvue-loaderの詳細な挙動を確認しないとなんとも言えないところですので、少々返信お待ちください。
すぐに回答できそうでした。
いただいた、参考資料に廃止はされていないけれど、今後廃止になるかもねと書かれていますね。
わたしの意図としては、vueに対しては、vue-style-loaderが必要なこともあって 、loaderの設定を純粋に外部からimportするcss/sass/scssと、vueファイル内部のcss/sass/scssと分けたくて、このような定義にしていました。
また、vue-loader14/15のどちらでもこの定義方法であれば、そのまま利用できるかなと思いますので、こちらで大丈夫かと思います!
丁寧なご回答ありがとうございます!
なるほど。
vue-loader
15系の仕様としては、loaders
で指定されている場合は内部、されてない場合は外部の依存と扱われ、SFC内の依存とそれ以外でビルド戦略を分けるときはあえてこうしたりもするんですね。参考になりました!topics名が
esbuid
になっていますね…😣ありがとうございます😭
修正しておきます