先上实验结果
构建框架 | 耗时 |
---|---|
vue cli 4 (babel-loader & TerserPlugin) | 7min |
vue cli 5 (babel-loader & TerserPlugin) | 4min+ |
vue cli 5 (esbuild-loader & EsbuildPlugin) | 2min+ |
vue cli 5 (swc-loader & EsbuildPlugin) | 2min+ |
esbuild-loader 是什么
原文搬运:www.npmjs.com/package/esb...
Speed up your Webpack build with esbuild! 🔥
esbuild is a JavaScript bundler written in Go that supports blazing fast ESNext & TypeScript transpilation and JS minification.
总的来说有这样一些功能:
- 替换 babel-loader
- 替换 ts-loader
- 替换 Terser or UglifyJs
- 替换 DefinePlugin
- 支持压缩 CSS
Vite 就是基于 esbuild 的。
swc-loader 是什么
SWC - Rust-based platform for the Web.
swc-loader allows you to use SWC with webpack.
Rspack 使用 builtin:swc-loader 对 TypeScript、JSX 以及最新的 JavaScript 语法进行转换。
Turbopack 原生使用 SWC 作为编译器。
都说用 GO 写的 esbuild 和用 Rust 写的 swc 比用 JavaScript 写的 babel 快很多,接下来验证下。
先用小项目验证
n年前的一个小项目,使用的是 webpack 3 自建脚手架。
将 webpack 3 切换到 vue cli 5 并使用 esbuild-loader,构建时长从2分钟优化到27秒。
- webpack 3:
2分钟
- vue cli 5:
1分钟
- vue cli 5 & esbuild-loader:
27秒
需要把转换 js 的 babel-loader 删掉,改为用 esbuild-loader。TerserPlugin 也删掉,使用 EsbuildPlugin 压缩代码。本项目不需要兼容一些低版本浏览器,支持 Chrome 90+ 就行,不需要完全编译为 ES5,可以减少构建耗时。
lintOnSave 记得设置为 false,生产构建时跑 eslint 也是很耗时的。
两种配置都可以:
vue.config.js
js
{
lintOnSave: process.env.NODE_ENV !== 'production',
configureWebpack: {
optimization: {
minimizer: [
new EsbuildPlugin({
target: 'chrome90',
css: true
})
]
}
},
chainWebpack: config => {
const rule = config.module.rule('js')
rule.uses.clear()
rule.use('esbuild-loader').loader('esbuild-loader').options({
loader: 'js',
target: 'chrome90'
})
config.optimization.minimizers.delete('terser')
},
}
用 vue inspect > output.js
查看 minimizer 的配置。
js
{
...
optimization: {
...
minimizer: [
{
options: {
target: 'chrome90',
css: true
}
}
]
},
...
}
更推荐第二种配置方式:
vue.config.js
js
const { EsbuildPlugin } = require('esbuild-loader')
{
lintOnSave: process.env.NODE_ENV !== 'production',
chainWebpack: config => {
const rule = config.module.rule('js')
rule.uses.clear()
rule.use('esbuild-loader').loader('esbuild-loader').options({
loader: 'js',
target: 'chrome90'
})
config.optimization.minimizers.delete('terser')
config.optimization
.minimizer('esbuild')
.use(EsbuildPlugin, [{ target: 'chrome90', css: true }])
},
}
js
{
...
optimization: {
...
minimizer: [
/* config.optimization.minimizer('esbuild') */
new EsbuildPlugin(
{
target: 'chrome90',
css: true
}
)
]
},
...
}
配置就两点:用 esbuild-loader 替换 babel-loader;去掉 terser,换成 EsbuildPlugin。
构建效果如前所示:
- webpack 3:
2分钟
- vue cli 5:
1分钟
- vue cli 5 & esbuild-loader:
27秒
优化效果还是很明显的。
应用到更大更复杂的项目
这个项目是用 vue cli 4 创建的,直接使用 esbuild-loader 遇到很多坑,没能解决。
考虑到前一个轻量级项目的成功经验,遂先升级到 vue cli 5,当然 vue cli 4 升级 vue cli 5 的过程依然踩了一些坑,不过都不难处理。
先看看升级前项目使用 vue cli 4 的打包情况:
File | Size | Gzipped |
---|---|---|
dist/js/chunk-vendors.c3af1a2b.js | 4535.40 KiB | 1210.86 KiB |
dist/ts.worker.js | 3560.75 KiB | 806.96 KiB |
dist/js/chunk-4bce028b.5428fd26.js | 3144.10 KiB | 968.15 KiB |
dist/js/chunk-b4acb204.5c187046.js | 2170.96 KiB | 642.01 KiB |
dist/js/chunk-6a6dc5a4.b6240583.js | 2165.08 KiB | 644.84 KiB |
dist/js/chunk-30ef5c8c.3ed9597e.js | 2007.50 KiB | 597.89 KiB |
Time: 7 minutes
耗时 7 分钟左右,打包后的大文件又大又多。
再看看升级 vue cli 5 后的打包情况:
File | Size | Gzipped |
---|---|---|
dist/js/chunk-vendors.80fdd77f.js | 4436.97 KiB | 1158.05 KiB |
dist/ts.worker.js | 3556.88 KiB | 805.04 KiB |
dist/js/81.43d383ad.js | 1332.49 KiB | 421.39 KiB |
dist/js/chunk-common.f1e7960d.js | 1245.42 KiB | 352.42 KiB |
Time: 256044ms
可以看到大文件明显变少了,耗时也缩短到了4分多钟。
chunk-vendors 还是很大的,有必要做分包处理。
使用 splitChunks 优化大文件
vue ui 打开 Analyzer 分析包文件,看看 chunk-vendors 里哪些包可以分出来。

