Bun 包管理器工程手册:从高速安装到可回滚依赖治理
把 npm install 换成 bun install,第一次通常很顺利:下载快、命令短、已有 package.json 也能继续使用。真正决定团队能否采用 Bun 的,却不是这一轮热缓存耗时,而是另一组问题:谁提供 Bun 二进制,bun.lock 是否成为唯一依赖事实,旧项目继续 hoisted 还是切 isolated,原生依赖的安装脚本有没有被错误拦截,以及 Node.js 运行项目是否仍由 Node.js 完成验证。
Bun 同时包含 runtime、package manager、script runner、test runner 和 bundler。只把 bun install 用作依赖恢复入口时,生产运行时仍然可以是 Node.js;不要因为更换包管理器,就默认把测试、构建或线上进程一并切到 Bun runtime。
先判断是否适用
Bun package manager 适合希望缩短依赖恢复时间,同时愿意把工具版本、bun.lock、hoisted / isolated linker、workspace、registry、代理与 CA、缓存和生命周期脚本纳入团队契约的项目。采用前必须能回答:二进制从哪里来,干净环境如何冻结恢复,Node.js 项目如何验证,以及从 npm、pnpm 或 Yarn 迁入后怎样停止和回滚。
如果目标是使用 Bun runtime API、测试框架、bundler 或服务端能力,应转向对应运行时和开发工具;如果问题是 Nx、Turborepo 等任务图、affected 计算和远端缓存,应转向 Monorepo 构建工具。包发布、制品仓库服务端和生产流水线由 CI/CD 与制品工具负责,Bun package manager 在这些链路中只承担依赖恢复和脚本入口。
安装 Bun 前先保存旧锁文件和项目入口
准备一台非生产开发机、一个可删除实验目录和一个代表真实生态的候选项目。候选项目应至少包含一种真实约束,例如 workspace、原生扩展、postinstall、私有 scope、补丁或跨平台构建;只有纯 JavaScript 的空项目无法证明迁移兼容。
先记录当前事实,不要急着安装:
node --version
npm --version
git status --short再盘点仓库入口:
git ls-files package.json package-lock.json pnpm-lock.yaml yarn.lock bun.lock bun.lockb .npmrc bunfig.toml如果项目尚未批准 Bun,不要直接在主分支执行 bun install。Bun 会在没有 bun.lock 时尝试迁移已有 lockfile,可能产生新的依赖事实;应先创建试点分支并保存旧锁文件与 CI 基线。
先选受控来源
Bun 是单一可执行文件。安装页面列出官方脚本、npm、Homebrew、Scoop、Docker 和直接下载入口,也提供旧版本、CPU 与平台要求。个人实验可以使用官方脚本;团队选定来源前,应在该页面确认目标平台支持、精确版本安装方式与卸载路径。
macOS / Linux 官方脚本入口:
curl -fsSL https://bun.com/install | bashWindows PowerShell 官方入口:
powershell -c "irm bun.sh/install.ps1|iex"也可以从组织批准的软件源安装:
npm install --global bun
brew install oven-sh/bun/bun
scoop install bun不要在企业终端盲目执行远程脚本。先由工具 owner 校验官方域名、下载产物、签名或摘要,再把批准版本放进软件分发、开发镜像或 CI setup。通过 Homebrew、Scoop 等包管理器安装后,应继续由同一来源升级,避免 bun upgrade 与外部包管理器争抢二进制。
版本和命令来源一起确认
安装后记录版本与精确 revision:
bun --version
bun --revisionPOSIX 环境检查命令来源:
command -v bunWindows 检查:
Get-Command bun | Format-List Name,Source,Version版本升级不能使用浮动 latest 直接穿透所有开发机和 CI。升级前先查看安装与升级说明中的稳定版、旧版本安装和平台要求,在试点分支安装精确批准版本,记录 bun --revision,通过代表项目后再更新开发镜像和 CI。canary 构建是问题复现入口,不是生产基线。
配置:先固定安装语义
项目级 bunfig.toml 应只保存可公开、需要团队一致的行为。一个显式选择 isolated linker 的示例是:
[install]
linker = "isolated"
[install.cache]
disable = false
disableManifest = false配置不是越多越好。只有当团队已验证该项确实改变构建契约时才提交;用户级代理、私有 CA 绝对路径和 token 不应写进仓库。
安装结果由四层事实共同决定:
故障排查必须先判断是哪一层漂移。删掉 node_modules 只能重建物化层,不能修复错误 registry、被污染 lockfile 或失效凭证。
bun.lock 是依赖事实,不是速度附件
bun install 创建 bun.lock。锁文件说明要求把它提交到 Git,并说明无安装生成、旧二进制格式转换和自动迁移行为。它是人类可读、可 diff 的锁文件,评审时至少检查:直接依赖变化、间接版本变化、下载来源、workspace 关系、patch、override / resolution 和配置版本是否符合预期。
只生成锁文件、不物化 node_modules:
bun install --lockfile-only这条命令仍可能填充全局缓存中的 registry 元数据和 Git / tarball 依赖,不能把它当成“完全无网络、无副作用”。
旧项目从 bun.lockb 转成文本格式时,先保持解析结果冻结:
bun install --save-text-lockfile --frozen-lockfile --lockfile-only确认生成的 bun.lock、依赖树和项目测试后,再在同一评审中删除 bun.lockb。不要长期提交两个 Bun lockfile;团队会失去唯一事实来源。
hoisted 与 isolated:选择依赖可见性
isolated installs 说明展示了两种布局和项目历史对默认值的影响。linker 不存在一个适合所有仓库的永久默认值:新 workspace 可能选择 isolated,新单包项目通常使用 hoisted,旧项目或从 npm / Yarn 迁入的项目又可能保留 hoisted 兼容路径。因此项目应显式选择并验证,而不是根据另一仓库的结果猜测。
hoisted 把尽可能多的包提升到共享 node_modules,对传统工具和依赖平坦布局的旧项目兼容更好,代价是未声明依赖可能因为“恰好可见”而继续工作。
isolated 把包放入 node_modules/.bun/ 的中央结构,再通过链接暴露给直接声明它们的消费者。它能更早发现幽灵依赖,更适合 workspace 和库开发,但部分旧工具、文件监听器、打包器或受限文件系统可能不理解链接结构。
显式实验两种模式:
bun install --linker hoisted
bun install --linker isolated项目决定后写入 bunfig.toml,不要依赖版本变化中的隐式默认值:
[install]
linker = "hoisted"选择标准不是“哪个更新”,而是:
| 判断面 | hoisted | isolated |
|---|---|---|
| 旧生态兼容 | 通常更高 | 需要验证链接与严格依赖 |
| 幽灵依赖发现 | 较弱 | 较强 |
| workspace 隔离 | 依赖提升规则 | 依赖声明边界更清楚 |
| 文件系统约束 | 传统目录更友好 | 受 symlink / 工具兼容影响 |
| 迁移成本 | npm / Yarn 旧项目较低 | 需要修复未声明依赖 |
架构师应把 linker 当成模块边界策略。切换 linker 后必须删除旧 node_modules,做冷恢复并运行完整测试;热目录上的“安装成功”没有证明布局切换有效。
最小验证:用干净恢复证明 Bun 只负责包管理
创建可删除目录,写入 package.json:
{
"name": "bun-pm-lab",
"version": "1.0.0",
"private": true,
"scripts": {
"verify": "node -e \"console.log(require('lodash/package.json').version)\""
},
"dependencies": {
"lodash": "4.17.21"
}
}执行:
bun install
bun run verify
bun pm ls
bun why lodash预期生成 bun.lock 和 node_modules,验证脚本输出 4.17.21。这里明确使用 node 执行脚本,是为了证明 Bun package manager 产生的 node_modules 可以服务既有 Node.js 项目,而不是悄悄把 runtime 一起迁了。
接着删除 node_modules,执行冻结恢复:
bun ci
bun run verify最后只修改 package.json 中 lodash 版本,不更新 bun.lock,再次执行 bun ci。预期安装失败,这证明 CI 不会静默改写依赖事实。恢复文件后,删除整个实验目录。
项目接入:workspace 与真实脚本
根 package.json 可以按 Bun workspace 说明声明普通 workspace,并用 workspace: 保证内部依赖解析到本地包:
{
"name": "bun-workspace-lab",
"private": true,
"workspaces": ["packages/*"],
"scripts": {
"verify": "bun --filter '*' verify"
}
}子包通过 workspace:* 明确引用本地包:
{
"name": "@example/app",
"private": true,
"dependencies": {
"@example/shared": "workspace:*"
},
"scripts": {
"verify": "node index.cjs"
}
}安装和筛选验证:
bun install
bun install --filter '@example/app'
bun --filter '*' verifyworkspace 只解决多包依赖链接和作用域命令,不自动解决 affected 计算、远端任务缓存或大仓权限。那些能力属于 Monorepo 与构建加速家族。
明确新增、删除和更新依赖时使用:
bun add zod
bun add --dev typescript
bun remove zod
bun outdated
bun update zod
bun update --interactive
bun why typescript
bun pm ls -abun update --latest 可以突破 manifest 现有 semver 范围,不应作为无人审查的日常命令。依赖变更提交必须同时包含 manifest、bun.lock、变更原因和测试证据。
运行 package.json 脚本时优先写完整形式:
bun run test
bun run buildBun 也允许省略 run,但短命令可能与可执行文件或源码文件重名。团队脚本和文档使用完整形式更容易审查。运行 Node 项目时还要区分 bun run 的脚本调度与 Bun runtime;不要未经兼容验证加入 --bun 强制替换脚本中的 node。
registry、代理、CA 与凭证
Bun 默认访问 npm 公共 registry,可以读取 .npmrc。scope 与 registry 配置说明给出了 bunfig.toml、环境变量凭证和 .npmrc 的对应入口。从 npm 迁移的第一阶段,复用已审查的 scope 配置通常比同时改格式更稳:
registry=https://registry.npmjs.org/
@example:registry=https://packages.example.invalid/npm/
//packages.example.invalid/npm/:_authToken=${NPM_TOKEN}example.invalid 是保留示例域名。真实 token 由环境变量、凭证助手或 CI Secret 注入,不提交到仓库。
确认 Bun 兼容后,可以把 Bun 特有源配置迁到 bunfig.toml:
[install]
registry = "https://registry.npmjs.org"
[install.scopes]
"@example" = { token = "$NPM_TOKEN", url = "https://packages.example.invalid/npm/" }迁移配置时一次只改一个变量:先保持 registry、token、CA 和 linker 不变,证明新配置等价,再删除旧配置。把 .npmrc、bunfig.toml、用户级配置和环境变量叠在一起,会让最终源难以解释。
代理或私有 CA 失败时,不要使用 --no-verify 作为长期修复。它跳过完整性验证,会把网络问题升级为供应链问题。正确路径是验证 DNS、代理、证书链、CA 文件和 registry 权限,并在受控机器上复现。
生命周期脚本:默认安全也需要审批
依赖的 preinstall、install、postinstall 可以执行任意 shell 命令。生命周期脚本说明明确了内置允许列表、trustedDependencies 和 --ignore-scripts 的关系。Bun 不默认执行任意依赖脚本,而是基于允许列表和项目声明决定是否执行。
显式批准某个依赖:
{
"trustedDependencies": ["esbuild"]
}关键陷阱是:一旦项目声明 trustedDependencies,它会替换内置列表。遗漏原本需要脚本的包,可能表现为安装成功但原生二进制缺失。trustedDependencies: [] 则表示不信任任何依赖脚本。
审批时记录:包名、脚本内容、为什么需要、来源、owner、复审版本和撤销方法。不要看到某个原生包失败就把所有脚本打开。使用 Git、file:、link: 等非 registry 来源时,即使名称与内置允许项相同,也需要显式信任。
缓存清理与冷恢复
Bun 的全局缓存说明给出的默认位置是 ~/.bun/install/cache,也可通过 BUN_INSTALL_CACHE_DIR 或 bunfig.toml 调整。Linux 和 Windows 可能通过 hardlink 节省磁盘,macOS 使用 clonefile;因此项目目录与缓存目录并非总是两份完全独立的物理数据。
先定位现象,再清缓存:
bun pm cache rm缓存清理不是标准修复动作。它会放大网络、registry 和凭证依赖,也可能影响同机其他项目。团队排障应分三组证据:
热缓存安装是否成功。清空项目 node_modules 后是否成功。使用隔离缓存目录的冷安装是否成功。
可以在临时环境指定独立缓存:
BUN_INSTALL_CACHE_DIR=.bun-cache-lab bun ciWindows PowerShell 使用 $env:BUN_INSTALL_CACHE_DIR='.bun-cache-lab'。实验后删除该临时目录,不要误删用户全局缓存或共享 CI cache。
CI 契约
CI 必须安装精确批准的 Bun 版本、打印版本证据、恢复依赖后再缓存。以官方 GitHub Action 为例:
- uses: oven-sh/setup-bun@v2
with:
bun-version: "<approved-version>"
- run: bun --version
- run: bun --revision
- run: bun ci
- run: bun run test
- run: bun run build<approved-version> 必须在合入前替换为团队批准的精确值。不要把 latest 或 canary 用作主分支基线。
缓存键至少包含操作系统、架构、Bun 版本和 bun.lock 摘要。缓存命中只证明文件可复用,不证明 registry 在凭证过期后仍可访问;保留定期冷缓存任务。跨平台项目还要在目标 OS / CPU 上恢复,因为 lockfile 可以跨平台稳定,而最终安装的可选或原生包仍可能不同。
从 npm、pnpm 或 Yarn 迁入
当项目没有 bun.lock 时,锁文件迁移说明列出的来源包括 package-lock.json、Yarn v1 yarn.lock 和 pnpm-lock.yaml,转换时会保留旧锁文件。自动转换只是候选结果,不是迁移结论。
迁移前保存:旧包管理器精确版本、旧 lockfile、配置文件、冷安装日志、依赖树、测试和构建产物摘要。然后在独立分支执行一次 bun install,审查新 bun.lock 和配置变化。
pnpm 迁移还可能把 workspace、catalog、override 和 patch 信息转入根 package.json;官方当前要求可迁移的 pnpm lockfile 版本不低于 7。转换后必须检查每个 workspace 名称、catalog 引用和 patch 是否仍有效。
停止迁移的条件包括:
依赖版本、来源或 peer 组合无法解释地变化。原生包安装脚本、补丁或 Git 依赖失效。isolated / hoisted 布局改变运行结果。
私有源、代理或 CA 只能靠关闭验证才能工作。Node.js 执行的测试、构建或产物与旧基线不一致。CI 冷恢复失败,只有开发机热缓存成功。
回滚时恢复旧 lockfile、旧配置和旧 CI 命令,删除 bun.lock 与 Bun 物化的 node_modules,用旧包管理器冻结恢复并重跑测试。不要同时保留两套权威 lockfile 等待“以后再清理”。
bun 命令不存在
先用绝对安装路径执行 bun --version,再检查 PATH 和终端是否重启。不要重复安装多个来源;Get-Command bun 或 command -v bun 应只指向批准路径。
bun ci 报 manifest 与 lockfile 不一致
这是冻结契约生效。检查 PR 是否只改了 package.json,或者是否用不同 Bun 版本重写 lockfile。由依赖变更负责人运行批准版 bun install 并提交 lockfile,不要在 CI 改用普通安装绕过。
安装成功但原生模块运行失败
检查包是否依赖 postinstall、是否被 trustedDependencies 漏掉、目标 OS / CPU 是否匹配,以及系统编译工具是否存在。不要直接信任所有脚本;只批准已审查包后重装并验证二进制。
isolated 模式出现模块找不到
先确认缺失包是否写进当前 workspace 的 dependencies 或 devDependencies。若补齐声明后恢复,说明旧项目依赖了幽灵依赖;若是工具不支持 symlink,再评估临时 hoisted,而不是用大范围 hoist 掩盖所有问题。
私有包 401 / 403
401 多数是凭证缺失、过期或未注入,403 多数是身份存在但 scope / 包权限不足。检查实际 registry、变量名、token 作用域和 CA,不在日志打印 token。用最小只读凭证验证后再回到项目。
解析到旧版本或刚发布版本不可见
区分 lockfile 固定、manifest 范围、registry 元数据缓存和镜像同步延迟。用 bun why、bun pm ls 和冷缓存验证;不要先执行 bun update --latest,那会同时改变解析问题和业务依赖。
试点 Bun 时至少明确四个角色:工具 owner 负责版本与安装来源,项目 owner 负责兼容和回滚,安全 owner 审查脚本与 registry,CI owner 负责冷恢复和缓存隔离。
仓库基线应包含:
唯一权威 bun.lock,迁移完成后删除旧 lockfile。显式 linker 选择及原因。可公开的 bunfig.toml 或 .npmrc 模板,无真实凭证。
install、test、build 的稳定脚本入口。trustedDependencies 审批记录。Bun 精确版本的开发机和 CI 分发方式。
升级窗口、停止条件和旧工具回滚步骤。
采用 Bun 的收益要用冷缓存安装时间、失败率、磁盘占用和维护工时衡量。一次个人机器的秒级安装不是架构决策证据。
“只换包管理器”仍会改变安全语义
Bun 对生命周期脚本的默认策略不同。现象可能不是安装报错,而是某个原生二进制直到运行时才缺失。迁移检查必须列出所有需要 install script 的包,并证明每个批准项确实执行、每个未批准项确实被阻止。
linker 默认值会随项目历史不同
两个仓库执行同一条 bun install,可能因为 lockfile configVersion、workspace 和迁移来源而得到不同布局。判断标准不能是“命令一样”,而应是 bun.lock、显式 linker 配置和 node_modules 结构一致。
hardlink 改变缓存与项目目录关系
Linux / Windows 上缓存包可能通过 hardlink 进入项目。安全工具、备份软件和磁盘统计可能把同一内容重复计算或误判;不要直接修改 node_modules 中第三方包。需要修补依赖时使用可审查 patch 机制并纳入 lockfile。
兼容 Node.js 不等于兼容全部生态
bun install 可以为 Node.js 项目生成 node_modules,但原生扩展、peer 组合、脚本 shell、文件监听和平台可选依赖仍需验证。采用决策应基于代表项目矩阵,不基于“官方说兼容”或简单样例。
远程源和缓存都属于数据边界
registry 请求会暴露包名、scope 和构建节奏;CI cache 可能包含内部包和路径。团队需要定义源白名单、凭证最小权限、缓存读写角色、保留期和泄漏后的撤销流程,而不是只追求缓存命中率。
已明确只采用 Bun package manager 时,不会默认迁移 runtime。Bun 二进制来自批准来源,版本与 revision 可追踪。bun.lock 是唯一权威 lockfile,并进入代码评审。
linker 已显式选择,并在干净目录验证。workspace、peer、原生扩展、patch 和生命周期脚本已覆盖代表项目。trustedDependencies 每一项都有必要性、owner 和复审记录。
registry、scope、代理和 CA 配置无真实凭证与个人绝对路径。CI 使用精确 Bun 版本和 bun ci,不会改写 lockfile。热缓存、冷缓存和目标平台恢复均有证据。
迁移停止条件、旧工具回滚和缓存清理路径已演练。
