swc与esbuild-将你的构建提速翻倍
背景
最近一直在尝试对项目构建性能进一步优化, 除了webpack常用的一些优化方式,比如缓存优化、多进程优化等,也尝试在项目内使用swc
及esbuild
,以进一步提高构建速度,本篇主要记录如何在项目中使用swc
、esbuild
,及落地过程中碰到的一些问题,避免后续踩坑
项目内使用
使用swc
安装swc-loader
pnpm i -D @swc/core swc-loader @swc/helpers
使用swc-loader替换babel-loader
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules)/,
use: {
// `.swcrc` can be used to configure swc
- loader: "babel-loader",
+ loader: "swc-loader",
+ options: {
jsc: {
parser: {
syntax: "typescript",
tsx: true,
decorators: true,
},
transform: {
legacyDecorator: true,
},
externalHelpers: true, // 注意这里设置true时,需要在项目下安装@swc/helpers
target: 'es5',
},
env: {
targets: "last 3 major versions, > 0.1%", // 根据项目设置
mode: "usage",
coreJs: "3" // 根据项目选择
},
isModule: 'unknown'
}
}
}
]
}
terser-webpack-plugin开启swc压缩
import TerserPlugin from 'terser-webpack-plugin';
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
unused: true,
drop_console: true,
drop_debugger: true
},
mangle: true,
},
minify: TerserPlugin.swcMinify,
})
]
}
}
使用esbuild
安装esbuild-loader
pnpm i -D esbuild-loader
使用esbuild-loder替换babel-loader
module.exports = {
module: {
rules: [
- {
- test: /\.js$/,
- use: 'babel-loader'
- },
+ {
+ // Match js, jsx, ts & tsx files
+ test: /\.[jt]sx?$/,
+ loader: 'esbuild-loader',
+ options: {
+ // JavaScript version to compile to
+ target: 'es2015',
+ loader: 'tsx',
+ }
+ },
...
],
},
}
使用esbuild压缩
import { EsbuildPlugin } from 'esbuild-loader';
module.exports = {
optimization: {
minimizer: [
new EsbuildPlugin({
target: 'es2015',
css: true, // 开启css压缩,当这里开启的时候,就不需要css-minimizer-webpack-plugin这样的css压缩插件
})
]
}
}
具体demo
选择方案
其实webpack
构建过程中,相对耗时的两个部分,第一部分就是代码转换;第二部分就是代码压缩,所以无论是切换到swc
还是切换到esbuild
,还是一直使用的babel
,terser
,我们最终的目标
- 项目能够顺利构建
- 构建产物代码能够正常运行
- 构建时间尽可能的短
- 构建产物尽可能的小
所以我们是可以去组合使用swc
、esbuild
、babel
、terser
,通过不同的组合去达成我们最终的目标
代码转换
代码转换的目的,目前主要有两个
- ts转换成js
- js高版本转换成js低版本,以保证兼容性
下面是几种转换工具的优缺点
babel
: 基于Nodejs
,现在使用最多的js代码转换工具- 优点: 功能最全,覆盖场景最广,最成熟,支持输出es5、es6+代码,支持动态polyfill
- 缺点: 转换速度相对较慢
swc
: 基于Rust
,用来取代babel
的js代码转换工具- 优点: 相对
babel
快20-70倍,另外支持文件打包,支持输出es5、es6+代码,支持动态polyfill - 缺点: 配套周边相对没有
babel
成熟,基于rust对上手难度大
- 优点: 相对
esbuild
: 基于Go
,用来快速转换js的工具- 优点: 相对
babel
快10-100倍,另外支持文件打包,支持输出es6+代码,不支持动态polyfill - 缺点: 配套周边相对没有
babel
成熟,基于Go对上手难度大、不支持输出es5代码
- 优点: 相对
tsc
: 基于Nodejs
,typescript
内置的转换器- 优点: 相对babel有ts类型检查,速度慢于babel,支持输出es5、es6+代码,不支持动态polyfill
- 缺点: 速度慢,不支持取消类型检查
转换速度
esbuild
> swc
> babel
> tsc
功能齐全
babel
> tsc
> swc
> esbuild
所以我们可以基于我们的浏览器兼容目标,及我们需要达成的目标来选择不同的转换方案
压缩方案
js代码压缩,主要有两个目的
- 减少代码尺寸,以达到访问速度提升目的
- 混淆代码,防止js源代码在一定程度上的泄漏
下面是js代码压缩工具优缺点
teser
: 基于Nodejs
,由uglify
fork出来的新一代压缩工具- 优点: 功能齐全,支持
tree-shaking
,压缩比率最高 - 缺点: 速度相对较慢
- 优点: 功能齐全,支持
esbuild
: 基于Go
,支持js代码压缩- 优点: 速度快
- 缺点: 压缩比率相对
terser
低,压缩时如果target设置成es5,js代码内不能出现es6+代码,不然会报错
swc
: 基于Rust
,支持js代码压缩- 优点: 速度快
- 缺点: 压缩比率相对
terser
低
目前三者关于压缩率与压缩时间,如下所示
- 压缩率
terser
>esbuild
>swc
- 压缩时间
esbuild
>swc
>terser
所以我们可以根据实际项目选择代码压缩方式,更多压缩数据可以参考minification-benchmarks
项目实测
同一台电脑,同一个项目, 项目技术栈 react
+ antd
的常规后台cms系统
注意:
- 多进程用的是
happypack
- 上述数据都是在未开启
webpack
缓存的前提下进行
从数据上可以看出
swc
+esbuild
+es5+多进程组合效果最好,相对于babel
+terser
+es5组合提升49.4%swc
+esbuild
+es6+多进程组合效果最好,相对于babel
+terser
+es5组合提升48.6%esbuild
只能全局引入polyfill,无法根据使用的api动态导入polyfill- 而输出target一致的前提下,
swc
+esbuild
组合输出包大小仅比babel
+terser
组合大1.1%,大小完全可以忽略
注意以上数据仅作参考,具体效果,应当以自己项目为准
推荐组合
下面是对项目配置的一些推荐参数与组合方式,仅供参考
pc端项目推荐配置
选择全局polyfill
- 外部客户使用,推荐esbuild+esbuild+es2015+多进程组合,browserlist: 设置为edge 14
- 内部客户使用,推荐esbuild+esbuild+es2015+多进程组合,browserlist: 设置为last 2 Chrome versions
配置如下所示
const target = 'es2015';
module.exports = {
module: {
rules: [
{
test: /\.[jt]sx?$/,
loader: 'esbuild-loader',
options: {
// JavaScript version to compile to
target
}
},
],
},
optimization: {
minimizer: [
new ESBuildMinifyPlugin({
target,
css: true, // 开启css压缩,当这里开启的时候,就不需要css-minimizer-webpack-plugin这样的css压缩插件
})
]
}
}
选择按需polyfill
- 外部客户使用,推荐swc+esbuild+es2015+多进程组合,browserlist: 设置为edge 14
- 明源内部使用,推荐swc+esbuild+es2015+多进程组合,browserlist: 设置为last 2 Chrome versions
const target = 'es2015';
module.exports = {
module: {
rules: [
{
// Match js, jsx, ts & tsx files
test: /\.[jt]sx?$/,
loader: 'swc-loader',
options: {
jsc: {
parser: {
syntax: "typescript",
tsx: true,
decorators: true,
},
transform: {
legacyDecorator: true,
},
externalHelpers: true, // 注意这里设置true时,需要在项目下安装@swc/helpers
target,
},
env: {
targets: "edge 14",
mode: "usage",
coreJs: "3.22"
},
isModule: 'unknown'
}
},
],
},
optimization: {
minimizer: [
new ESBuildMinifyPlugin({
target,
css: true, // 开启css压缩,当这里开启的时候,就不需要css-minimizer-webpack-plugin这样的css压缩插件
})
]
}
}
移动端项目推荐配置
选择按需polyfill
- 兼容性要求到低配机型,要求输出es5,推荐swc+esbuild+es5+多进程组合,browserlist: 设置为ios 9
- 兼容性要求不高,直接输出es6,推荐swc+esbuild+es6+多进程组合,browserlist: 设置为ios 11
const target = 'es5';
module.exports = {
module: {
rules: [
{
test: /\.[jt]sx?$/,
loader: 'swc-loader',
options: {
jsc: {
parser: {
syntax: "typescript",
tsx: true,
decorators: true,
},
transform: {
legacyDecorator: true,
},
externalHelpers: true, // 注意这里设置true时,需要在项目下安装@swc/helpers
target,
},
env: {
targets: "IE 9",
mode: "usage",
coreJs: "3.22"
},
isModule: 'unknown'
}
},
],
},
optimization: {
minimizer: [
new ESBuildMinifyPlugin({
target,
css: true, // 开启css压缩,当这里开启的时候,就不需要css-minimizer-webpack-plugin这样的css压缩插件
})
]
}
}
可以根据公司的项目的实际使用情况,灵活进行选择
总结
从项目实践来看,可以得出如下结论
swc
、esbuild
相比babel
、terser
会快很多- 使用
swc
oresbuild
的时候可能会碰到一些兼容性问题,需要自己排查解决 - 从目前社区的使用情况,
swc
以后在项目中使用只会越来越普遍
FAQ
not implemented: automatic polyfill for scripts
原因是:@swc/core
版本过低,无法自动polyfill commonjs模块
解决方法:升级@swc/core
到1.3.27+版本
TypeError: _type_of is not function
下面是没有压缩代码的抛错,如下图所示
如果是压缩之后的代码抛错,如下图所示
原因是:
@swc/helpers
common导出名称不对,对于commonjs模块,swc添加的helper写法是require('@swc/helpers')._type_of
,而@swc/helpers
内的导出是驼峰写法,如下所示
解决方法:添加
webpack
alias,然后在修改@swc/helpers
的导出变量,具体如下所示
chain.resolve.alias.set('@swc/helpers$', path.join(__dirname, './swc/swc-helpers.js'));
const obj = require('@swc/helpers/lib/index');
function toLine(name) {
return name.replace(/([A-Z])/g, '_$1').toLowerCase();
}
const lineObj = Object.keys(obj).reduce((prev, key) => {
prev[toLine(key)] = obj[key];
return prev;
}, {});
module.exports = Object.assign({}, obj, lineObj);
swc转换装饰器抛错
原因:
swc
转换默认是没有开启支持装饰器语法的
解决:通过配置参数开启装饰器语法
jsc: {
parser: {
syntax: "typescript",
tsx: true,
+ decorators: true,
},
transform: {
+ legacyDecorator: true,
},
externalHelpers: true, // 注意这里设置true时,需要在项目下安装@swc/helpers
target,
},
esbuild 压缩样式会导致部分样式在低版本浏览器上有差异
如上图所示,弹窗位置在低版本浏览器上,没有出现在页面中间位置
原因:
esbuild
压缩css
会根据传入的targets转换部分css
写法,比如color,position等
原始代码
esbuild
压缩之后的代码
解决:
esbuild
压缩css
的时候传入具体的浏览器版本
{
optimization: {
minimizer: [
new ESBuildMinifyPlugin({
- target: ['es2015'],
+ target: ['es2015', 'safari12'],
css: true,
})
]
}
}
更多内容可以参考inset is not supported by many browsers
esbuild无法动态polyfill
原因是:esbuild
本身没有提供这项能力
解决方法:手动在全局引入polyfill代码
Transforming class syntax to the configured target environment ("es5") is not supported yet
原因是: 如果是esbuild
转换场景,不能输出es5代码,如果是压缩场景,则是有代码没有被转换成es5代码,导致esbuild
压缩的时候报错
解决方法: 如果是esbuild
转换场景,则输出es2015及以上;如果是压缩场景,则将没有转换成es5代码的包通过babel-loader
or swc-loader
处理
转载自:https://juejin.cn/post/7236670763272798266