启动 Bun
Bootstrapping Bun

原始链接: https://walters.app/blog/bootstrapping-bun

## 启动 Bun:无依赖构建 本文详细介绍了作者构建 Bun JavaScript 工具包,*无需*依赖预构建的 Bun 二进制文件的过程——这是一项具有挑战性的任务,因为 Bun 的构建过程本质上依赖于自身。由于在 OpenCode(一种 LLM 辅助编码工具)上安装 Bun 遇到困难,以及对自建包的信任需求,作者着手消除这种循环依赖。 核心挑战是用 npm、Node.js 和 esbuild 等替代方案分别取代 Bun 作为包管理器、TypeScript 运行时和打包器的角色。这涉及修改构建脚本、利用 CMake 选项进行工具选择,以及克服意外问题。这些问题包括 Zig 编译器崩溃(通过使用上游 Zig 版本和修补私有功能解决),以及导致运行时错误的微妙打包错误(通过更正文件后处理解决)。 最终,作者成功从源代码构建了一个可用的 Bun 二进制文件,从而可以使用 OpenCode。共享了由此产生的构建过程,并向 Bun 团队提供了补丁以供潜在的上游合并。作者强调了从源代码构建依赖项以及利用调试工具,而不是将其视为黑盒的价值。

黑客新闻 新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 启动 Bun (walters.app) 15 分,由 zerf 1小时前 | 隐藏 | 过去 | 收藏 | 2 评论 the__alchemist 10分钟前 | 下一个 [–] Bun 提醒!回复 lasgawe 24分钟前 | 上一个 [–] 很棒的文章回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请YC | 联系 搜索:
相关文章

原文

This article describes my journey building the Bun JavaScript toolkit without relying on any of its usual binary dependencies— namely itself.

It all started when I wanted to try OpenCode for LLM-assisted coding. I found that it wasn’t easy to install—my distro packaged neither OpenCode itself, nor Bun, the JavaScript runtime used by OpenCode. The aur didn’t come to the rescue either as the latest revision of the Bun package failed to build.

Note: Bun was packaged for Arch Linux on 2025-12-30 🎉! However Debian, Fedora, and Ubuntu still do not package it.

I also uncovered an unfortunate dependency: there was no clearly documented process for bootstrapping Bun; its build process unabashedly invokes the Bun CLI, assuming it was previously built. The development Dockerfiles and CI scripts for the Bun repo simply download the binary from a prior revision.

I’ll admit that I 99% trust binaries built by open source GitHub actions. However I completely trust packages built by myself or signed by my distro’s maintainers. I decided to pursue that incremental 1% (since maybe others could find it useful too).

Exploratory work

an RFC that I sent to the Bun maintainers detailing my end approach.

I concluded that Bun’s build scripts depend on Bun to fill three key roles:

  • As a package manager.
  • As a TypeScript runtime.
  • As a bundler.

In each case I found the most suitable replacement tools for the tasks.

(At some point I also ditched the wrapper script in favor of invoking replacement tools directly, modifying build scripts to invoke them directly or in place of Bun via CMake defines.)

Build-time package installation

shell out to bun install in its own directory if it detected that package dependencies were not yet installed. I replaced this with CMake orchestration to declare package installation as a dependency of this script in the build DAG.

Interpreter / type stripping

type stripping to similarly interpret TS. After a few minor syntax changes (primarily to replace declarations that were actually used at runtime with real values, e.g. declare unique symbolSymbol), this feature worked great.

Build-time bundling / transpiling

a fork of the Zig compiler, and I also knew that prebuilt binaries might lack debug info and may even fail subtly on a new machine.

To move forward, I created a side patch series (not part of my RFC submission) to enable defining a local Zig compiler to use for the build in a similar way to how local WebKit/JavaScriptCore is configured. This required CMake changes, but also some more interesting hacks:

With upstream Zig in place of the fork, I had to patch out any features that Bun’s Zig code had on private patches. In practice, there was only one such feature, but a big one. There’s a long-running feature request in the Zig issue tracker to add support for private struct fields. I’m not qualified to opine on that debate, but I can observe that the Bun team leans strongly in favor of the proposal given that they forked Zig, added this feature, and rely on it extensively in the Bun codebase. Reverting to upstream Zig required undoing this dependency, which was luckily possible with string substitution: I simply prefixed #private members as public _members.

One last puzzling change was a linker error. When assembling the final binary consisting of Zig and C++ object files, symbols couldn’t be found. nm on bun-zig.o showed that no symbols were exported, and furthermore the binary file was simply empty. I flailed here for a long time but what ultimately fixed the issue was building bun-zig.o as a static archive instead of an object file 🤷.

Since I opted to eliminate many variables at the same time, I’m unfortunately not sure what the problem was with the original binary (whether in the oven-sh patches or a binary incompatibility or something else). But I was unblocked; the build succeeded.

“Unexpected end of script”

using the creduce tool to produce a minimal reproduction of assert.js in the hopes that a problem in ~400 bytes would be more easy to eyeball than one in ~22,000.

In the end though, the winning debugging strategy was compiling WebKit’s JavaScriptCore from source and swapping it in place of the vendored binary (similarly to how I swapped in my own Zig above). Added debug logging revealed that the builtin in question was actually internal/util/inspect.js (which was prepended to, or perhaps a dependency of, assert.js).

The case was blown wide open when the logs showed that the postprocessing phase of builtin bundling erroneously appended }) to the end of the file with no preceding newline. For any bundled file which ended with a comment, this left the closing brackets commented out, hence the unexpected end of script.

Success

quick and dirty PKGBUILD and successfully used the resulting build to run OpenCode. Without further ado, here’s the bootstrapped Bun build command for use with my fork:

node ./scripts/build.mjs \
        -GNinja \
        -DCMAKE_BUILD_TYPE=Release \
        -B build/release \
        --log-level=NOTICE \
        -DBUN_EXECUTABLE="$(which node)" \
        -DNPM_EXECUTABLE="$(which npm)" \
        -DZIG_EXECUTABLE="$(which zig)" \
        -DENABLE_ASAN=OFF \
        -DCACHE_STRATEGY=none

In the end, the only regret I have from time spent on this project is some poor decisionmaking when using LLMs to assist me on the work. Overall, I’m happy to have achieved my goal.

A lesson to take away is that when you have the gift of an open source dependency, jump first to building it from source and using either a debugger or logs rather than treating it as a black box.

Upstreaming patches

an RFC to the Bun team to test the waters on whether these patches might be upstreamable. I tried to always choose the more maintainable option when faced with implementation decisions, so there is a chance. However I recognize that this is a big change to the build system and I won’t be disappointed if the answer is no.

In the mean time, feel free to check out my fork at bmwalters/bun and to try it for yourself.

联系我们 contact @ memedata.com