> ## Documentation Index
> Fetch the complete documentation index at: https://bun.zhcndoc.com/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={"theme":{"light":"github-light","dark":"dracula"}}
bun build ./index.ts --target=bun --bytecode --outdir=./dist
```

这会生成两个文件：

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

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

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun ./dist/index.js  # 自动使用 index.jsc
```

### 生成独立可执行文件时

使用 `--compile` 创建可执行文件时，字节码会被嵌入到二进制文件中。支持 ESM 和 CommonJS 两种格式：

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# ESM (requires --compile)
bun build ./cli.ts --compile --bytecode --format=esm --outfile=mycli

# CommonJS (works with or without --compile)
bun build ./cli.ts --compile --bytecode --outfile=mycli
```

生成的可执行文件同时包含代码和字节码，在单文件中提供极致性能。

### ESM 字节码

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

如果没有 `--compile`，ESM 字节码仍然需要解析源代码以分析模块依赖关系——这就失去了字节码缓存的意义。

### 与其他优化结合

字节码支持与代码压缩和源码映射同时使用：

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
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 钩子）
* 启动时间即全部用户体验
* 用户能明显感受到 90ms 与 45ms 启动时间的差异
* 例子：TypeScript 编译器、Prettier、ESLint

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

* 在开发过程中执行数百次甚至数千次
* 每次节省数毫秒，累计提升明显
* 改善开发者体验
* 例子：构建脚本、测试运行器、代码生成器

#### 独立可执行文件

* 发布给重视性能的用户
* 单文件分发方便
* 文件体积不如启动速度重要
* 例子：通过 npm 或二进制发布的 CLI

### 不适合：

* ❌ **小型脚本**
* ❌ **只运行一次的代码**
* ❌ **开发构建**
* ❌ **受大小限制的环境**

## 限制

### 仅支持 CommonJS

字节码缓存目前仅支持 CommonJS 输出格式。Bun 打包器会自动将大部分 ESM 代码转换为 CommonJS，但 **顶层 await** 是例外：

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
// 会阻止字节码缓存
const data = await fetch("https://api.example.com");
export default data;
```

**原因**：顶层 await 需要异步模块评估，这无法用 CommonJS 表示。模块图异步化，CommonJS 包装函数模型失效。

**解决方案**：将异步初始化放入函数：

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
async function init() {
  const data = await fetch("https://api.example.com");
  return data;
}

export default init;
```

现在模块导出的是函数，由调用方根据需要使用 `await`。

### 版本兼容性

字节码**不跨 Bun 版本通用**。字节码格式绑定于 JavaScriptCore 内部表示，不同版本会改变。

更新 Bun 后必须重新生成字节码：

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# 更新 Bun 后
bun build --bytecode ./index.ts --outdir=./dist
```

如果字节码与当前 Bun 版本不匹配，它会被自动忽略，代码将回退为解析 JavaScript 源码。你的应用仍然可以运行——性能优化会被跳过。

**最佳实践**：将字节码生成纳入 CI/CD 构建流程，不要将 `.jsc` 文件提交到 Git。更新 Bun 时重新生成字节码。

### 仍需源码文件

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

运行时流程：

1. Bun 加载 `.js` 文件，发现 `@bytecode` 指令，检查 `.jsc` 文件
2. Bun 加载 `.jsc`
3. Bun 验证字节码哈希与源码匹配
4. 验证通过则使用字节码
5. 否则回退为解析源码

### 字节码不是混淆手段

字节码**不会隐藏你的源代码**。它是性能优化，而非安全措施。

## 生产部署

### Docker

在 Dockerfile 中集成字节码生成：

```dockerfile Dockerfile icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
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 \
  --outdir=./dist \
  --compile \
  ./src/server.ts --outfile=./dist/server

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

字节码文件与架构无关。

### CI/CD

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

```yaml workflow.yml icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
# GitHub Actions
- name: Build with bytecode
  run: |
    bun install
    bun build --bytecode --minify \
      --outdir=./dist \
      --target=bun \
      ./src/index.ts
