使用 esbuild-loader 和 swc-loader 优化 Vue CLI 5 项目构建时间

先上实验结果

构建框架 耗时
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-editorelement-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
        }
      }
    })
  },
}

借鉴 使用splitChunks

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,耗时差不多缩短一半。

相关推荐
计算机学姐17 分钟前
基于微信小程序的调查问卷管理系统
java·vue.js·spring boot·mysql·微信小程序·小程序·mybatis
黄尚圈圈4 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
一路向前的月光9 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   9 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Jiaberrr10 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
程序员大金13 小时前
基于SpringBoot+Vue+MySQL的装修公司管理系统
vue.js·spring boot·mysql
道爷我悟了14 小时前
Vue入门-指令学习-v-html
vue.js·学习·html
无咎.lsy14 小时前
vue之vuex的使用及举例
前端·javascript·vue.js
工业互联网专业15 小时前
毕业设计选题:基于ssm+vue+uniapp的校园水电费管理小程序
vue.js·小程序·uni-app·毕业设计·ssm·源码·课程设计
计算机学姐15 小时前
基于SpringBoot+Vue的在线投票系统
java·vue.js·spring boot·后端·学习·intellij-idea·mybatis