Bun 1.1 是一个让 JavaScript 开发更快、更简单的工具,相当于把 Node.js + npm + Webpack + Jest 等功能打包在一起,并且速度更快。

Bun 1.1 的核心特性:

  • 编译时特性标记和死代码消除
  • 运行时 Bun 特定 API (如 FFI)
  • 构建时源码转换和优化
  • 条件功能编译和模块加载

总共涉及 139+ 个文件,覆盖了 Claude Code 的所有主要功能模块

但用户可能没有 Bun,只有 Node.js,这个时候就需要构建脚本,把 "Bun 专有代码" 换为 "通用 Node.js 代码",为什么呢?因为这是Claude Code 原始设计                                

 • 用 Bun 编译(bun build --compile)                

 • 利用 Bun 的编译时特性优化产物大小                  

 • 使用 Bun 的运行时 API(bun:ffi 等)

注:FFI 是 JavaScript 和原生代码(C/C++等)之间的"桥梁",能在 JS 里直接调用用其他语言写的高性能函数。

阶段1脚本:prepare-src.mjs

这是一个源代码预处理脚本,在构建前修改原始源代码。

在理解这脚本之前,需要理解MACRO 对象,它是 Bun 提供的"编译时宏"系统,用于在打包阶段将某些标识符替换为具体的值,而不是在运行时计算,其实就类似于C/C++ 的 #define 宏定义,实现零运行时开销    值在编译时就确定了,不需要在代码里计算。

// 1. 替换 bun:bundle 导入
import { feature } from 'bun:bundle'
// ↓ 变成
import { feature } from '../stubs/bun-bundle.js'

// 2. 替换 MACRO 宏为实际值
MACRO.VERSION        → '2.1.88'
MACRO.BUILD_TIME     → '2026-04-01T09:43:...'
MACRO.FEEDBACK_CHANNEL → 'https://github.com/anthropics/claude-code/issues'
// 等等...

执行流程如下:

src/ 目录下的所有 .ts/.tsx 文件
    ↓
检查是否包含 bun:bundle 导入或 MACRO 引用
    ↓
替换并写回文件
    ↓
创建 stubs/ 目录下的填充文件(bun-ffi.ts, global.d.ts)

在 Claude Code 中,feature() 函数被广泛用于条件编译,这样的Bun编译时特性标记无处不在,而编译时常量渗透到各个层面.

新的文件可能也会使用 Bun 特性,需要自动包含在预处理范围内.

于是prepare-src.mjs 首先检查所有 .ts/.tsx 文件。

确保完整的 Bun-to-Node.js 兼容性转换,处理以下两类 Bun 特性:

  1. 编译时特性标记 (feature()) - 控制代码是否被包含在最终构建中
  2. 编译时常量宏 (MACRO.X) - 提供构建时确定的字符串值

接下来检查替换并写回之后创建的 Stub 存根文件,

这些存根文件是 Bun-to-Node.js 兼容性层 的关键组件:

  1. bun-bundle.ts - 提供运行时 API,让代码能正常执行
  2. global.d.ts - 提供类型安全,让 TypeScript 编译通过
  3. bun-ffi.ts - 提供缺失的 Bun 特定 API

它们确保 Claude Code 能在没有 Bun 运行时的环境中编译和运行,同时保持代码的完整性和类型安全性。

附:

构建系统的整体架构

原始 Bun 代码 (src/)
├── 使用 feature() 进行条件编译
├── 使用 MACRO.X 编译时常量
└── 使用 bun:ffi 等 Bun 特性

prepare-src.mjs 预处理
├── 创建 stubs/ 目录
├── 替换导入路径
└── 生成类型声明

build.mjs 构建
├── 进一步转换 feature() 调用
├── 替换 MACRO.X 为字面量
├── 移除 Bun 特定代码
└── 生成最终的 dist/cli.js

阶段2脚本:build.mjs 构建过程

这是主构建脚本,使用 esbuild 打包项目 即复制→转换→迭代打包→生成可执行文件

