> ## Documentation Index
> Fetch the complete documentation index at: https://bun.ll1025.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# 全局虚拟存储

> 包只安装一次。每个项目都链接到同一份副本。

你机器上的每个项目都依赖于相同的那几个重量级包——`typescript`、`next`、`webpack`、`@babel/*`、`react-dom`。没有共享存储，每个 `node_modules` 都有自己的副本，每次全新的 `bun install` 都要重新写入它们。

全局虚拟存储改变了这一点，实现**一次安装，处处链接**。包文件存在于一个共享缓存中；每个项目的 `node_modules` 是指向该缓存的一个薄层符号链接树。第二次检出、新的分支工作树、CI 工作空间——它们都指向磁盘上已有的副本。

结果是：热安装大约**快 7 倍**（每个包一个符号链接，而不是复制每个文件），而 `node_modules` 从每个项目数百兆字节缩减到几兆字节的链接。

## 启用

全局虚拟存储**默认关闭**。它仅适用于[隔离链接器](/pm/isolated-installs)；提升链接器不使用它。

要为项目启用它：

```toml title="bunfig.toml" icon="settings" theme={null}
[install]
linker = "isolated"
globalStore = true
```

或者通过环境变量逐次调用：

```bash terminal icon="terminal" theme={null}
BUN_INSTALL_GLOBAL_STORE=1 bun install --linker isolated
```

要明确选择退出（默认行为），设置 `globalStore = false` 或 `BUN_INSTALL_GLOBAL_STORE=0`。

## 为什么它很快

之前的隔离链接器在每次安装时为每个包调用 `clonefileat()`（macOS）或 `link()`/`copyfile()`（其他平台），即使包缓存是热的，唯一缺少的是 `node_modules`。对 macOS 上一个包含 1,400 个包的测试用例进行热安装分析显示，主线程在 `clonefileat` 中花费了 **95.4% 的时间**：

```
按栈顶排序（bun install --linker isolated, warm cache）：
    clonefileat        891 / 934 样本
    __openat           15
    __read_nocancel    10
```

APFS 上的 `clonefileat` 持有一个卷级的内核锁，因此将工作分散到更多线程上几乎没有帮助——八个线程仅将 2,830 个目录的克隆从 959 毫秒改善到 743 毫秒。解决方法是在热路径上完全不调用它。

使用全局存储，热路径是每个包一次 `access()`（检查全局条目是否存在）加上一次 `symlink()`（将项目指向它）。

## 基准测试

热 CI 安装——锁文件存在，包缓存热，`node_modules` 在运行之间删除——在一个包含 1,400 个包的 React/webpack/Babel/jest 测试用例上，Apple Silicon macOS，`hyperfine --warmup 3 --runs 10`：

|                                         | 墙钟时间         | 系统时间      | `clonefileat` | 总系统调用数    |
| --------------------------------------- | ------------ | --------- | ------------- | --------- |
| `--linker hoisted`                      | 823.9 毫秒     | 477 毫秒    | 1,387         | 7,857     |
| `--linker isolated`，`globalStore=false` | 840.9 毫秒     | 1,256 毫秒  | 1,387         | —         |
| **`--linker isolated`，全局存储**            | **124.8 毫秒** | **94 毫秒** | **0**         | **4,957** |

**比没有全局存储的同一链接器快 6.6 倍**，**比提升链接器快 6.6 倍**。

### 磁盘

相同测试用例的 `node_modules` 大小（`du -sh node_modules` on APFS；clonefile 副本是写时复制的，因此提升/每项目数字是\_逻辑大小\_——在不支持 CoW 的文件系统上，这也是物理大小）：

|                                         | 每个项目的 `node_modules` | 磁盘共享      |
| --------------------------------------- | -------------------- | --------- |
| `--linker hoisted`                      | 391 MB               | —         |
| `--linker isolated`，`globalStore=false` | 391 MB               | —         |
| **`--linker isolated`，全局存储**            | **约 5 MB 的符号链接**     | 391 MB 一次 |

将同一个项目克隆五次，差异大约是约 2 GB 的重复包文件对约 400 MB 总量。一个项目就达到收支平衡；之后每次额外的检出、分支工作树或 CI 工作空间都是免费的。

### 真实世界冷→热

在克隆的真实世界仓库上的冷到热计时（macOS arm64）：

| 项目                             | 包数      | 冷启动    | 热启动        |
| ------------------------------ | ------- | ------ | ---------- |
| cal.com                        | \~3,580 | 37.4 秒 | **4.7 秒**  |
| remix                          | \~1,750 | 23.1 秒 | **2.0 秒**  |
| excalidraw                     | \~1,332 | 5.9 秒  | **1.1 秒**  |
| hono                           | \~790   | 4.5 秒  | **1.3 秒**  |
| `next build` (create-next-app) | \~382   | 1.0 秒  | **0.35 秒** |

## 目录结构