```

## 调试

### 验证字节码是否生效

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

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
ls -lh dist/
```

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
-rw-r--r--  1 user  staff   245K  index.js
-rw-r--r--  1 user  staff   1.1M  index.jsc
```

`.jsc` 文件通常比 `.js` 大 2-8 倍。

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

成功时日志示例：

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[Disk cache] cache hit for sourceCode
```

若缓存未命中，日志示例：

```txt theme={"theme":{"light":"github-light","dark":"dracula"}}
[Disk cache] cache miss for sourceCode
```

多次缓存未命中正常，因为 Bun 目前不缓存内置模块的 JavaScript 字节码。

### 常见问题

**字节码被静默忽略**：通常是 Bun 版本更新导致的缓存版本不匹配，重新生成可解决。

**文件太大**：这是预期的。建议：

* 先用 `--minify` 减小代码体积再生成字节码
* 网络传输时压缩 `.jsc` 文件（gzip/brotli）
* 评估启动性能提升是否值得文件增大代价

**顶层 await**：不支持，请用异步初始化函数替代。

## 什么是字节码？

运行 JavaScript 时，JavaScript 引擎并不直接执行源代码，而是经过以下步骤：

1. **解析**：读取代码，生成抽象语法树（AST）
2. **字节码编译**：将 AST 编译为字节码，一种更低级但执行更快的中间表示
3. **执行**：引擎通过解释器或 JIT 编译器执行字节码

字节码介于源代码和机器码之间，可理解为虚拟机的汇编语言。每个字节码指令对应一条操作，如“加载变量”、“两个数字相加”或“调用函数”。

每次运行你的代码都会执行以上步骤。如果你的 CLI 每天运行 100 次，则代码被解析 100 次。如果是 serverless 函数发生频繁冷启动，解析在每次冷启动时都会进行。

字节码缓存将步骤 1 和 2 移到构建时。运行时直接加载预编译的字节码，加快启动。

### 为什么懒解析让字节码更有效

现代 JavaScript 引擎有种巧妙的优化叫**懒解析**，不会一开始解析所有代码，只有函数首次调用时才解析：

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
// 没有字节码缓存时：
function rarely_used() {
  // 这段 500 行的函数仅在调用时被解析
}

