WebPack
是我一直不是很喜欢但又不得不用的一个工具。主要原因:
- 配置很奇怪 : 尤其是前几年升到5后,还要引一些 plugin 去 licence,经常看到一些几百行的 webpack.config。
- 性能也很慢 : 虽然有一些非主流的工具比它快一点,但基实提升并不明显,没有成为主流之前,风险大于收益。
- 生态太完善 : 这个应该是优点才对,但对于一个工具,不干本质工作,什么都想搞,导致团队里面很多人过度滥用,引一大堆的 loader,plugin,进一步放慢打包速度,且严重降低代码可读性。
最近发现了一个新的工具:esbuild
,主要特性一个字 快 !
EsBuild : https://esbuild.github.io/
当然,眼见为虚,Run了才为实
速度对比
手头有两个项目,参考 esbuild 的文档,花不了一个小时,项目成功从 webpack 改造成 esbuild。
- 一个比较重,用 webpack 启动就要半分钟,watch 每改一下1秒多。
- 一个比较轻,用 webpack 启动也要等5到8秒,watch 改一下 0.3秒左右,很快,但也有肉眼可见的延时。
由于差距实在太大,就没有太大必要去看具体多少毫秒了,但实际上,这要的对比对于二者都有些不公平。
webpack 的 watch 我用的是 development
模式,速度快于 none
和 production
但实际上是通过 eval
方法执行,webpack 仅做了简单的拼接操作。
而 esbuild 默认的模式就已经相当于 webpack 里面的 none
,还有一个 minify
模式相当于 production
, 从这点上来看, esbuild 又超出了 webpack 一档。
但是,esbuild 没有了 lint 的检查功能,如果一段 typesciprt 格式有错(VSCode 里面出现了红线), webpack 会报错,而 esbuild 会支持编译成功。
当然也不是完全没有检查,对于基本的 es语法错误,包引用错误,esbuild 还是会报错的。
有这个工具替换后,每天打开电脑,心情都会美丽一些。
安装
npm install --save-dev esbuild
虽然 esbuild 有自带的命令行,但不想增加认知压力。鉴于项目使用了 gulp ,就直接给 gulpfile 加 task 就可以了。
在 gulpfile 里面这样写,这样可以用 gulp watch
代替 webpack -w
了,还省了一个 webpack config 文件。
import * as gulp from 'gulp'
import * as esbuild from 'esbuild';
import * as _ from 'lodash';
const js = cb => {
let watch = cb ? false : {
onRebuild: (error, result) => {
if (error) {
console.error('build error', error)
} else {
console.log('build success')
}
}
}
esbuild.build({
entryPoints: ["./src/index.ts"],
outfile: './public/js/out.js',
bundle: true,
minify: cb ? true : false,
platform: 'browser',
watch,
}).then(result => {
if (watch) {
console.log('Start Watching...')
} else {
console.log(result)
cb();
}
}).catch(err => {
console.error(err);
throw (err);
})
}
export default gulp.parallel(js, ...);
export const watch = () => js(false);
特性
除了快,还有很多不错的特性
- 支持 es6 和 CommonJS 模式
- 支持编程与命令行接口,编程支持 nodejs 和 golang
- 原生支持 typescript, tsx,jsx等,不再需要 ts-loader,babel-loader 等了。
- 可以生成 Source maps文件
- 支持压缩,相当于 production 模式
- 支持插件扩展
直接支持的类型
- js 、 jsx、 mjs 、 cjs
- ts 、 tsx、 mts 、 cts
- json
- css
- 文本 默认后缀为 .txt
- 二进制 默认不开启,需要指定后缀,如 –loader:.data=binary
- base64 默认不开启,需要指定后缀,如 –loader:.data=base64
- 转base64数据链接,默认不开启,需要指定后缀,如 –loader:.png=dataurl
- 文件地址链接,默认不开启,需要指定后缀,如 –loader:.png=file
依赖 external
在之前 webpack 的项目中,由于页面多,于是把一此通用的依赖抽了出来,像这样:
externals: {
"lodash": "_",
"dat.gui": "dat",
"three": "THREE"
}
esbuild 也提供了类似的参数
esbuild.build({
...,
external: ['three', 'lodash', 'dat.gui'],
})
编译后,发现不灵了。
目前我的解决方案是 : 自己写个 require 方法去兼容, 不清楚有没更好的方法。
<script src="js/three.min.js"></script>
<script src="js/lodash.min.js"></script>
<script src="js/dat.gui.min.js"></script>
<script>
const require = function (mod) {
switch (mod) {
case 'dat.gui':
return dat;
case 'three':
return THREE;
case 'lodash':
return _;
}
}
</script>
小结
- esbuild 速度上优势明显,但功能上不能完全取代 webpack,特别是在 webpack 生态耕耘了的项目来说更是难以迁移。
- 另外不支持 typescript 的lint 感觉有些小麻烦(只是简单使用,我也不知道,可能可以通过扩展支持说不定。)
- 由于我用 react 可以原生支持,但 vue 的项目(.vue后缀)不知道如何解决,应该也有方案,没细研究。
- 还有,现在的插件也比较少,需要自己写,例如一个 envPlugin 插件就是一个方法,也不是很困难。
let envPlugin = {
name: 'env',
setup(build) {
// Intercept import paths called "env" so esbuild doesn't attempt
// to map them to a file system location. Tag them with the "env-ns"
// namespace to reserve them for this plugin.
build.onResolve({ filter: /^env$/ }, args => ({
path: args.path,
namespace: 'env-ns',
}))
// Load paths tagged with the "env-ns" namespace and behave as if
// they point to a JSON file containing the environment variables.
build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({
contents: JSON.stringify(process.env),
loader: 'json',
}))
},
}
require('esbuild').build({
entryPoints: ['app.js'],
bundle: true,
outfile: 'out.js',
plugins: [envPlugin],
}).catch(() => process.exit(1))