使用swc与esbuild闪电加速你的webpack打包链路

景阳曜
2023-12-01

前言

直入正题,自从 umi mfsu 和 vite unbundle 打响 FE 高效开发第一枪,淘系 rax 新增 swc 功能、swc 作者加入 vercel 麾下,nextjs 新版本默认 swc 打包,再到阿里 ice2 新增 swc + esbuild 链路支持,swc 和 esbuild 逐渐入侵 FE 主工具流,暗示了一统天下的预兆。

Rust 和 Go 将成为未来 JavaScript 基础建设的主流语言

本文将就 swc 与 esbuild 如何打通落地到 webpack 项目中为抓手来进行蓝图描绘,在阅读前,希望你已经具备了足够的基础知识和了解度,过于细致的内容将留给各位同学自行收束。

正文

由于 esbuild 的定位是打包器,而不是真正的转译 polyfill 器,故我们的链路是 swc 先代替 babel 打包和 polyfill,再走 esbuild 压缩代码。

到这里为止,假如你是使用的 vue-cli 或 cra 的脚手架非配置透明项目,基本就可以说拜拜了(不排除 craco 的神仙高手),我们只针对 webpack 透明项目介绍使用规则。

打通 swc

首先打通 swc 为我们做 ts 的转译,当然我们是对标 React 项目。

安装 swc
  pnpm add -D swc-loader @swc/core @swc/helpers
配置 swc

项目内新建 .swcrc.js 即 swc 配置文件,该文件和官方定义的 .swcrc 配置文件名称并不同,其原因是我们需要给不同环境分流,从而细粒度手动加载 config ,一个默认配置文件满足不了我们的野望。

// .swcrc.js

const path = require('path')

module.exports = (isDev = false) => {

  // swc polyfill 策略,会复用 babel 链路,但效率比 babel 低
  const polyfillConfig = isDev ? {} : {
    env: {
      mode: 'usage', // or entry
      coreJs: 3,
      path: path.resolve(__dirname),
    },
  }

  return {
    module: {
      type: 'es6',
      ignoreDynamic: true,
    },
    // polyfill
    ...polyfillConfig,
    jsc: {
      parser: {
        syntax: 'typescript',
        dynamicImport: true,
        decorators: true,
        tsx: true,
      },
      loose: true,
      target: 'es2015',
      externalHelpers: true,
      transform: {
        legacyDecorator: true,
        decoratorMetadata: true,
        react: {
          runtime: 'automatic', // or classic
          throwIfNamespace: true,
          useBuiltins: true,
          development: isDev,
        },
      },
    },
  }
}

这里着重要说的是,swc 的配置选项都很语义化,了解
es6 - es13 规范和 babel 生态的同学们相信一眼就很熟悉了,关于其具体说明可见官方文档:

本质上官方文档也书写的极其简单,因为这些配置项大多来自 es 规范和 babel 生态圈,如果你真的不懂,请照猫画虎使用即可,此处将不再浪费口舌介绍基础知识。

打通 webpack swc 链路

配置 webpack.config.js 代替 babel-loader 的部分:

// webpack.config.js

const jsReg = /\.(js|jsx|ts|tsx)$/
const swcLoader = 'swc-loader'
const swcConfig = require('./.swcrc.js')
const nodeModulesReg = /node_modules/

module: {
  rules: [{
    test: jsReg,
    loader: swcLoader,
    options: swcConfig(isDev),
    exclude: nodeModulesReg,
  }]
},

关于 exclude 要着重注意配置上,polyfill 侵染到 css 是要吃苦头的,详见:

《 mini-css-extract-plugin TypeError: $ is not a function 问题解决 》

当然如果你有特殊的定制逻辑,按自己的高级正则配置即可。

到现在为止,swc 链路就完整打通了,下面我们打通 esbuild 链路。

打通 esbuild

安装 esbuild
  pnpm add -D esbuild esbuild-loader
配置 esbuild

我们使用 esbuild-loader 自带的 mini 插件,借助为压缩而生打通 webpack 的插件 ESBuildMinifyPlugin 完成我们的野望。

// webpack.config.js

const esbuild = require('esbuild')
const { ESBuildMinifyPlugin } = require('esbuild-loader')

minimizer: [
  new ESBuildMinifyPlugin({
    target: 'es2015',
    legalComments: 'none', // 去除注释
    css: true, // 压缩 css
    implementation: esbuild, // 自定义 esbuild instance 实现
  })
]

相信有知识积累的同学一眼就知道了,直接把 Terser 和 CssMini 给替换了,其实就是 TerserPluginCssMinimizerPlugincss-minimizer-webpack-plugin )。

当然,css mini 还要联动 mini-css-extract-plugin 的 Loader ,整体是一环套一环,具体可见 esbuild-loader文档说明

到此为止,我们的 esbuild 链路也打通了。

总结

根据实战经验,项目越大享受的收益越大,至少有 2x 打包时间减半以上的收益。

目前 swc 和 esbuild 都处于疯狂迭代中,使用过程中即时 sync 最新的 @swc/coreesbuild 实现本体才是王道。

 类似资料: