> ## 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.

# 字节码缓存

> 使用 Bun 打包器的字节码缓存加速 JavaScript 执行

字节码缓存是一种构建时优化，通过将 JavaScript 预编译为字节码来改善启动时间。例如，在启用字节码的情况下编译 TypeScript 的 `tsc` 时，启动时间提升了 **2 倍**。

## 用法

### 基本用法（CommonJS）

使用 `--bytecode` 标志启用字节码缓存。未指定 `--format` 时，输出格式默认为 CommonJS：

```bash terminal icon="terminal" theme={null}
bun build ./index.ts --target=bun --bytecode --outdir=./dist
```

构建会写入两个文件：

* `dist/index.js` - 打包后的 JavaScript（CommonJS）
* `dist/index.js.jsc` - 字节码缓存文件

运行时，Bun 会自动检测并使用 `.jsc` 文件：

```bash terminal icon="terminal" theme={null}
bun ./dist/index.js  # 自动使用 index.js.jsc
```

### 使用独立可执行文件

当你使用 `--compile` 创建可执行文件时，Bun 将字节码嵌入到二进制文件中。ESM 和 CommonJS 都支持：

```bash terminal icon="terminal" theme={null}
# ESM（需要 --compile）
bun build ./cli.ts --compile --bytecode --format=esm --outfile=mycli

# CommonJS（无论是否使用 --compile 都可以）
bun build ./cli.ts --compile --bytecode --outfile=mycli
```

生成的可执行文件同时包含代码和字节码。

### ESM 字节码

ESM 字节码需要 `--compile`，因为 Bun 将模块元数据（导入/导出信息）嵌入到编译后的二进制文件中。有了这些元数据，JavaScript 引擎在运行时可以完全跳过解析。

没有 `--compile` 时，ESM 字节码仍然需要解析源代码以分析模块依赖关系，这就违背了字节码缓存的目的。

### 与其他优化结合使用

将字节码与压缩和 source map 结合使用：

```bash terminal icon="terminal" theme={null}
bun build --compile --bytecode --minify --sourcemap ./cli.ts --outfile=mycli
```

* `--minify` 在生成字节码之前减少代码体积（更少的代码 -> 更少的字节码）
* `--sourcemap` 保留错误报告（错误仍然指向原始源代码）
* `--bytecode` 消除解析开销

## 性能影响

性能提升随你的代码库规模而扩展：

| 应用程序大小            | 典型启动提升    |
| ----------------- | --------- |
| 小型 CLI（\< 100 KB） | 快 1.5-2 倍 |
| 中大型应用（> 5 MB）     | 快 2.5-4 倍 |

越大的应用受益越多，因为它们有更多代码需要解析。

## 何时使用字节码

### 非常适合：

#### CLI 工具

* 频繁调用（linter、格式化工具、Git hooks）
* 启动时间就是整个用户体验
* 用户能察觉 90ms 和 45ms 启动的差异
* 示例：TypeScript 编译器、Prettier、ESLint

#### 构建工具和任务运行器

* 开发期间运行成百上千次
* 每次节省的毫秒数快速累积
* 改善开发者体验
* 示例：构建脚本、测试运行器、代码生成器

#### 独立可执行文件

* 分发给关心性能的用户
* 单文件分发很方便
* 文件大小不如启动时间重要
* 示例：通过 npm 或作为二进制分发的 CLI

### 跳过它，当：

* ❌ **小型脚本**
* ❌ **只运行一次的代码**
* ❌ **开发构建**
* ❌ **对大小敏感的环境**

## 限制

### 版本兼容性

字节码**在不同 Bun 版本之间不可移植**。字节码格式与 JavaScriptCore 的内部表示绑定，该表示在不同版本之间会变化。

当你更新 Bun 时，必须重新生成字节码：

```bash terminal icon="terminal" theme={null}
# 更新 Bun 后
bun build --bytecode ./index.ts --outdir=./dist
```

如果字节码与当前 Bun 版本不匹配，Bun 会忽略它并回退到解析 JavaScript 源代码。

**最佳实践**：在 CI/CD 构建过程中生成字节码。不要将 `.jsc` 文件提交到 git。每次更新 Bun 时重新生成它们。

