依赖管理进阶
本教程共 34 篇 · 第 14 篇 · 更新于 2026-08-06
本节目标:
- 理解 Bun 的全局缓存(
~/.bun/install/cache)如何复用已下载的包、用 hardlink / clonefile 加速并省磁盘。- 掌握生命周期脚本的安全模型:默认不执行
postinstall,以及如何用trustedDependencies精确放行。- 会用
overrides/resolutions强制”依赖的依赖”使用指定版本。- 学会用
bun patch给第三方包打补丁并纳入版本库,以及了解 isolated 安装与最小发布年龄等工程/安全细节。
14.1 全局缓存
Bun 把从 registry 下载过的每一个包版本都存进一个全局缓存,默认位于 ~/.bun/install/cache(也可用环境变量 BUN_INSTALL_CACHE_DIR 覆盖)。缓存目录里按 ${name}@${version} 命名子目录,因此同一个包的多个版本可以同时并存。
它的价值体现在两方面:
- 避免重复下载:安装时如果缓存里已经有
package.json范围内匹配的版本,Bun 直接复用,不再联网。 - 快速拷贝到 node_modules:下载进缓存后,Bun 仍要把文件”放”进项目的
node_modules。这一步它用平台最快的系统调用——Linux / Windows 上用 hardlink(硬链接),macOS 上用 clonefile(写时复制)。硬链接意味着包内容在磁盘上只有一份,多个项目共享,极大节省空间。
Note若 semver 版本带预发布后缀(如
1.0.0-beta.0)或构建后缀(如1.0.0+20220101),Bun 会用该值的哈希替换这部分字符串,以降低超长文件路径导致出错的概率。
缓存行为可在 bunfig.toml 里微调:
[install.cache]
dir = "~/.bun/install/cache" # 缓存目录
disable = false # 为 true 时不从全局缓存加载(仍可能写 node_modules/.cache)
disableManifest = false # 为 true 时总是向 registry 请求最新元数据
需要彻底清理缓存时:
bun pm cache rm
# 或等价地
rm -rf ~/.bun/install/cache
14.2 生命周期脚本与 trustedDependencies
npm 上的包可以在 package.json 里定义生命周期脚本,例如:
preinstall:安装前运行postinstall:安装后运行(常用于为原生插件下载/构建平台二进制,比如sharp、esbuild、node-sass)preuninstall/prepublishOnly等
这类脚本本质是”任意 shell 命令”。Bun 的设计取向是默认安全:它不会自动执行已安装依赖包的生命周期脚本(这一点和 npm/yarn/pnpm 的默认行为不同)。理由很直接——执行第三方任意代码是供应链攻击的高风险面。
Tip你自己项目的
{pre|post}install、{pre|post}prepare脚本依然会照常运行,被限制的只是”依赖带来的”脚本。
如果你确实需要某个依赖跑它的 postinstall,把它加入 trustedDependencies 白名单:
{
"name": "my-app",
"version": "1.0.0",
"trustedDependencies": ["node-sass"]
}
加入后重新安装,Bun 就会为该包运行生命周期脚本。Bun 还内置了一份常用可信包列表(针对 npm 来源的包默认放行,例如 sharp、esbuild 等),所以多数常见原生包开箱即用。
这里有个容易踩坑的细节:trustedDependencies 一旦显式定义,就会”替换”而非”追加”默认列表。三种情形如下:
package.json 状态 | 允许运行脚本的包 |
|---|---|
完全不写 trustedDependencies | 仅 Bun 内置列表(仅限 npm 来源) |
写了具体列表 ["pkg-a", ...] | 只有列表里的包,默认列表被忽略 |
写成空数组 [] | 任何包都不行(比 --ignore-scripts 更彻底) |
Warning内置可信列表只适用于从 npm 安装的包。对
file:、link:、git:、github:来源的依赖,即便名字恰好命中内置列表,也必须显式加进trustedDependencies——这是为了防止恶意包通过本地路径或 git 仓库”冒充”可信包名。
全局禁用的方式也有两种:命令行 --ignore-scripts,或在 bunfig.toml / .npmrc 里设置 install.ignoreScripts=true / ignore-scripts=true。
14.3 用 overrides / resolutions 锁定”依赖的依赖”
假设你只声明了一个直接依赖 foo,而 foo 又依赖 bar。bar 就是你的元依赖(metadependency)。若 bar@4.5.6 出现了安全漏洞,你想强制把它钉到更旧的 4.4.x,可以用 npm 的 overrides 或 Yarn 的 resolutions(Bun 两者都支持):
{
"name": "my-app",
"dependencies": {
"foo": "^2.0.0"
},
"overrides": {
"bar": "~4.4.0"
}
}
{
"name": "my-app",
"resolutions": {
"bar": "~4.4.0"
}
}
效果一致:无论 bar 是直接依赖还是某包的依赖,Bun 在解析版本时都会服从这里指定的范围。这在进行安全应急处理、或统一某深层依赖的版本时非常有用。
NoteBun 目前只支持顶层的
overrides/resolutions,不支持嵌套写法。
14.4 用 bun patch 给依赖打补丁
偶尔你需要对 node_modules 里某个包做一点小改动(修 bug、加特性)。直接改 node_modules 不行——下次安装就丢了。bun patch 提供了可持久化、对 git 友好的方案。
它的思路是:生成 .patch 文件,提交进你的仓库;之后每次 bun install 都会自动把这些补丁应用到对应依赖上,同时不破坏全局缓存的完整性。补丁文件还能跨项目、跨机器复用。
完整三步:
# 第 1 步:准备要打补丁的包(会生成一份"去链接"的副本,避免误改全局缓存)
bun patch react
# 也可指定版本或路径:bun patch react@17.0.2 / bun patch node_modules/react
# 第 2 步:直接编辑 node_modules/react 里的文件,本地验证效果
# 第 3 步:提交改动,生成 patches/ 下的补丁并更新 package.json 与锁文件
bun patch --commit react
# 指定补丁目录:bun patch --commit react --patches-dir=mypatches
提交后,package.json 里会通过 "patchedDependencies" 记录被打补丁的包,补丁文件落在 patches/。bun patch-commit 也提供,以兼容 pnpm 习惯。
Warning不要跳过
bun patch <pkg>这一步。它确保node_modules/中的包是一份”无 symlink / hardlink”的干净副本。若直接编辑,可能改到的是全局缓存里的包,污染其他项目。
14.5 安装策略:isolated 与 hoisted
第 11 章提到过,bun install --linker 有两种 node_modules 组织方式:
- hoisted(平铺):传统 npm/Yarn 风格,依赖被扁平化到共享的
node_modules,可能存在”幽灵依赖”(没声明却能import)。 - isolated(隔离):类似 pnpm,在
node_modules/.bun/建一个中央包仓库,顶层node_modules用符号链接指向它,包只能访问自己声明过的依赖。
默认策略:isolated 用于新建工作区/monorepo(防幽灵依赖),hoisted 用于新建单包项目(贴近 npm)以及 v1.3.2 之前创建的旧项目(保兼容)。底层由锁文件里的 configVersion 决定。
Tip文件拷贝的”后端”也可用
--backend强制指定:hardlink(Linux/Windows 默认)、clonefile(macOS 默认)、clonefile_each_dir、copyfile(兜底)、symlink(仅用于file:依赖)。正常情况下让 Bun 自动选最快的即可。
14.6 最小发布年龄(供应链防护)
为防范”刚发布就被塞入恶意代码”的供应链攻击,Bun 支持给 npm 包设置最小发布年龄:安装时会过滤掉”发布时间距今不足阈值”的版本。
# 只安装至少 3 天前发布的版本(259200 秒)
bun add @types/bun --minimum-release-age 259200
在 bunfig.toml 里配置更持久:
[install]
minimumReleaseAge = 259200 # 秒
minimumReleaseAgeExcludes = ["@types/node", "typescript"] # 对这些包豁免
启用后:只影响新解析的包;已存在于 bun.lock 的包保持不变;所有直接/间接依赖都会被过滤;若某版本被年龄门槛挡住,Bun 还会做稳定性检查——如果门槛外紧接着连续发布了多个版本(疑似快速打补丁的异常模式),它会把过滤范围扩大、挑选更成熟的旧版本。
14.7 小结
- 全局缓存
~/.bun/install/cache复用已下载包,配合 hardlink / clonefile 既快又省磁盘。 - 生命周期脚本默认不执行(默认安全),需要时用
trustedDependencies精确放行;注意显式列表会替换默认列表。 overrides/resolutions用于强制”依赖的依赖”版本,适合安全应急与版本统一。bun patch以 git 友好的.patch文件持久化修改第三方包;安装策略提供 isolated/hoisted 选择;最小发布年龄是额外的供应链防护。
下一章我们把 Bun 和 npm/yarn/pnpm 做一个整体对比,并汇总常见命令速查与 CI 加速技巧。