用 vue inspect > output.js
查看 splitChunks 的默认配置。
js
{
optimization: {
realContentHash: false,
splitChunks: {
cacheGroups: {
defaultVendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
},
minimizer: [
/* config.optimization.minimizer('terser') */
new TerserPlugin(
{
...
}
)
]
},
}
将 monaco-editor
、element-ui
、@antv/**
这几个比较大的包分拆。
vue.config.js
js
{
chainWebpack: config => {
config.optimization.splitChunks({
cacheGroups: {
monaco: {
name: 'chunk-monaco',
test: /[\\/]node_modules[\\/]monaco-editor[\\/]/,
priority: 1,
chunks: 'initial'
},
element: {
name: 'chunk-element',
test: /[\\/]node_modules[\\/]element-ui[\\/]/,
priority: 1,
chunks: 'initial'
},
antv: {
name: 'chunk-antv',
test: /[\\/]node_modules[\\/]@antv[\\/]/,
priority: 1,
chunks: 'initial'
},
defaultVendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
})
},
}
chunks:
- all: 不管文件是动态还是非动态载入,统一将文件分离。当页面首次载入会引入所有的包
- async: 将异步加载的文件分离,首次一般不引入,到需要异步引入的组件才会引入。
- initial:将异步和非异步的文件分离,如果一个文件被异步引入也被非异步引入,那它会被打包两次(注意和all区别),用于分离页面首次需要加载的包。
再来看看分拆后的打包效果:
File | Size | Gzipped |
---|---|---|
dist/ts.worker.js | 3556.88 KiB | 805.04 KiB |
dist/js/chunk-monaco.a6d2fb5c.js | 2269.46 KiB | 540.92 KiB |
dist/js/81.43d383ad.js | 1332.49 KiB | 421.39 KiB |
dist/js/chunk-common.3fe65876.js | 1245.42 KiB | 352.42 KiB |
dist/js/chunk-vendors.bd8a1fb8.js | 975.05 KiB | 306.00 KiB |
dist/js/chunk-element.01e0ff3f.js | 751.09 KiB | 190.42 KiB |
dist/js/2467.0bc420b8.js | 631.27 KiB | 196.59 KiB |
dist/js/363.25f4118e.js | 511.77 KiB | 138.14 KiB |
dist/js/5517.4ad6dc76.js | 468.67 KiB | 136.35 KiB |
dist/js/chunk-antv.a6d76427.js | 437.98 KiB | 121.40 KiB |
Time: 240821ms
效果还是比较明显的,原来 4436KB 的 chunk-vendors 现在被拆分成了几个小一点的包,耗时都差不多,4分钟。
使用 esbuild-loader 进一步优化
首先安装依赖。
npm i -D esbuild-loader
配置:
vue.config.js
js
const { EsbuildPlugin } = require('esbuild-loader')
...
{
chainWebpack: config => {
const rule = config.module.rule('js')
rule.uses.clear()
rule.use('esbuild-loader').loader('esbuild-loader').options({
loader: 'jsx',
target: 'chrome80'
})
config.optimization.minimizers.delete('terser')
config.optimization
.minimizer('esbuild')
.use(EsbuildPlugin, [{ target: 'chrome80', css: true }])
config.optimization.splitChunks({
cacheGroups: {
monaco: {
name: 'chunk-monaco',
test: /[\\/]node_modules[\\/]monaco-editor[\\/]/,
priority: 1,
chunks: 'initial'
},
element: {
name: 'chunk-element',
test: /[\\/]node_modules[\\/]element-ui[\\/]/,
priority: 1,
chunks: 'initial'
},
antv: {
name: 'chunk-antv',
test: /[\\/]node_modules[\\/]@antv[\\/]/,
priority: 1,
chunks: 'initial'
},
defaultVendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
})
},
}
此时用 vue inspect > output.js
看看新的 Webpack 配置。
js
{
optimization: {
realContentHash: false,
splitChunks: {
cacheGroups: {
monaco: {
name: 'chunk-monaco',
test: /[\\/]node_modules[\\/]monaco-editor[\\/]/,
priority: 1,
chunks: 'initial'
},
element: {
name: 'chunk-element',
test: /[\\/]node_modules[\\/]element-ui[\\/]/,
priority: 1,
chunks: 'initial'
},
antv: {
name: 'chunk-antv',
test: /[\\/]node_modules[\\/]@antv[\\/]/,
priority: 1,
chunks: 'initial'
},
defaultVendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
},
minimizer: [
/* config.optimization.minimizer('esbuild') */
new EsbuildPlugin(
{
target: 'chrome80',
css: true
}
)
]
},
...
/* config.module.rule('js') */
{
test: /\.m?jsx?$/,
exclude: [
function () { /* omitted long function */ }
],
use: [
/* config.module.rule('js').use('esbuild-loader') */
{
loader: 'esbuild-loader',
options: {
loader: 'jsx',
target: 'chrome80'
}
}
]
},
}
使用 esbuild-loader 的打包情况:
File | Size | Gzipped |
---|---|---|
dist/ts.worker.js | 3614.37 KiB | 841.85 KiB |
dist/js/chunk-monaco.d6e97f94.js | 2323.88 KiB | 580.06 KiB |
dist/js/3248.10a0d27c.js | 1386.29 KiB | 435.81 KiB |
dist/js/chunk-common.a7f44eae.js | 1286.91 KiB | 358.54 KiB |
dist/js/chunk-vendors.95f02ea3.js | 1081.33 KiB | 328.69 KiB |
dist/js/chunk-element.6a0335ac.js | 755.60 KiB | 198.92 KiB |
dist/js/4634.497be067.js | 647.22 KiB | 192.28 KiB |
dist/js/6121.228f6aad.js | 632.93 KiB | 199.96 KiB |
dist/js/7959.7bc34ce1.js | 521.49 KiB | 142.09 KiB |
dist/js/chunk-antv.4f544fdc.js | 440.22 KiB | 127.06 KiB |
Time: 133415ms
构建包大小差别不大,构建耗时从4分钟优化到了2分钟多一点。
使用 swc-loader
使用 swc-loader 替代 babel-loader,因 swc-loader 的 minify 尝试无效果,依然使用 esbuild-loader 的 EsbuildPlugin 来替代 TerserPlugin。
安装依赖:
npm i -D @swc/core swc-loader
修改 vue.config.js
js
{
chainWebpack: config => {
const rule = config.module.rule('js')
rule.uses.clear()
rule.use('swc-loader').loader('swc-loader')
config.optimization.minimizers.delete('terser')
config.optimization
.minimizer('esbuild')
.use(EsbuildPlugin, [{ target: 'chrome80', css: true }])
...
},
}
额外添加配置文件 .swcrc
js
{
"jsc": {
"parser": {
"syntax": "ecmascript",
"jsx": true,
"dynamicImport": true
},
"minify": {
"compress": true,
"mangle": true
}
},
"env": {
"targets": {
"chrome": "80"
},
"corejs": "3"
},
"minify": true
}
使用 swc-loader 的打包情况:
File | Size | Gzipped |
---|---|---|
dist/ts.worker.js | 3614.21 KiB | 841.85 KiB |
dist/js/chunk-monaco.d6fd694c.js | 2309.22 KiB | 579.10 KiB |
dist/js/5391.e7a92584.js | 1372.97 KiB | 432.64 KiB |
dist/js/chunk-common.85c44fdd.js | 1283.95 KiB | 357.94 KiB |
dist/js/chunk-vendors.cf9dba32.js | 1075.11 KiB | 327.63 KiB |
dist/js/chunk-element.615e6aba.js | 755.34 KiB | 198.92 KiB |
dist/js/4326.7a6b4053.js | 645.09 KiB | 192.02 KiB |
dist/js/4914.7598439b.js | 633.89 KiB | 200.15 KiB |
dist/js/8313.48877e43.js | 521.19 KiB | 142.04 KiB |
dist/js/chunk-antv.6b751d47.js | 430.46 KiB | 126.56 KiB |
Time: 135782ms
构建时长和 esbuild-loader 差不多,也是2分钟多一点。
总结
构建框架 | 耗时 |
---|---|
vue cli 4 (babel-loader & TerserPlugin) | 7min |
vue cli 5 (babel-loader & TerserPlugin) | 4min+ |
vue cli 5 (esbuild-loader & EsbuildPlugin) | 2min+ |
vue cli 5 (swc-loader & EsbuildPlugin) | 2min+ |
可以看出使用 esbuild-loader 和 swc-loader 都能明显加快构建速度,相比 babel-loader,耗时差不多缩短一半。