### 仍然需要源代码

字节码不会替代你的 JavaScript。你必须同时部署两个文件：

* `.js` 文件（打包后的源代码）
* `.jsc` 文件（字节码缓存）

运行时：

1. Bun 加载 `.js` 文件，看到 `@bytecode` 编译指示，检查 `.jsc` 文件
2. Bun 加载 `.jsc` 文件
3. Bun 验证字节码哈希与源代码匹配
4. 如果有效，Bun 使用字节码
5. 如果无效，Bun 回退到解析源代码

### 字节码不是混淆

字节码**不会模糊你的源代码**。它是一种优化，而非安全措施。

## 生产部署

### Docker

在你的 Dockerfile 中包含字节码生成：

```dockerfile Dockerfile icon="docker" theme={null}
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

COPY . .
RUN bun build --bytecode --minify --sourcemap \
  --target=bun \
  --compile \
  ./src/server.ts --outfile=./dist/server

FROM oven/bun:1 AS runner
WORKDIR /app
COPY --from=builder /app/dist/server /app/server
CMD ["./server"]
```

字节码与架构无关。

### CI/CD

在构建流水线期间生成字节码：

```yaml workflow.yml icon="file-code" theme={null}
# GitHub Actions
- name: 使用字节码构建
  run: |
    bun install
    bun build --bytecode --minify \
      --outdir=./dist \
      --target=bun \
      ./src/index.ts
```

## 调试

### 验证正在使用字节码

检查 `.jsc` 文件是否存在：

```bash terminal icon="terminal" theme={null}
ls -lh dist/
```

```txt theme={null}
-rw-r--r--  1 user  staff   245K  index.js
-rw-r--r--  1 user  staff   1.1M  index.js.jsc
```

`.jsc` 文件应该比 `.js` 文件大 2-8 倍。

要记录是否使用了字节码，设置环境变量 `BUN_JSC_verboseDiskCache=1`。

缓存命中时，Bun 会记录：

```txt theme={null}
[Disk Cache] sourceCode 缓存命中
```

缓存未命中时：

```txt theme={null}
[Disk Cache] sourceCode 缓存未命中
```

几条缓存未命中的日志是正常的：Bun 不会对其内置模块中的 JavaScript 进行字节码缓存。

### 常见问题

**字节码被静默忽略**：通常是由 Bun 版本更新引起的。缓存版本不匹配，因此字节码被拒绝。重新生成即可修复。

**文件体积过大**：这是预期的。考虑以下方式：

* 使用 `--minify` 在字节码生成前减少代码体积
* 为网络传输压缩 `.jsc` 文件（gzip/brotli）
* 评估启动增益是否值得体积增加

## 什么是字节码？

当你运行 JavaScript 时，JavaScript 引擎不会直接执行你的源代码。相反，它会经历几个步骤：

1. **解析**：引擎读取你的 JavaScript 源代码并将其转换为抽象语法树（AST）
2. **字节码编译**：AST 被编译为字节码——一个较低层次的表示，执行速度更快
3. **执行**：字节码由引擎的解释器或 JIT 编译器执行

字节码是一种中间表示——它比 JavaScript 源代码层次更低，但比机器码层次更高。可以把它理解为虚拟机的汇编语言。每条字节码指令代表一个单一的操作，如"加载这个变量"、"将两个数相加"或"调用这个函数"。

所有这一切**每次**运行你的代码时都会发生。一个每天运行 100 次的 CLI 工具会被解析 100 次；一个 serverless 函数在每次冷启动时都会被解析。

有了字节码缓存，Bun 将步骤 1 和 2 移到了构建步骤。运行时，引擎加载预编译的字节码并直接进入执行阶段。

### 为什么延迟解析让它变得更好

现代 JavaScript 引擎使用一种称为**延迟解析**的优化。它们不会一次性解析所有代码——相反，函数只在首次被调用时才被解析：

```js theme={null}
// 没有字节码缓存时：
function rarely_used() {
  // 这个 500 行的函数只在实际被调用时才会解析
}

function main() {
  console.log("启动应用");
  // rarely_used() 从未被调用，因此从未被解析
}
```