function main() {
  console.log("Starting app");
  // rarely_used() 从未调用，故无解析
}
```

解析开销不仅是启动时的成本，而是在应用运行期间根据不同路径陆续产生。字节码缓存则提前编译所有函数，包括本应懒解析的，那些解析工作一次性在构建时完成。

## 字节码格式

### .jsc 文件内部结构

`.jsc` 文件包含序列化的字节码结构。理解其内容能解释性能优势与文件增大间的权衡。

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

* **缓存版本**：与 JavaScriptCore 框架版本关联的哈希，确保字节码只在对应版本的 Bun 中运行。
* **代码块类型标签**：指示这是程序、模块、eval 还是函数代码块。

**SourceCodeKey**（验证字节码对应源码）：

* **源码哈希**：原始 JavaScript 代码的哈希，Bun 会校验匹配后才使用字节码。
* **源码长度**：源码精确长度，用于进一步验证。
* **编译标记**：如严格模式、脚本或模块类型、eval 类型等。不同标记下同一源码生成不同字节码。

**字节码指令**：

* **指令流**：实际的字节码操作码，表示 JavaScript 编译后代码，是可变长度序列。
* **元数据表**：每条操作码相关的元信息，如性能计数器、类型提示、执行次数（即使未填充）。
* **跳转目标**：针对控制流（if/else、循环、switch）预计算的地址。
* **Switch 表**：针对 switch 语句的优化查找表。

**常量与标识符**：

* **常量池**：代码中所有字面量，包括数字、字符串、布尔值、null、undefined。以 JavaScript 值（JSValue）存储，运行时无需解析源码来加载。
* **标识符表**：代码使用过的所有变量与函数名，采用去重字符串存储。
* **源码表示标记**：指示常量如何存储（整数、双精度、BigInt 等）。

**函数元数据**（每个函数对应）：

* **寄存器分配**：函数所需寄存器数，如 `thisRegister`、`scopeRegister`、`numVars`、`numCalleeLocals`、`numParameters`。
* **代码特性**：函数特性位掩码，如是否构造函数、箭头函数、使用 `super`、尾调用等，影响执行行为。
* **词法作用域特性**：严格模式及其他词法上下文。
* **解析模式**：函数被解析时的模式（普通、异步、生成器、异步生成器）。

**嵌套结构**：

* **函数声明和表达式**：每个内嵌函数含独立字节码块。文件包含 100 个函数即有 100 个嵌套字节码块。
* **异常处理**：try/catch/finally 边界及处理地址经过预计算。
* **表达式信息**：字节码位置映射回源码位置，方便报错及调试。

### 字节码不包含什么

重要的是，**字节码不包含你的源码文本**，而是：

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

因此需要同时发布 `.js` 和 `.jsc` 文件，缺一不可。

## 权衡：文件大小

字节码文件显著大于源码——一般比源码大 2-8 倍。

### 为什么字节码更大？

**字节码指令冗长**
一行压缩后的 JS 代码可能对应数十条字节码指令，例如：

```js theme={"theme":{"light":"github-light","dark":"dracula"}}
const sum = arr.reduce((a, b) => a + b, 0);
```

产生的字节码包括：

* 加载 `arr` 变量
* 取 `reduce` 属性
* 创建箭头函数（其本身含字节码）
* 加载初始值 0
* 设定调用参数数量
* 执行调用
* 结果赋值给 `sum`

每步都是独立操作指令附带元信息。

**常量池存储所有字面量**
例如字符串 `"hello"` 出现 100 次，常量池只存储一次，但标识符表和引用增加额外空间。

**每函数元数据**
即使是一行小函数，也有完整的元数据：

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

文件中有 1000 个小函数即有 1000 份这类数据。

**性能分析结构**
即使未填充，也有用来记录性能数据的结构：

* 值类型分析槽
* 数组访问分析槽
* 二元算术分析槽
* 一元算术分析槽

都占用空间。

**预计算控制流**
跳转目标、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` 文件头部包含缓存版本哈希。加载时：

1. 读取 `.jsc` 中缓存版本
2. 计算当前 JavaScriptCore 版本哈希
3. 不匹配时字节码**静默拒绝**
4. 回退解析 `.js` 源码

你的应用仍然可以运行——性能优化被跳过。

**降级设计**
字节码缓存对异常“开放失败”：出错时自动回退解析，无异常抛出，保证代码运行。

## 未链接与已链接字节码

JavaScriptCore 区分“未链接”和“已链接”字节码，这使字节码缓存成为可能。

### 未链接字节码（缓存内容）

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

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

不包含：

* 指向运行时对象的指针
* JIT 编译的机器码
* 运行时性能分析数据
* 调用链接信息（函数调用关系）

未链接字节码是**不可变且可共享的**，同一代码多次运行共用一份。

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

运行时，Bun 对字节码完成“链接”过程，生成运行时结构，附加：

* **调用链接信息**：优化函数调用路径
* **性能分析数据**：统计指令执行次数、类型流、数组模式等
* **JIT 编译状态**：基线 JIT 或优化 JIT（DFG/FTL）的代码
* **运行时对象**：JavaScript 对象、原型、作用域指针等

每次运行产生新链接副本，允许：

1. 缓存昂贵的解析编译（未链接字节码）
2. 收集运行时分析数据
3. 启用基于分析的 JIT 优化

字节码缓存将编译工作从运行时移至构建时，对于频繁启动的应用，可将启动时间减半，但换来更大的磁盘文件。

在生产 CLI 和无服务器部署中，结合 `--bytecode --minify --sourcemap` 可获得性能和调试性的平衡。

# 结束
