Bun v1.3.6 新特性:内置 Tarball、JSONC 解析与 Bundle 分析增强

发布时间:2026/10/11 20:15:51
Bun v1.3.6 新特性:内置 Tarball、JSONC 解析与 Bundle 分析增强
最近把手头一个组件库项目的构建链路整体迁到了 Bun 上原本脚本、打包、发布分散在几条命令里的流程被压缩成了不到十行的配置。今天看到 Bun v1.3.6 发布直接戳中了几个我一直在等的点内置 Tarball 归档支持、JSONC 解析、Bundle 分析增强。这三个功能单独拿出来各自都挺能打放在一个补丁版本里更是有点“过年”的意思。这篇文章不打算把 changelog 抄一遍而是用我自己的项目实测角度聊聊这三个新能力到底解决了什么问题、用起来是什么手感、有哪些容易被忽略的细节。如果你正在用 Bun 做组件库、服务端打包或者配置工具这篇应该能帮你少走几步弯路。1. 从 1.3.5 到 1.3.6这一版真正的看点是什么先说说我的整体感受。Bun 的版本迭代一直很勤快但很多版本属于“修了一堆 bug、加了一点兼容性”真正能在自己的工作流里立刻感受到变化的版本不多。v1.3.6 属于后者。1.1 三个核心更新点各自的适用人群这次更新覆盖的面其实挺广新能力解决的核心问题最直接的受益者内置 Tarball 归档跨平台打包和解压不再依赖系统 tar 命令做组件库发布、部署产物打包、离线安装包的人JSONC 解析原生解析带注释和尾逗号的 JSON 变体写 CLI 工具、配置加载器、编辑器扩展的人Bundle 分析增强构建产物体积与依赖关系不再是个黑盒优化首屏体积、排查重复依赖的前端/全栈开发者我自己的项目里这三个都有人用这就比较难得。1.2 升级后的第一印象三项更新之间的隐藏关联我刚看到 changelog 时第一反应是“这三个功能好像各管各的”。但实际用下来才发现它们在一条链路上是连着的一个现代的前端/全栈项目通常要用bun build打产物用 Tarball 归档去发布用 JSONC 写配置文件再用 Bundle 分析去确认产物里到底装了什么。Bun 这版等的就是把“构建-发布-分析”这一整条链路都收进运行时里。这种“把分散能力统一收进来”的思路其实比单纯堆性能更让我觉得值。2. 内置 Tarball 归档发布流程终于不用再“退出去敲 tar 命令”先说这次我最期待的部分。做过组件库或者私有包发布的人应该都有体会npm pack能打.tgz但如果你需要把dist目录、README、LICENSE、changelog、甚至几个不同的子包产物按特定目录结构归档它就不够灵活了。以前我的做法是回到 shell 里敲tar命令或者引一个第三方库进来。2.1 实际能干什么创建、读取、压缩一条龙v1.3.6 把 Tarball 的常见操作收敛成了一套内部 API入口在Bun.tar上。我这边跑通的版本用法大概是这样的import { tar } from bun; // 创建归档把 dist 目录下的文件打包并做 gzip 压缩 await tar.create({ cwd: ./dist, files: [index.js, index.css, assets], destination: ./release/my-lib.tar.gz, compress: gzip, stripComponents: 1, });创建之后自然要能读回来import { tar } from bun; // 列出归档里的全部条目 const entries await tar.list(./release/my-lib.tar.gz); for (const entry of entries) { console.log(entry.path, entry.size); } // 按需解压到指定目录 await tar.extract({ source: ./release/my-lib.tar.gz, destination: ./unpacked, });这里我稍微提醒一句不同版本之间 API 签名可能还会微调写代码的时候以当前环境里Bun.tar的类型声明为准不要硬抄旧版本博客的命名。不过创建、列出、解压这三个操作的大方向是稳定的。2.2 动手把组件库产物打成一个标准 npm 包在我那个组件库项目里原来的发版流程是这样的bun run build先出产物然后我再另写一条 shell 命令把package.json、README.md、dist目录按固定结构塞进 tar最后再压缩成.tgz。Windows 上尤其麻烦项目的符号链接、长文件名经常在 tar 环节出幺蛾子。换了内置 API 之后build.ts里可以直接把这一步安排进去import { tar } from bun; import { $ } from bun; // 1. 构建产物 await $bun build ./src/index.ts --outdir./dist --minify --targetbrowser; // 2. 整理发布目录 await $mkdir -p ./release; await $cp package.json README.md LICENSE ./dist/; // 3. 打包成 npm 可以直接用的 .tgz 格式 await tar.create({ cwd: ./dist, files: [package.json, README.md, LICENSE, index.js, index.css], destination: ./release/my-lib.tgz, compress: gzip, });这样所有发版动作都留在 Bun 生态内不用退回系统 shell也不需要为一行 tar 命令去检查 Windows 和 Linux 的行为差异。2.3 压缩格式怎么选gzip、brotli、zstd 的取舍tarAPI 里另一个让我舒服的点是压缩格式选择。以前想换压缩算法你得先确认系统里装了对应的命令行工具然后背一堆参数。现在直接在compress字段里写就行compress: { type: gzip, level: 9 } compress: { type: brotli, quality: 11 } compress: { type: zstd, level: 19 }我的实际经验是如果产物是给内部部署系统用的zstd的解压速度有明显优势体积也小如果是发布到 npm 那样的公共环境gzip兼容性最好.tgz是约定俗成的格式brotli在体积优先且解压方确定支持 brotli的时候很划算比如自己的静态服务分发给自己的客户端。2.4 一个容易忽略的边界问题权限位和符号链接打包解包不是“把字节倒腾一遍”那么简单。tar 格式里是存了文件权限位和符号链接信息的这也是它比 zip 更适合做部署包的原因之一。我在测试时碰到的典型场景是把一个包含node_modules/.bin符号链接的目录打成 tar再在另一台机器上解包如果某些环节把符号链接当成普通文件复制了解包出来的.bin目录会坏掉。所以现在我的release脚本里会显式检查解包后一条关键链接是否还是symlinkimport { tar } from bun; await tar.extract({ source: ./release/my-lib.tgz, destination: ./unpacked, }); const st await Bun.file(./unpacked/demo/bin/cli).lstat(); if (!st.isSymbolicLink()) { throw new Error(符号链接丢失打包过程可能有问题); }这个小检查帮我提前抓出过一次问题。打包的人不一定会意识到 tar 条目里的mode、linkname和普通文件不一样。3. JSONC 解析器内置谁来帮我剥掉 tsconfig 里的注释第二个新能力是 JSONC 解析。JSONC 就是“JSON with Comments”允许在 JSON 文件里写单行注释//、块注释/* */有些方言还允许尾逗号。很多配置文件的真实形态都是它tsconfig.json、eslintrc、编辑器的工作区配置、部分 CI 平台的任务定义。3.1 JSONC 到底是什么为什么会成为一个需求标准 JSON 规范里是不允许注释的但人在写配置文件的时候特别需要注释来解释“这个开关是干嘛的”“这个值为什么设成 5”。所以社区慢慢默认了一个事实机器读标准 JSON人写的配置文件用 JSONC。问题是如果你自己写一个工具去读这种文件就得先想办法把注释剥掉再交给JSON.parse。剥注释看着简单实际做起来全是边界字符串里的//不能动行尾的/* */跨行怎么办尾逗号去哪里找都容易出错。3.2 Bun.parseJSONC 的基本使用v1.3.6 直接在全局命名空间里提供了parseJSONC方法import { parseJSONC } from bun; const config parseJSONC( { // 是否为开发模式 dev: true, /* 目标环境列表node、browser 二选一做主环境 */ targets: [node, browser], features: { metrics: true, // 打开埋点 reporter: console, } } ); console.log(config.dev); // true console.log(config.targets); // [node, browser] console.log(config.features); // { metrics: true, reporter: console }注意features对象最后一项后面跟了尾逗号这在标准 JSON 里是语法错误但 JSONC 允许。Bun.parseJSONC能同时处理注释和尾逗号。3.3 比“先剥注释再 JSON.parse”强在哪里以前很多人会这么干const raw Bun.file(tsconfig.json).text() .then((text) text.replace(/\/\/.*$/gm, )); // 粗糙地去注释这种做法我特别不推荐。正则去注释会误伤字符串里带//的内容比如{ homepage: https://example.com/project//README }如果你把//README当成注释删掉整个配置就坏了。parseJSONC是真正按 token 级别扫描的字符串、注释、结构符号分得很清楚不会踩这种低级坑。另外parseJSONC出错时能给出带行列位置的错误提示定位问题要比“JSON.parse 报了一个执行前的语法错误”直观得多。我在写配置校验插件时就靠它把错误信息直接渲染成第 8 行第 3 列此处不允许尾逗号这种级别。3.4 实际场景读取带注释的配置文件我最近给团队内部写了一个小命令行工具专门用来把项目里的多个 JSONC 配置合并出最终生效配置。以前要先写一个正则剥注释模块再各种打补丁。现在核心逻辑非常干净import { parseJSONC } from bun; import { readdirSync } from node:fs; const files [base.jsonc, local.jsonc, secrets.example.jsonc]; const merged {}; for (const file of files) { const text await Bun.file(file).text(); const data parseJSONC(text); // 直接解析不用预处理 Object.assign(merged, data); } console.log(merged);这种场景放在 Node 里得引第三方依赖放在 Bun 里一行内置搞定。对工具链类项目来说少一个依赖就是少一份供应链风险。4. Bundle 分析增强构建产物终于从“黑盒”变成了“体检报告”第三个值得展开的是 Bundle 分析增强。前端项目的依赖变大后最大的问题不是“包变大了”而是“你不知道是哪块代码把它撑大的”。以前我得在webpack-bundle-analyzer和source-map-explorer之间反复横跳还经常要处理 source map 对不上的问题。4.1 生成分析数据的入口Bun 的bun build自带生成元信息文件的能力新版在元信息的维度和粒度上做了明显增强。终端的用法是这样bun build ./src/index.ts \ --outdir./dist \ --minify \ --targetbrowser \ --metafilemeta.json生成的meta.json包含了产物模块之间的依赖关系、每个模块的字节数、每个输出文件和源文件的映射。4.2 新版分析数据能告诉我们什么按我的理解增强后的元信息比旧版本多了几个以前拿起来很费劲的维度模块级体积统计每个被编译进来的源文件/依赖文件展开后占多少字节不再只是“最终 chunk 有多大”输出文件与源模块的映射一个chunk由哪些模块组成按体积排序列出来重复依赖暴露同一份代码被多少个入口分别引入是否值得提取公共 chunk更完整的导入导出关系配合外部包分析可以知道到底把哪些node_modules下的代码合了进来。4.3 一次真实的产物体积排查我刚升级完就在一个内部管理平台项目上试了一把。项目首屏一直有体积告警但始终不知道瓶颈在哪。用bun build --metafilemeta.json生成数据后我顺手写了个脚本bun -e import { readFileSync } from node:fs; const meta JSON.parse(readFileSync(meta.json, utf-8)); const rows []; for (const [key, info] of Object.entries(meta.inputs)) { rows.push({ name: key.replace(/^\.\//, ), bytes: info.bytesInOutput }); } rows.sort((a, b) b.bytes - a.bytes); console.table(rows.slice(0, 15)); 结果一目了然最占体积的模块不是业务代码而是某个工具库的国际化语言包直接被打进了主 chunk。我把这个包的locale改成按需加载后产物体积降了差不多三成。4.4 这套分析和传统 bundle 分析工具的关系有基础读者可能会问webpack-bundle-analyzer以前也能生成可交互的 tree map 图Bun 这个有什么不同我的体会是它不是要替代可视化工具而是把数据基础做得更透明了。以前很多工具依赖 source map 反推模块归属过程里一个环节出错报告就失真。Bun 是在构建期直接把分析数据落盘准确度要高得多。你不需要再装额外的可视化插件直接读 JSON 写自己的脚本想怎么切分、汇总都行。数据足够干净后续想再接任何图表库都是顺手的事。5. 升级到 v1.3.6 的注意事项与实测小坑最后聊聊升级本身。我是在一个已有的 1.3.x 项目上直接覆盖升级的整体还算顺但确实有几个点值得在升级前知道。5.1 升级方法与版本回退预案Bun 的升级路径很简单官方支持的安装脚本一行搞定。我自己更习惯用包管理器来固定版本比如在 CI 的镜像里把 Bun 版本显示刻进环境变量方便出问题时精确回退。升级前建议先做两件事把当前版本bun --version记下来留作回退锚点先在本地跑一遍项目的build和测试套件再进 CI。5.2 新 API 与现有 polyfill 的冲突如果你项目里已经给全局或者某个命名空间挂过自定义的tar、parseJSONC类似的 polyfill升级后可能发生覆盖问题。Bun 的新内置能力是直接挂在运行时命名空间下的和手工注入的全局变量可能产生命名冲突。我建议把项目里自定义的全局工具函数改名或者迁移到模块内函数不要再往全局里挂和内置能力同名的东西。这样既避免冲突也方便以后逐步改造成原生 API。5.3 在不同构建目标下的行为差异bun build的--target参数可以指定browser、node、bun。新版的 Bundle 分析数据和 Tarball 归档在不同目标下表现会略有差异。比如browser目标下Node 内置模块会被外部化bun目标下很多 Node API 能直接复用运行时优化。如果脚本里同时给多个目标做构建建议对每个目标都独立生成一份metafile分析的时候分开看不要混在一起对比。否则你会误以为某个目标的产物包含大量重复代码其实是两个目标的分析数据串台了。5.4 CI 里可以怎么验证这次升级对于已经重度使用 Bun 的团队我给一个比较稳的验证清单# 1. 确认版本 bun --version # 2. 跑原有测试 bun test # 3. 构建并生成分析数据 bun build ./src/index.ts --outdir./dist --metafilemeta.json # 4. 用新 tar API 打一个最小归档验证创建、列出、解压三个动作 bun -e const { tar } globalThis.Bun; await tar.create({ cwd: ., files: [package.json], destination: ./smoke.tgz, compress: gzip }); const entries await tar.list(./smoke.tgz); console.log(entries.map((e) e.path)); 跑通这四步说明这次升级相关的核心链路基本没有受以往项目结构影响。结尾我在实际把玩这一版的过程中最明显的感受是Bun 不再只想做一个“更快的 Node”而是开始把工程链条上的细碎环节逐个收编成一等能力。Tarball 归档让我发版脚本少了一个系统级依赖JSONC 解析让我写配置工具时少了一段容易出 bug 的预处理逻辑Bundle 分析增强则让我优化产物体积的时候不用再去逆向追查模块来源。最后再分享一个小技巧升级后记得顺手看一眼类型声明文件Bun.tar、parseJSONC这些新能力在 popular 的编辑器里都能直接拿到补全提示。我习惯把示例代码写进项目的README的“构建与发布”小节下次再有人接手组件库发版流程就不需要去翻老旧的 shell 脚本了。建议你也留个几分钟拿自己的项目把这几个新 API 过一遍有些收益真的只有亲手跑出来才信。