┌───────────────────────────────────────────────────────────
│  Phase 1: Copy source                                       
│  把 src/ 复制到 build-src/(保持原始文件不变)            
├───────────────────────────────────────────────────────────
│  Phase 2: Transform source                               
│  • feature('X') → false                                     
│  • MACRO.X → 字符串字面量                            
│  • 删除 bun:bundle 导入                                     
│  • 删除 global.d.ts 类型导入                              
├───────────────────────────────────────────────────────────
│  Phase 3: Create entry wrapper                           
│  创建入口文件 entry.ts,导入 CLI 入口                
├───────────────────────────────────────────────────────────
│  Phase 4: Iterative stub + bundle                    
│  循环最多 5 次:                                            
│    1. 运行 esbuild                                         
│    2. 解析错误,找出缺失的模块                       
│    3. 为缺失模块创建 stub                                
│    4. 重试,直到成功或达到最大轮数                      
└───────────────────────────────────────────────────────────

关键转换详解

1. feature() 函数的处理

原始代码(Bun 环境):

import { feature } from 'bun:bundle'

if (feature('EXPERIMENTAL_API')) {
  // 编译时决定是否包含这段代码
  enableExperimentalFeature()
}

Bun 的行为:

  • feature()编译时求值

  • 如果返回 false,这段代码会被死码消除(tree-shaking),不会出现在最终产物中

脚本转换后:

// feature() replaced with false at build time
if (false) {
  enableExperimentalFeature()
}

esbuild 会优化:

// 最终产物中这段代码完全消失

2. MACRO 宏的处理

console.log(`Version: ${MACRO.VERSION}`)
console.log(`Built at: ${MACRO.BUILD_TIME}`)

转换后:

console.log(`Version: ${'2.1.88'}`)
console.log(`Built at: ${''}`)  // 或实际构建时间

3. Stub 文件的作用

// Stub for bun:bundle — feature() is compile-time in Bun; replaced by build script
export function feature(_flag: string): boolean {
  return false
}

为什么返回 false

原因 说明
安全默认 未知特性默认关闭,避免启用实验性功能导致问题
编译时行为 在 Bun 中 feature() 是编译时确定的,这里模拟"特性未启用"的情况
简化处理 不需要实现复杂的特性检测逻辑

迭代式 Stub 生成(Phase 4 的核心)

这是最巧妙的设计——因为不知道代码会依赖哪些模块,所以用试错法

// 第 1 轮:运行 esbuild
// 报错:Could not resolve "./utils/helper"
// 创建 stubs/utils/helper.ts
// 
// 第 2 轮:运行 esbuild
// 报错:Could not resolve "./assets/config.json"
// 创建 stubs/assets/config.json(空 JSON)
//
// 第 3 轮:运行 esbuild
// 成功!

具体如下:

.txt, .md  —— 创建空文件

.ts, .tsx, .js   —— 创建导出空函数的模块

.json  ——创建 {}

总而言之,

这两构建脚本的作用:把 "Bun 专有代码" → "通用 Node.js 代码" 

  • 替换编译时特性为运行时兼容代码                    

  • 创建 stub 填补缺失的 API                       

  • 用 esbuild 替代 bun build

构建优化之树摇动:

未使用的特性代码在编译时被完全移除,树摇动是一种代码优化技术,用于自动删除未使用的代码,就像摇晃一棵树让枯叶掉落一样。

🌳 代码库就像一棵树
    ├── 主干(入口文件,一定会用到)
    ├── 树枝(被导入的模块)
    │   ├── 绿叶(实际使用的代码)✅ 保留
    │   └── 枯叶(从未使用的代码)❌ 删除
    └── 树根(依赖的第三方库)
        ├── 用到的函数 ✅ 保留
        └── 没用到的函数 ❌ 删除

术语来自 ES2015 模块系统(ESM) 的图形表示:

  • 导入/导出关系形成一棵依赖树

  • 构建工具"摇晃"这棵树,未引用的代码就像枯叶一样掉落

// ✅ 可以树摇动(ESM,静态导入)
import { usedFunction } from './utils.js'  // 明确知道用了什么

// ❌ 难以树摇动(CommonJS,动态)
const utils = require('./utils.js')  // 运行时才知道内容

关键区别:

  • ESMimport/export静态的,编译时就能确定依赖关系

  • CommonJSrequire动态的,运行时才能确定

在 Claude Code 的构建中,树摇动是关键优化手段——它确保了只有实际使用的代码才会进入最终产物,而那些被 feature() 控制的实验性功能则不会增加包体积,最终产物更小、启动更快、不包含无用功能。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