这意味着解析开销不仅仅是启动时的成本——它在应用程序的整个生命周期中，当不同的代码路径被执行时都会发生。有了字节码缓存，**所有函数都被预编译**，即使是引擎原本会延迟解析的那些函数。

## 字节码格式

### .jsc 文件内部

`.jsc` 文件包含序列化的字节码结构。

**头部部分**（每次加载时验证）：

* **缓存版本**：与 JavaScriptCore 框架版本绑定的哈希值。这确保一个 Bun 版本生成的字节码只能在该精确版本上运行。
* **代码块类型标签**：标识这是 Program、Module、Eval 还是 Function 代码块。

**SourceCodeKey**（验证字节码与源代码匹配）：

* **源代码哈希**：原始 JavaScript 源代码的哈希值。Bun 在使用字节码前验证其匹配。
* **源代码长度**：源代码的确切长度，用于额外验证。
* **编译标志**：编译上下文，如严格模式、脚本 vs 模块、eval 上下文类型。相同的源代码使用不同的标志编译会产生不同的字节码。

**字节码指令**：

* **指令流**：字节码操作码——你的 JavaScript 的编译表示，以变长指令序列存储。
* **元数据表**：每个操作码关联的元数据，如性能计数器、类型提示和执行计数（即使尚未填充）。
* **跳转目标**：控制流的预计算地址（if/else、循环、switch 语句）。
* **Switch 表**：switch 语句的优化查找表。

**常量和标识符**：

* **常量池**：代码中的所有字面值——数字、字符串、布尔值、null、undefined。它们存储为 JavaScript 值（JSValues），这样就不需要在运行时从源代码解析。
* **标识符表**：代码中使用的所有变量和函数名。存储为去重后的字符串。
* **源代码表示标记**：指示常量应如何表示的标志（整数、双精度浮点数、大整数等）。

**函数元数据**（代码中的每个函数）：

* **寄存器分配**：函数需要的寄存器（局部变量）数量——`thisRegister`、`scopeRegister`、`numVars`、`numCalleeLocals`、`numParameters`。
* **代码特性**：函数特征的位掩码：它是构造函数吗？箭头函数？是否使用 `super`？是否有尾调用？这些影响函数的执行方式。
* **词法作用域特性**：严格模式和其他词法上下文。
* **解析模式**：函数被解析的模式（普通、async、generator、async generator）。

**嵌套结构**：

* **函数声明和表达式**：每个嵌套函数递归获得自己的字节码块。一个有 100 个函数的文件有 100 个独立的字节码块，全部嵌套在结构中。
* **异常处理器**：try/catch/finally 块及其预计算的边界和处理程序地址。
* **表达式信息**：将字节码位置映射回源代码位置，用于错误报告和调试。

### 字节码中不包含什么

**字节码不嵌入你的源代码**。相反：

* JavaScript 源代码单独存储（在 `.js` 文件中）
* 字节码只存储源代码的哈希值和长度
* 加载时，Bun 验证字节码与当前源代码匹配

这就是为什么你需要同时部署 `.js` 和 `.jsc` 文件：没有对应的 `.js` 文件，`.jsc` 文件是无用的。

## 权衡：文件大小

字节码文件通常比源代码大 2-8 倍。

### 为什么字节码要大得多？

**字节码指令很冗长**：
一行压缩后的 JavaScript 可能编译成数十条字节码指令。例如：

```js theme={null}
const sum = arr.reduce((a, b) => a + b, 0);
```

编译成字节码需要：

* 加载 `arr` 变量
* 获取 `reduce` 属性
* 创建箭头函数（其本身也有字节码）
* 加载初始值 `0`
* 使用正确数量的参数设置调用
* 实际执行调用
* 将结果存储到 `sum` 中

这些步骤中的每一步都是一条独立的字节码指令，带有自己的元数据。

**常量池存储所有内容**：
每个字符串字面量、数字、属性名——所有内容都存储在常量池中。即使你的源代码有 `"hello"` 一百次，常量池也只存储一次，但标识符表和常量引用会增加开销。

**每个函数的元数据**：
每个函数——即使是一行的小函数——都有自己完整的元数据：

* 寄存器分配信息
* 代码特性位掩码
* 解析模式
* 异常处理器
* 用于调试的表达式信息

