最近我把OpenCode这个AI Agent项目从源码完整构建了一遍。起因挺朴素——官方的一键安装脚本在我这台Win11 + WSL2的机器上连续翻了两次车,第一次装完命令找不到,第二次装完运行起来又提示缺少运行环境。与其继续跟安装器搏斗,不如直接把仓库拉到本地自己编译,顺便验证我一直在琢磨的一个问题:同一个项目,用Bun构建和用Node构建,速度到底能差多少。
这篇文章适合三类人看:打算从源码安装OpenCode的开发者、对Agent框架感兴趣想看看它内部怎么组织的人,以及正在评估要不要把Bun引入日常构建流程的人。文章会完整记录OpenCode从clone到运行的构建链路,给出同一环境下的Bun和Node实测数据,最后把我踩过的几个坑逐个讲清楚。
1. 从一键脚本翻车到决定自己构建
先说结论:如果你在官网或者GitHub Releases页面能顺利下载到对应平台的安装包,并且装完一切正常,真没必要折腾源码构建。我这次完全是安装器被环境坑了,才被迫走上源码这条路。但走完之后我反而觉得,这趟折腾挺值的。
1.1 官方安装脚本在我机器上的翻车现场
我平时的主力环境是Win11宿主机加WSL2(Ubuntu 22.04),OpenCode这类AI Agent工具我习惯放在WSL里,因为它跟命令行生态、git、容器环境挨得近。官方一键安装脚本在Linux下一般很顺,但我在WSL里执行完之后,回到Windows自带的PowerShell执行opencode,发现命令完全不可用,报错信息如下:
code复制opencode : 无法将“opencode”项识别为 cmdlet、函数、脚本文件或可运行程序的名称
这个报错很经典。问题出在脚本装好的二进制放在WSL内部的 ~/.opencode/bin 下,Windows的PATH根本不知道这个路径的存在。我尝试用 bash -c "opencode --version" 又能跑通,说明WSL内部其实装好了,只是PowerShell没找到。后来我改用VSCode的WSL插件,直接在WSL终端里执行,倒也能运行,但总觉得别扭——每次要先进WSL,再手动加载环境,体验非常割裂。
1.2 源码构建到底让我得到了什么
折腾了一阵子之后,我决定不再和安装器搏斗,直接源码构建。这套路线换来了几个实打实的好处:
- 拿到主分支的最新改动能第一时间用上,包括新出的技能系统、模型接入调整等,不用等官方打包发布;
- 想改内置行为时可以顺着源码直接改,比如自定义Agent技能目录、调整系统提示词模板;
- 遇到报错时能看到具体是哪个模块抛出来的,排查问题的信息量比黑盒安装包大得多;
- 可以顺手在自己机器上做性能验证,也就是这次的Bun和Node速度对比。
当然代价也很明显:构建过程会暴露大量依赖兼容性问题,特别是当项目用了比较新的ESM规范和Option Chaining语法时,Node版本太低就会翻车。如果你只是想要一个开箱即用的工具,官方包已经足够;但如果你像我一样想搞明白这玩意儿到底怎么跑的,源码构建是必经之路。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手前的环境底牌:Node版本、nvm和Bun的进场方式
源码构建的第一步其实不是clone,而是把运行环境捋顺。OpenCode本身的代码是用TypeScript写的,构建期需要Node或者Bun来跑脚本。我的建议是两套都装上,这样对比才方便,遇到兼容性问题时也能快速切换验证。
2.1 Node侧:版本锁定的坑
版本选择上,我强烈建议直接上Node.js 20 LTS或者更高。原因很简单,OpenCode的源码用了现代ESM规范,很多依赖包也在用 import { promisify } from 'node:util' 这种显式导入方式。Node 16以下的版本对ESM的导出支持不完整,很容易出现那个经典报错:
code复制SyntaxError: The requested module 'node:util' does not provide an export named 'promisify'
这个报错表面看是模块导入问题,本质是Node版本太老,node:util 的ESM导出表里没有对应的具名导出。解决办法不是改依赖,而是直接升级Node。我这里用nvm管理多版本:
bash复制curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 20
nvm use 20
nvm alias default 20
nvm alias default 这步容易被忽略,但很重要。否则新开终端后node版本会退回系统默认,构建完的脚本再跑时又可能用错版本。我在第一次构建时就忘了设默认版本,导致后面排查了很久。
2.2 Bun侧:合理选择安装路径
Bun的安装比Node简单得多,官方一行命令:
bash复制curl -fsSL https://bun.sh/install | bash
装完后默认落在 ~/.bun,并把 ~/.bun/bin 加进PATH。确认安装成功直接看版本:
bash复制bun --version
我用的版本是1.1.x,功能稳定度已经很高了。Bun厉害在自带了三样东西:包管理器(bun install)、打包器(bun build)、测试运行器(bun test),所以用它来跑构建流程,等于把npm和一堆构建工具链一起替换掉了,省了不少事。
这里有一个被忽略的细节:在Windows上装Bun,它会把可执行文件放在 %USERPROFILE%\.bun\bin,PowerShell打开新窗口才能识别bun命令。如果你在WSL里使用,则需要单独在WSL里再装一遍,两边共享不了。我在实际测试中差点搞混,最后确认了WSL里的Bun和Windows侧的Bun是两套独立的东西,构建OpenCode必须要用WSL侧的那份。
2.3 被全局变量坑过的经验
构建大项目时,Node默认的内存上限(老版本大概1.5GB左右)可能不够用,常见做法是调大V8堆内存。但很多人直接把Linux下的写法搬到Windows上:
code复制node --max-old-space-size=4096
结果Windows的cmd或者PowerShell直接报“不是内部或外部命令”。这是因为Windows下必须把参数放进环境变量,而不是写在命令行里。正确的做法是:
bash复制# PowerShell
$env:NODE_OPTIONS="--max-old-space-size=4096"
# cmd
set NODE_OPTIONS=--max-old-space-size=4096
# bash / WSL
export NODE_OPTIONS="--max-old-space-size=4096"
设置完之后再执行构建命令,Node会自动读取这个环境变量。这个属于“不踩一次坑永远不会注意”的细节,先记在这里,后面遇到内存溢出时直接翻回去看。
3. Node构建全流程实录:从git clone到可执行文件
环境准备好之后就可以正式构建了。整个过程我会分成依赖安装、构建、链接三部分来讲,记录下每一步的关键输出和耗时。
3.1 依赖安装:npm install的漫长等待
先把仓库拉到本地:
bash复制git clone https://github.com/sst/opencode.git
cd opencode
简单说下仓库结构。OpenCode是典型的monorepo工程,里面会看到几个关键区域:
packages/:拆分出来的核心模块,Agent逻辑、CLI入口、编辑器集成都在各自的包里;apps/或client/:面向用户的应用层,比如桌面端、VSCode插件这些;skills/:内置Agent技能的定义目录,后期可以自定义。
克隆完成后先别急着跑构建,打开 package.json 看一下scripts区,确认项目用的包管理器。OpenCode这类新项目很多直接支持Bun,所以可能是 "packageManager": "bun@..." 这种字段。为了拿到Node侧基准数据,我第一次还是用npm:
bash复制npm install
这一步耗时最久,我的机器上跑了大约1分42秒。期间会拉几百个依赖包,网络状况直接影响时间。如果速度太慢,建议提前把registry切成速度更快的镜像源,或者直接用后面介绍的Bun install来提速。
3.2 构建命令拆解:typecheck、打包、产物落盘
依赖装完,看package.json里的scripts,典型的结构是这样的:
json复制"scripts": {
"build": "tsc --noEmit && bun run build:app",
"dev": "tsx watch src/index.ts",
"start": "node dist/index.js"
}
注意这里有个小细节:整个项目是multipackage结构,根目录的build脚本会调用各个子包的构建逻辑。有的子包用 tsc 做类型检查,有的用esbuild做打包,最终产物统一输出到各自的 dist/ 目录。
执行:
bash复制npm run build
根脚本会按依赖顺序逐个构建子包,期间如果有TypeScript类型错误会直接中断。我的实测耗时约1分26秒,输出信息里能看到每个子包的构建时间,包括compile、bundle这些阶段。
构建完成后,产物通常落在 packages/opencode/dist/ 之类的位置,里面会有入口JS文件和CLI壳。验证是否成功:
bash复制node packages/opencode/dist/index.js --version
这里值得多说一句:如果你看到类似“The agent execution provider did not respond in time”的报错,那不是构建问题,而是运行时与模型服务通信超时,属于Agent执行阶段的事情,别混为一谈。
3.3 首次运行与PATH链接
构建产物确认没问题后,把它暴露到全局。如果不做这步,每次都得用完整路径跑命令,很别扭。推荐用npm link:
bash复制cd packages/opencode
npm link
这时回到任意目录执行 opencode --version 就应该能用了。如果还是提示找不到命令,八成是npm全局bin目录不在PATH里:
bash复制npm prefix -g
查看全局根目录后,把 <全局根目录>/bin 加进PATH,重新打开终端即可。如果是在VSCode里用,我建议装一个官方提供的VSCode插件,插件能自动识别源码构建出来的CLI,体验比手动链接更顺。
到这里,Node环境的完整构建链路就跑通了。整个过程从clone到能运行,不算安装系统环境的时间,我这台机器大约是3分半钟左右。
4. 同一套源码换Bun跑:速度提升的数据和缘由
Node版本的流程跑通之后,我把node_modules整个删掉,换成Bun重新走了一遍同样的流程。这一遍不只是为了测速,还顺便验证了OpenCode的构建脚本对Bun的兼容程度。结论是:这个项目在Bun下跑得非常丝滑,速度还快了一大截。
4.1 依赖安装:一个肉眼可见的降维打击
先用Bun清掉Node的痕迹:
bash复制rm -rf node_modules package-lock.json
bun install
Bun安装依赖的机制跟npm差别很大。npm是串行解压每个包,然后逐个写入node_modules,而Bun会利用并发把多个包的下载和安装同时跑起来,再加上底层是Zig原生实现,启动开销极低。这次install只用了约41秒,比npm快了大约60%。
中间的输出也很直观。Bun会实时打印每个包的安装进度和当前并发数,这是我第一次用Bun时觉得“这才是现代包管理器该有的样子”的时刻。
不过提醒一下:bun install 生成的锁文件是 bun.lockb,和npm的 package-lock.json 不兼容。如果你跟其他人协作,最好统一指定包管理器,否则每次切换都会让git历史变得杂乱。
4.2 构建阶段:脚本能复用多少
接下来执行:
bash复制bun run build
需要说明的是,bun run 会先读package.json里的scripts,然后调用对应的shell命令。如果某个子包里的构建命令是 tsc --noEmit,Bun会继续调用tsc,并不会自己把tsc替代掉。构建期速度的提升主要来自三个方面:
- Bun自身进程启动更快,脚本调用开销低;
- 文件读写更高效,依赖的加载速度明显提升;
- 项目里如果直接用
bun build,它会走内置打包器,比调独立的打包器更快。
我的实测结果是:build阶段约54秒,比npm跑出来的1分26秒快了约37%。如果项目里大量使用Bun自带的 bun build 而不是独立的打包器,差距会更加夸张。
4.3 两次构建的实测对比表
下面这张表是我在同一台机器、同一份源码上,连续跑三遍取中间值的记录:
| 阶段 | npm耗时 | bun耗时 | 提升幅度 |
|---|---|---|---|
| 依赖安装 | 约102秒 | 约41秒 | 约60% |
| 构建打包 | 约86秒 | 约54秒 | 约37% |
| 整体流程 | 约188秒 | 约95秒 | 约49% |
测试环境是i5-12490F、32GB DDR4、NVMe固态、WSL2 Ubuntu 22.04。数字会因为网络和机器配置浮动,但整体趋势是稳定的:Bun在依赖安装阶段优势最大,构建阶段也有明显提升。
为什么依赖安装差距最大?因为npm的瓶颈主要在串行I/O和解压上,Bun把这两块都并发化了。构建阶段之所以没有拉开更大差距,是因为很多子包实际还是走的tsc/esbuild,Bun只是把调用开销省掉了,真正的重型工作在tsc身上。
5. 构建期最容易翻车的几个坑,逐个排查给你看
源码构建这条路我走了两遍,踩的坑比想象中多。这里把典型的几个问题列出来,每个都是实际遇到过的,直接给出根因和解决办法。
5.1 node:util模块报错:版本歧视
这个坑前面提过,属于最典型的Node版本问题。现象是执行构建或启动时直接抛:
code复制SyntaxError: The requested module 'node:util' does not provide an export named 'promisify'
根因是Node 16以下的ESM导出实现和现代依赖包的预期不匹配。说人话就是:依赖包用了 import { promisify } from 'node:util' 这种写法,但老版本Node在ESM下没有正确暴露这个具名导出。
解决办法很直接——升级Node到18以上,建议20 LTS。用nvm切换:
bash复制nvm install 20
nvm use 20
rm -rf node_modules
npm install
升级后重新构建,报错自然消失。以后如果再遇到某个依赖说“does not provide an export named”,第一反应就该是检查Node版本,而不是盯着代码纠结。
5.2 内存上限设置无效:Windows语法问题
构建OpenCode时如果项目规模变大,可能遇到内存溢出或 JavaScript heap out of memory。解决办法是设置V8堆上限,但Windows命令行和Linux语法不一样,直接写 node --max-old-space-size=4096 是没用的,cmd和PowerShell都会报“不是内部或外部命令”。
正确做法在前文2.3已经写过,这里再强调一次:Windows下要设置 NODE_OPTIONS 环境变量,而不是把参数拼在node命令后面。PowerShell用 $env:...,cmd用 set,WSL和Linux用 export。我建议在WSL里构建时直接在 ~/.bashrc 加上:
bash复制export NODE_OPTIONS="--max-old-space-size=4096"
一劳永逸,以后所有Node进程都会带上这个参数。
5.3 命令行找不到opencode:PATH惹的祸
构建成功但命令找不到,这个坑特别常见。现象分两种:
第一种是npm link之后依然提示“opencode不是可识别命令”,多半是npm全局bin目录不在PATH里。先执行 npm prefix -g 拿到全局根目录,再把 <prefix>/bin 加进PATH。
第二种是Windows的PowerShell不认WSL里的安装路径。比如你明明在WSL里装好了opencode,回到PowerShell执行却报无法识别。原因就是Windows PATH里压根没有WSL内部路径。解决方案:要么一直在WSL里使用,要么把 \\wsl$\Ubuntu\home\用户名\.opencode\bin 映射进Windows PATH。我最终选择在WSL里常驻,跟开发环境保持一致。
5.4 Bun的卸载:别傻傻删不完
有些读者可能会想,先装Bun试试,不行再卸。没问题,Bun卸载不算复杂,但很多人只删了命令,没删干净。
Bun的默认安装目录是 ~/.bun,卸载时:
bash复制rm -rf ~/.bun
然后编辑shell配置文件(~/.bashrc 或 ~/.zshrc),把里面和bun相关的 export PATH="$HOME/.bun/bin:$PATH" 这行删掉,再 source 一下。Windows下则是删除用户目录里的 .bun 文件夹,并清理系统PATH中的对应条目。
要注意的是,如果你用其他方式装过Bun,比如npm全局装过 @bun/cli,还需要单独卸载对应包,别只删目录。否则在某些环境下还是能跑出bun命令,造成“没删干净”的误解。
6. 构建完之后,我现在的日常使用姿势
源码构建不只是为了“能装”和“测速”,最大的价值是在这个过程中把工具本身的运行机制看透了。最后聊聊我现在的选择,以及一个很容易被忽略的Agent技能目录问题。
6.1 用Bun构建还是用Node构建:我的最终选择
两者没有绝对优劣,关键看使用场景。我的习惯是:
- 本地日常开发调试用Bun。依赖安装快,构建也快,改完代码能更快跑起来验证;
- 正式发布或CI环境用Node。CI里的Node生态更成熟,npm锁文件行为更确定,排查问题的资料也更多;
- 如果是团队协作开发,建议统一用Node,避免Bun和npm两套锁文件在git里打架。
双环境并存其实很舒服。源码目录里改东西时用 bun run dev 热更,修改技能后重载很快;跑正式任务或需要稳定输出时,切回Node构建好的版本,互不干扰。
另外提醒一点:同一个源码目录里,npm install 和 bun install 生成的 package-lock.json 和 bun.lockb 尽量不要混提进git,二选一作为主锁文件,否则每次切换安装器都会导致较大的diff。
6.2 Agent技能目录:源码构建最容易忽略的价值
构建完OpenCode后,建议去看一眼仓库里的skills目录。OpenCode这类Agent项目除了本身是一个CLI工具,还带一套技能机制,就是可以给Agent预设可复用的行为模板。这是它区别于普通终端命令行的核心所在。
技术圈里经常有人问“agent skill和MCP有什么区别”,简单说:skill是Agent自身的能力封装,解决“它懂怎么做”;MCP是Agent与外部工具通信的协议,解决“它能调什么”。两者配合起来,OpenCode才能完成从读取项目、执行命令到调用外部服务的完整闭环。
源码构建让你可以直接改内置的skill定义,甚至新增一套自己团队的技能模板。这个能力是官方二进制包给不了的,也是我认为整个折腾过程最有价值的产出。我现在就把自己常用的几个工作流做成了skill,放在源码目录里一起维护,效果非常好。
最后再分享一个实操小技巧:每次从主分支拉最新代码后,如果构建报一些诡异的模块错误,先别急着深挖源码,执行一遍 rm -rf node_modules 再重新安装依赖,多数问题都能消失。依赖树里某个包升级导致的不兼容,比你想的常见得多。
