调研 Bun bundler 实现依赖预打包。Bun 还未发布正式版本,bun bundler 现在还在 beta 阶段,为啥选用 bun 打包?
看了下官方文档,bun build 配置非常简单,无法解决业务工程复杂工程化问题。而且 target
仅支持 browser
、bun
、node
三个值,format
暂时只有 esm
(后续计划支持 cjs
和 iife
),这样如果 bun 打包业务工程,产物兼容性会有很大问题。另外,bun 作为一个新的打包器,产物稳定性无法与 Webpack、Rollup 相比。
虽然 bun 不能用于打包业务工程,但是有几个场景可以考虑:
- 业务工程本地 dev;
- 打包组件库;
- 打包一些 node 依赖。
为啥以上场景可以用 bun,因为这些场景打包产物并不直接部署,比如打包组件库,输出 esm 格式完全没问题,兼容性也可以在业务工程构建的时候进行处理,再比如打包 node 依赖,这些一般都是 devDependencies,对兼容性没有那么敏感,同时如果产物质量有问题,一般本地跑就能提前发现了,最坏情况就是 CI 构建报错,对用户侧来说不会有影响。
下面来谈谈 bun 打包 node 依赖有啥好处。如果用过 UmiJS 或者 Next.js,对依赖预打包这个概念应该都很熟悉。为了解决第三方库间接依赖不受控问题,进而导致一些潜在的 npm 供应链攻击问题,解法就是依赖预打包,收敛 npm 依赖层级。核心思想就是交给中间商锁定依赖,并对此负责,定期更新。
依赖预打包还可以显著提升 Node CLI 应用启动性能。我们知道,在 Node 里面 require()
是非常昂贵的,存在大量 IO 操作,如果分析一下火焰图,实际上 Node CLI 应用运行大部分时间都在做模块加载。如果将 Node 应用提前打包为单文件,则可以避免运行时 require()
开销(另一种优化策略是按需 require()
,将 require()
延迟到需要的时候再去加载)。
如何做依赖预打包,Next.js 和 UmiJS 的做法是用 ncc
打包(内部封装了 Webpack,用 target: "node"
打包),当然也可以用 father-build 打包(底层实际上也是 Webpack)。考虑到依赖更新问题,依赖预打包可能是高频操作,整体构建速度会影响开发效率。我们可以看下 Next.js 仓库,预打包依赖有数百个,无论本地 dev 还是 CI build、发包,都要跑一遍依赖预打包。显然,用更快速的打包器可以极大提升开发效率。
从 bun 官方文档可以看到,打包 10 份 Three.js 作为基准测试,bun 仅需 0.17s,甚至比 esbuild 还要快(大家可以本地跑一下,几乎在敲命令的瞬间,bun 就已经打包完了):
安装:
$ brew tap oven-sh/bun # for macOS and Linux
$ brew install bun
更新:
$ brew upgrade bun
依赖预打包:
$ bun build webpack
--outdir ./compiled
--entry-naming "webpack/[name].[ext]"
--target node --minify
--external pnpapi
--external uglify-js
--external @swc/core
--external esbuild
# 本人 M1 Pro 机器,全量打包一份 Webpack 仅需 68ms
[68ms] bundle 867 modules
可以看出,bun 确实非常快,几乎在敲命令的瞬间就打包完了,性能提升巨大。
另外还推荐尝试一下 bun run
和 bunx
,极快。平时我们跑 npm scripts,一般会用 npm run、yarn run、pnpm run 等等,需要注意,这些 runner 启动也是有开销的,而且都比较慢,而 bun run
跑 npm scripts 启动速度极快,体验非常丝滑。
体验过 bun 之后,个人建议 Vite 可以放弃维护了。Vite 4.3 没有新功能,只是更快,几乎已经榨干了 JS 代码的性能,而且还是在降低代码语义性、可维护性的情况下,后续应该很难有进一步提升空间了。Vite 4.3感觉和 patak 大佬观点背道而驰,当初选用 JS 为了让前端开发者更容易维护,结果现在却牺牲维护性换取性能。其实我个人觉得,选用 Go 语言也不错,容易上手(对前端友好,同样是 FP 编程风格),而且性能又好。
参考:
bun.sh/blog/bun-bu…
shaneosullivan.wordpress.com/2023/05/17/…