9.1 什么是 FFI
FFI(Foreign Function Interface,外部函数接口)指"从一种语言调用另一种语言写的函数"——JavaScript 调 C 库就是典型场景。历史上 Node 靠 node-gyp 编译 C++ 扩展实现 FFI(如 better-sqlite3),开发和部署成本都高。
Bun 提供 bun:ffi 直接加载动态库(.so/.dylib/.dll),声明函数签名就能调——不用写 C++ 胶水层。
9.2 bun:ffi 基本用法
import { dlopen, FFIType, suffix } from "bun:ffi";
const libPath = `./libadd.${suffix}`; // dylib/so/dll,suffix 跨平台
const { symbols: lib } = dlopen(libPath, {
add: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
});
console.log(lib.add(40, 2)); // → 42
对应的 C 源:
// add.c
// 编译:cc -shared -o libadd.dylib -fPIC add.c
int add(int a, int b) {
return a + b;
}
9.3 支持的类型
- 整数
i8/u8/i16/u16/i32/u32/i64/u64- 浮点
f32/f64- 指针
ptr、cstring(C 字符串→JS string)- 复合
buffer(传 Uint8Array)、function(回调函数指针)- 特殊
void(无返回值)、bool
9.4 Buffer 传递(零拷贝)
// C:void hash(const uint8_t* data, size_t len, uint8_t* out)
const { symbols } = dlopen(path, {
hash: { args: [FFIType.ptr, FFIType.u64, FFIType.ptr], returns: FFIType.void },
});
const data = new TextEncoder().encode("hello");
const out = new Uint8Array(32);
symbols.hash(data, data.length, out);
JS 的 TypedArray 直接按指针传给 C——不复制一份。
9.5 cc / tcc 即时编译
Bun 内置了 TinyCC(tcc)——可以把一段 C 源码即时编译成可调用函数,开发阶段特别方便:
import { cc } from "bun:ffi";
const { symbols } = cc({
source: `
int add(int a, int b) { return a + b; }
`,
symbols: {
add: { args: ["int", "int"], returns: "int" },
},
});
console.log(symbols.add(1, 2));
9.6 性能:比 N-API 快
Bun 官方 benchmark 显示:FFI 调用单次开销 < 5ns,比 Node N-API 的 ~50ns 快一个数量级。这让"JS 热路径调用 C 函数"真正可行(例如做加密、图像处理热点)。
9.7 bun build --compile:单文件可执行
把源代码 + 所有依赖 + Bun 运行时本身,一起打进一个二进制文件:
$ bun build src/cli.ts --compile --outfile mycli
$ ./mycli hello
hello
产物是一个 ~50MB 的静态链接二进制——用户不用装 Bun,不用装 Node,直接运行。
分发内部工具、CLI、Electron-free 桌面助手、轻量 daemon。运行时启动 <20ms,比 pkg/nexe 的 Node 单文件更小更快。
9.8 交叉编译
在 macOS 上打 Linux / Windows 二进制:
$ bun build src/cli.ts --compile --target=bun-linux-x64 --outfile mycli-linux
$ bun build src/cli.ts --compile --target=bun-linux-arm64 --outfile mycli-linux-arm
$ bun build src/cli.ts --compile --target=bun-darwin-arm64 --outfile mycli-mac-arm
$ bun build src/cli.ts --compile --target=bun-windows-x64 --outfile mycli-win.exe
支持的 target:bun-linux-x64/bun-linux-arm64/bun-linux-x64-baseline(老 CPU)/bun-darwin-x64/bun-darwin-arm64/bun-windows-x64。
9.9 精简体积 --minify
$ bun build src/cli.ts --compile --minify --outfile mycli
同时启用 bytecode caching 可进一步加快冷启动:
$ bun build src/cli.ts --compile --bytecode --outfile mycli
9.10 打包静态资源
单文件里捆绑资源:通过 embed-file 的形式:
// src/cli.ts
import cssText from "./styles.css" with { type: "text" };
import logo from "./logo.png";
console.log(cssText.length);
$ bun build src/cli.ts --compile --outfile mycli
文件会被嵌进二进制,运行时自动解出。
9.11 FFI 实战:SQLite 加密扩展
import { dlopen, FFIType } from "bun:ffi";
const { symbols } = dlopen("./libsqlcipher.so", {
sqlite3_key: {
args: [FFIType.ptr, FFIType.cstring, FFIType.i32],
returns: FFIType.i32,
},
});
9.12 小结
bun:ffi让 JS 直接调 C 动态库——单次调用 < 5ns,比 N-API 快。cc()内嵌 tcc 即时编译 C 源码,开发超方便。bun build --compile打出单文件可执行——分发工具/CLI 的终极形态。- 跨平台:一个 Mac 命令打 Linux+Windows+macOS 产物。