磁盘布局相比于[隔离安装](/pm/isolated-installs#directory-structure)增加了一层间接性：

```bash tree layout icon="list-tree" theme={null}
~/.bun/install/cache/
├── react@18.3.1@@@1/                     # 包缓存（不变）
│   └── ...包文件...
└── links/                                # 全局虚拟存储
    └── react@18.3.1-5664d3cd670b3205/    # <存储路径>-<条目哈希>
        └── node_modules/
            ├── react/                    # 包文件（创建一次）
            ├── loose-envify -> ../../loose-envify@1.4.0-ea24…/node_modules/loose-envify
            └── .bin/
                └── ...

project/node_modules/
├── .bun/
│   ├── node_modules/                     # 隐藏的提升层（每个项目）
│   │   └── react -> ../react@18.3.1/node_modules/react
│   └── react@18.3.1 -> ~/.bun/install/cache/links/react@18.3.1-5664d3cd670b3205
└── react -> .bun/react@18.3.1/node_modules/react
```

16 位十六进制 `entry_hash` 后缀编码了条目的**已解析依赖闭包**：包自身的存储路径和 tarball 完整性，加上它链接到的每个依赖的哈希。将 `react@18.3.1` 解析为相同传递版本集的两个项目共享一个全局目录；将传递依赖解析为不同版本的项目会获得一个单独的全局条目，其依赖符号链接指向正确的兄弟版本。参与依赖循环的包共享一个在整个强连通分量上计算的哈希，因此该键独立于给定项目的依赖图首先到达哪个成员。

## 什么保留在项目本地

只有当条目可以安全共享时，它才会存在于全局存储中。当以下情况时，条目会回退到每个项目的 `node_modules/.bun/<storepath>/` 目录：

* 包通过 `bun patch` 应用了**补丁**——修补后的内容是项目特定的；
* 包被列在 **`trustedDependencies`** 中（或通过 `bun add --trust` 信任）——其生命周期脚本可能会修改安装目录，而通过项目符号链接运行的脚本会修改共享副本；
* 该包或它链接到的**任何**依赖是 `workspace:`、`file:` 或 `link:` 依赖——这些解析为其他项目无法看到的项目本地路径。

不符合资格会传播：如果 `your-app` 依赖于 `internal-utils`，而后者是一个工作空间包，那么 `internal-utils` 是项目本地的，链接到它的每个条目也是如此。在安装之间失去资格的条目（新修补的、新信任的）会与全局存储分离，并在下次安装时在项目本地重建；共享条目保持不变。

## 对等依赖

已解析的对等依赖（必需的和可选的）作为依赖符号链接折叠到每个全局条目中，并参与其哈希计算。Bun 会为那些仅在没有匹配 `peerDependencies` 条目的 `peerDependenciesMeta` 中列出名称的包合成一个隐式的 `"*"` 可选对等依赖（与 pnpm 和 yarn 一致），因此像 `webpack` 这样仅在 `peerDependenciesMeta` 中声明 `webpack-cli` 的包，当项目中安装了 `webpack-cli` 时，仍会获得一个 `webpack-cli` 符号链接。

## 权衡

### 幽灵依赖回退

当包位于项目的 `node_modules/.bun/` 下时，Node 的模块解析会在到达项目根之前向上遍历 `node_modules/.bun/node_modules/`——即隐藏的提升层。使用全局存储时，包的真实路径位于 `<cache>/links/`，因此该层不再位于从包内部开始的解析路径上。

在实践中，这仅影响**真正的幽灵依赖**：某个包对从未在 `dependencies`、`peerDependencies` 或 `peerDependenciesMeta` 中声明的东西执行 `require('helper')`。如果遇到此问题，请将辅助包添加到消费包的依赖中（正确的修复方法）或设置 `globalStore = false`。

[`publicHoistPattern`](/runtime/bunfig#install-publichoistpattern) 和 [`hoistPattern`](/runtime/bunfig#install-hoistpattern) 会提升到项目的 `node_modules` 中，全局存储内的包无法访问这些目录。它们对于从你自己的源代码解析提升的包仍然有效。

### `node_modules` 主要是符号链接

不跟随符号链接扫描 `node_modules` 的工具，或通过字符串相等比较文件路径的工具，可能表现不同。这与任何 pnpm 风格布局的注意事项相同。

### 磁盘使用

每个唯一的 `(package, version, resolved-dependency-set)` 三元组在 `<cache>/links/` 中获得一个目录。跨多个项目，这是一个巨大的净收益——磁盘上一份副本而不是每个检出一份——但随着新版本和新对等依赖组合的出现，存储确实会随时间增长。运行 `bun pm cache rm` 清除包括全局存储在内的缓存；下次安装仅会重新填充该项目所需的内容。

### 并发

多个 `bun install` 进程（并行 CI 作业、并发工作空间构建）可能竞相填充同一个全局条目。每个进程在一个私有的 `<entry>.tmp-<random>/` 暂存目录下构建整个条目——包文件、依赖符号链接、bin 链接——然后将其重命名为最终位置。重命名失败的进程看到 `EEXIST` 并丢弃其相同的暂存树；在构建过程中崩溃的写入者只留下一个未引用的暂存目录，下次安装会忽略它。因此，已发布的条目始终是完整的；没有单独的完整性标记。

## 相关文档

* [包管理器 > 隔离安装](/pm/isolated-installs) — 全局存储所基于的链接器
* [包管理器 > 全局缓存](/pm/global-cache) — 下载的包存储的位置
* [运行时 > bunfig](/runtime/bunfig#install-globalstore) — `bunfig.toml` 参考