一个有 1,000 个小函数的文件就有 1,000 组元数据。

**性能分析数据结构**：
即使性能分析数据尚未填充，承载这些数据的\_结构\_已经被分配。这包括：

* 值分析槽（跟踪每个操作流经的类型）
* 数组分析槽（跟踪数组访问模式）
* 二元算术分析槽（跟踪数学运算中的数字类型）
* 一元算术分析槽

即使是空的，它们也占用空间。

**预计算的控制流**：
跳转目标、switch 表和异常处理程序边界都被预计算并存储。这使得执行更快，但增加了文件大小。

### 缓解策略

**压缩**：
字节码用 gzip/brotli 压缩效果很好（60-70% 压缩率）。重复的结构和元数据能被高效压缩。

**先压缩**：
在生成字节码之前使用 `--minify` 有助于：

* 更短的标识符 → 更小的标识符表
* 死代码消除 → 生成更少的字节码
* 常量折叠 → 池中更少的常量

**权衡**：
你是在用 2-4 倍的文件大小换取 2-4 倍的启动速度。对于 CLI 来说，这通常是值得的。对于数兆字节磁盘空间不重要的长时间运行的服务器来说，这就更不是问题了。

## 版本控制与可移植性

### 跨架构可移植性：✅

字节码是**架构无关的**。你可以：

* 在 macOS ARM64 上构建，部署到 Linux x64
* 在 Linux x64 上构建，部署到 AWS Lambda ARM64
* 在 Windows x64 上构建，部署到 macOS ARM64

字节码包含在任何架构上都能工作的抽象指令。架构特定的优化在运行时的 JIT 编译中发生，而不是在缓存的字节码中。

### 跨版本可移植性：❌

字节码在不同 Bun 版本之间**不稳定**。原因如下：

**字节码格式变化**：
JavaScriptCore 的字节码格式在各个版本之间会变化。新的操作码被添加，旧的被移除或改变，元数据结构也会变化。

**版本验证**：
`.jsc` 文件头中的缓存版本是 JavaScriptCore 框架的哈希值。当 Bun 加载字节码时：

1. 它从 `.jsc` 文件中提取缓存版本
2. 它计算当前的 JavaScriptCore 版本
3. 如果它们不匹配，字节码会被**静默拒绝**
4. Bun 回退到解析 `.js` 源代码

**优雅降级**：
这种设计意味着字节码缓存"故障打开"——如果出现问题（版本不匹配、文件损坏、文件丢失），你的代码仍然正常运行。你可能会看到启动变慢，但不会看到错误。

## 未链接 vs 链接字节码

JavaScriptCore 区分"未链接"和"链接"字节码。这种分离使得字节码缓存成为可能：

### 未链接字节码（被缓存的）

保存在 `.jsc` 文件中的字节码是**未链接字节码**。它包含：

* 编译后的字节码指令
* 代码的结构信息
* 常量和标识符
* 控制流信息

但它**不包含**：

* 指向实际运行时对象的指针
* JIT 编译后的机器码
* 之前运行时的性能分析数据
* 调用链接信息（哪个函数调用了哪个）

未链接字节码是**不可变且可共享的**。同一代码的多次执行都可以引用相同的未链接字节码。

### 链接字节码（运行时执行）

当 Bun 运行字节码时，它会"链接"它——创建一个运行时包装器，添加：

* **调用链接信息**：随着代码运行，引擎会了解哪些函数互相调用，并优化这些调用点。
* **性能分析数据**：引擎跟踪每条指令执行了多少次、流经代码的值类型、数组访问模式等。
* **JIT 编译状态**：对热点代码的 baseline JIT 或优化 JIT（DFG/FTL）编译版本的引用。
* **运行时对象**：指向实际 JavaScript 对象、原型、作用域等的指针。

这种链接表示每次运行代码时都会重新创建。这种分离允许：

1. **缓存昂贵的工作**（解析和编译为未链接字节码）
2. **仍然收集运行时性能分析数据**以指导优化
3. **仍然应用 JIT 优化**基于实际的执行模式

对于生产环境的 CLI 和 serverless 部署，`--bytecode --minify --sourcemap` 的组合为你提供了最佳的启动时间，同时保持错误映射到原始源代码。
