CodexBar 的 CQuickJS:在 SwiftPM 包中植入 quickjs-ng 最小可嵌入 JavaScript 引擎的完整指南
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
本篇技术指南围绕 CodexBar 仓库中的 Sources/CQuickJS/README.md 展开,系统讲解该项目如何在 Swift Package Manager(SwiftPM)C target 中 vendor(内置第三方源码)一个"最小可嵌入"的 quickjs-ng 引擎:包括版本与校验和溯源、文件清单与排除策略、Package.swift中的编译与链接配置、宿主层 C 封装(看门狗与宿主函数分派)、以及它在用户 Provider 插件执行与 TypeScript 转译中的真实用途。读完你既能独立复现该 vendor 流程,也能理解 CodexBar 插件运行时为何需要一个受控、可限制资源、可超时中断的嵌入式 JS 引擎。
一、CQuickJS 在 CodexBar 中的定位
CodexBar 是一个用于展示 OpenAI Codex 与 Claude Code 用量统计的菜单栏应用,其"用户 Provider 插件"机制允许以 JavaScript/TypeScript 编写自定义用量抓取逻辑。为了保证这类第三方代码运行在隔离、可限流、可被强制中断的沙箱内,而不是直接放进应用主进程,CodexBar 需要一个可嵌入、轻量、单线程友好的 JavaScript 引擎——这正是 CQuickJS 存在的理由。
从依赖关系可以清晰看到这一角色:Package.swift 定义了CQuickJS这个 C target,而CodexBarCoretarget 将其声明为首要依赖(Package.swift),随后CodexBarCore/Plugins目录下的QuickJSProviderPluginEngine、QuickJSTypeScriptTranspiler等 Swift 文件通过import CQuickJS直接调用其 C API。换言之,CQuickJS 是整个插件子系统的底层执行引擎,本身不携带任何业务逻辑。
二、vendored 内容与版本溯源:一个可校验的固定快照
CQuickJS 的 README 明确说明:它 vendor 的是quickjs-ng项目的v0.15.1发布版(2026 年 6 月 4 日)。为了让每一次构建都严格可复现,项目固定了两项溯源信息:
- 源码归档地址:quickjs-ng 官方发布的
v0.15.1tar.gz 归档; - SHA-256 校验和:
c4e813951b7c46845096a948e978c620b11ab4cf5fd622ca09c727ec31f42623。
该校验和同时被硬编码进 Scripts/regenerate-quickjs-vendor.sh(EXPECTED_SHA256变量),下载归档后会先校验再解包,任何与上游不一致的内容都会导致脚本失败,从源头杜绝"下载到被篡改的源码"。
文件清单:4 个翻译单元 + 14 个头文件 + 1 份许可证
README 给出的统计是:19 个 vendored 文件,共 2,694,082 字节。对照 Sources/CQuickJS 目录实际内容,这一统计对应:
- 4 个上游引擎翻译单元(.c):
quickjs.c(核心解释器)、dtoa.c(浮点字符串转换)、libregexp.c(正则表达式)、libunicode.c(Unicode 表与处理); - 14 个必需头文件:
quickjs.h、quickjs-atom.h、quickjs-opcode.h、quickjs-c-atomics.h、libregexp.h、libregexp-opcode.h、libunicode.h、libunicode-table.h、cutils.h、dtoa.h、list.h、builtin-array-fromasync.h、builtin-iterator-zip.h、builtin-iterator-zip-keyed.h; - 1 份上游 MIT 许可证(
LICENSE)。
说明:
Sources/CQuickJS目录下还有两个不属于这 19 个上游文件的本地文件——CQuickJSHost.c与include/CQuickJSHost.h,它们是 CodexBar 自研的宿主层封装(详见第四节)。
有意排除的部件
README 明确列出了故意不打包的内容:qjs/qjsc两个命令行工具、REPL(交互式解释器)、libc 模块(如os、std等内置模块)、官方示例、测试以及上游构建系统文件。这是"最小可嵌入"定位的直接体现:CodexBar 只关心引擎本身(runtime/context/eval/内存限制/中断钩子),不需要任何 CLI 外壳或构建脚本。
源码保持未修改
README 强调 "The sources are unmodified"——四个.c与 14 个头文件与上游v0.15.1逐字节一致(这也是check模式用cmp逐文件比对的基础,见第六节)。所有适配工作都被隔离在 SwiftPM 的编译定义与链接设置中,而不是直接改动上游源码,这样既便于升级,也便于审计"本地到底改了什么"。
三、SwiftPM 集成:C target 的编译与链接配置
Package.swift中CQuickJStarget 的完整定义如下(Package.swift):
.target( name: "CQuickJS", path: "Sources/CQuickJS", exclude: ["README.md", "LICENSE"], publicHeadersPath: "include", cSettings: [ .define("_GNU_SOURCE"), ], linkerSettings: [ .linkedLibrary("m", .when(platforms: [.linux])), ]),各配置项的作用:
path: "Sources/CQuickJS":指定源码根目录,SwiftPM 会编译该目录下的所有.c文件(即 4 个上游翻译单元加CQuickJSHost.c)。exclude: ["README.md", "LICENSE"]:README.md与LICENSE不属于编译单元,显式排除可避免 SwiftPM 尝试将其作为资源处理,同时保留文件在仓库中的可读性。publicHeadersPath: "include":声明Sources/CQuickJS/include为公开头文件目录,Swift 代码通过import CQuickJS即可看到quickjs.h等全部 14 个上游头文件以及本地的CQuickJSHost.h。cSettings: [.define("_GNU_SOURCE")]:为所有 C 编译单元定义_GNU_SOURCE宏,确保在 glibc 环境下暴露 GNU 扩展接口(如clock_gettime等),这是跨 macOS/Linux 构建兼容性的关键一环。linkerSettings: [.linkedLibrary("m", .when(platforms: [.linux]))]:仅在 Linux 上链接数学库libm。dtoa.c、quickjs.c等翻译单元依赖浮点数学函数,而 macOS 上这些符号由系统库提供,无需显式链接。
值得注意的细节是:该 target 属于无平台限制的通用 target(未用#if os(macOS)包裹),因此 CodexBar 的 Linux(glibc 与静态 musl)CLI 构建与 macOS 应用构建共用同一份引擎源码,这与仓库中 "Both glibc and static-musl CLI builds use this target" 的注释(Package.swift)相互印证。
四、宿主层扩展:CQuickJSHost 的看门狗与函数分派
纯上游引擎只能"执行脚本",无法满足 CodexBar 对超时控制和宿主能力注入的需求。为此仓库在Sources/CQuickJS内新增了两个文件:
- Sources/CQuickJS/include/CQuickJSHost.h:公开 C API 声明;
- Sources/CQuickJS/CQuickJSHost.c:实现。
4.1 看门狗(Watchdog):超时即中断
CQuickJSHost的核心是一套基于原子变量与JS_SetInterruptHandler的看门狗机制(CQuickJSHost.c):
CQJSWatchdogCreate/Destroy:创建/销毁看门狗对象,内部用calloc分配,包含一个回调指针、不透明上下文指针,以及两个_Atomic字段interrupted与deadline_nanoseconds;CQJSWatchdogInstall:通过JS_SetContextOpaque把看门狗挂到 context 上,再通过JS_SetInterruptHandler(runtime, CQJSInterruptHandler, watchdog)注册中断处理器;CQJSWatchdogArm(timeout_milliseconds):先清除中断标志,再用clock_gettime(CLOCK_MONOTONIC, ...)取单调时钟并累加出截止时刻,写入原子变量;- 中断处理器每次被 QuickJS 调用时,检查
interrupted标志或"当前时刻是否已过截止时刻",命中即返回 1 触发引擎中断; CQJSWatchdogInterrupt与CQJSWatchdogIsInterrupted:前者允许外部线程(如 Swift 侧)主动设置中断标志——这是引擎在串行工作线程之外唯一的线程安全逃生口;后者供 Swift 层在捕获异常时区分"超时"与"普通脚本错误"。
4.2 宿主函数分派:magic 编号桥接 Swift
为了让 Swift 能向 JS 暴露任意宿主函数,CQJSNewHostFunction使用 QuickJS 的JS_NewCFunctionMagic(CQuickJSHost.c)创建"魔法函数":每个函数绑定一个int32_t magic编号,所有函数共享同一个 C 分发器CQJSHostDispatcher(CQuickJSHost.c)。分发器从 context 的 opaque 指针取回看门狗,再把magic、argc、argv原样转交给 Swift 侧注册的回调。
头文件中还提供了一组轻量值操作:cqjs_undefined/cqjs_null/cqjs_bool/cqjs_dup_value/cqjs_free_value、类型判定(is_exception/is_undefined/is_null/is_string/is_number/is_object)以及cqjs_throw_error。头文件注释特别说明(CQuickJSHost.h):这些小写拼写的内联包装是为了避免 Clang 的 Swift importer 把CQJS当作可剥离的类型前缀,从而保证 Swift 侧 API 名称稳定。
五、引擎的真实用途:Provider 插件运行时与 TypeScript 转译
CQuickJS 不是孤立存在的 target,它的消费方集中在 Sources/CodexBarCore/Plugins 目录。理解这些消费方式,才能明白 README 中"最小可嵌入"的每一项取舍。
5.1 插件执行引擎:QuickJSProviderPluginEngine
Sources/CodexBarCore/Plugins/QuickJSProviderPluginEngine.swift 是核心消费方。它在专用的串行工作线程(QuickJSSerialWorker)上:
- 创建 runtime 与 context:
JS_NewRuntime()→JS_SetMemoryLimit(runtime, 64MB)→JS_SetMaxStackSize(runtime, ...)→JS_NewContext(runtime)(源码 L218-L229); - 将内存上限固定为64 MiB(
static let memoryLimitBytes = 64 * 1024 * 1024); - 依据"JS 栈预算必须远低于宿主原生栈"的原则,把 JS 栈限制设为工作线程栈的四分之一(下限 64 KiB,见
QuickJSRuntimeLimits.javaScriptStackLimitBytes,源码 L33-L35),避免在栈余量不足时抛 RangeError 反而导致宿主崩溃; - 创建并安装看门狗,在加载脚本与每次
fetchUsage执行前cqjs_watchdog_arm,超时后由 Swift 侧映射为ProviderPluginError.timedOut; - 通过
cqjs_new_host_function向 JS 注入 11 个宿主函数(QuickJSHostFunction枚举,源码 L8-L20):defineProvider、settingGet、http、cookieHeader、cacheGet、cacheSet、log、nextDailyReset、pct、amountFromPercent、isDetailLabel——涵盖配置读取、网络请求、浏览器 Cookie 桥接、进程内缓存、日志、时区重置计算与百分比换算; - 插件若返回 Promise,引擎会循环调用
JS_ExecutePendingJob驱动微任务队列直至 settle(源码 L429-L452),实现宿主侧同步等待异步 JS 结果。
仓库内置的 Provider 插件脚本位于 Sources/CodexBarCore/Resources/Plugins(如openai.js、openrouter.js、zai.js、deepgram.js等),它们正是运行在该引擎之上。
5.2 TypeScript 转译:Sucrase 跑在 QuickJS 里
Sources/CodexBarCore/Plugins/QuickJSTypeScriptTranspiler.swift 展示了 CQuickJS 的第二个用途:在引擎内加载打包好的Sucrase(Sources/CodexBarCore/Resources/Plugins/sucrase-3.35.1.min.js),把用户写的 TypeScript 插件源码即时转译为 JavaScript,再交给上述执行引擎运行。
转译过程同样套用看门狗超时保护,并刻意用带自定义stackSize(8 MiB)的Thread子类承载,因为 Dispatch 协程工作线程的原生栈可能小于 QuickJS 的 2 MiB 限制(QuickJSTypeScriptTranspiler.swift 源码注释 L32)。最终转译代码通过sucrase.transform(source, {transforms:['typescript']}).code求值获得(源码 L111-L113)。
六、可复现的 vendor 流程:check 与 write 两种模式
README 给出的运维入口是 Scripts/regenerate-quickjs-vendor.sh。该脚本以仓库根为基准,把整个"下载 → 校验 → 挑选 → 落盘"流程固化为两个模式:
校验模式(默认)
Scripts/regenerate-quickjs-vendor.sh check脚本会下载归档、比对 SHA-256(不匹配立即失败退出),解包后在临时目录中按固定清单挑选 4 个.c、14 个.h与LICENSE,然后用cmp逐文件与Sources/CQuickJS下已检入的文件比对。全部一致时输出:
ok: vendored quickjs-ng v0.15.1 matches c4e813951b7c46845096a948e978c620b11ab4cf5fd622ca09c727ec31f42623写入模式
Scripts/regenerate-quickjs-vendor.sh write删除旧文件并按同一清单重新落盘,输出wrote Sources/CQuickJS from quickjs-ng v0.15.1 (...)。由于write模式只覆盖清单内的文件,任何本地新增文件(如宿主层封装)都不会被误删;同时脚本对哈希与临时目录使用trap清理,保证失败时不留脏状态。
这一流程的价值在于:任何人在任何机器上都能独立验证"仓库里检入的引擎确实来自未被篡改的 quickjs-ng v0.15.1",也便于日后升级到新版本——只需改QUICKJS_VERSION与EXPECTED_SHA256两个变量。
七、测试与验证
仓库用测试锁定了 CQuickJS 引擎的关键行为边界:
- TestsPlugin/UserProviderPluginPortableTests.swift 验证 QuickJS 能执行打包的 Sucrase 转译、且超过堆上限的内存分配会被拒绝(
QuickJS rejects allocations beyond its heap cap); - TestsPlugin/ProviderPluginEngineBenchmarkTests.swift 对同一批插件在 JavaScriptCore 与 QuickJS 两种引擎上分别计时对比(输出
| Plugin | JavaScriptCore | QuickJS |表格),用于在 macOS 平台上做引擎性能回归观测。
此外,CodexBarCoretarget 在 macOS 平台还链接了系统 JavaScriptCore(Package.swift),说明 QuickJS 主要服务于 Linux 与需要严格资源限制的场景,macOS 上两套引擎并行存在并共享同一套插件协议——这也解释了为什么引擎边界(内存、栈、超时)的测试被单独抽出成可移植用例。
八、小结:一份"最小可嵌入"的工程范本
从 Sources/CQuickJS/README.md 的寥寥数行出发,可以看到 CodexBar 在引擎选型与 vendor 管理上的完整思路:
- 固定版本与校验和:锁死 quickjs-ng v0.15.1 与 SHA-256,构建可复现、内容可审计;
- 最小裁剪:只保留 4 个翻译单元 + 14 个头文件(19 个文件、约 2.6 MiB),剔除 CLI、REPL、libc 模块、示例、测试与构建系统;
- 零修改 + 配置外置:上游源码不改动,编译宏(
_GNU_SOURCE)与链接库(Linux 的libm)全部收敛在 Package.swift; - 宿主层补齐缺口:用
CQuickJSHost的看门狗与 magic 分派,把"超时中断"与"宿主函数注入"两大能力接到 Swift 侧; - 消费端落实边界:64 MiB 内存上限、按线程栈比例推导的 JS 栈上限、超时看门狗、串行工作线程约束,共同构成用户插件运行的受控沙箱;
- 脚本化再生成:
check/write双模式让任何人在任何平台都能验证或更新这份 vendor。
如果你需要在自有 Swift 包中嵌入一个轻量 JS 引擎(尤其是需要强超时控制、内存限制与 Linux 支持的场景),完全可以照此模式落地:以固定校验和的归档为唯一事实来源,把宿主扩展与上游源码物理隔离,再配一个可复现的再生成脚本——这正是 CQuickJS 这份 README 及其仓库实现给出的最直接的工程答案。
【免费下载链接】CodexBarShow usage stats for OpenAI Codex and Claude Code, without having to login.项目地址: https://gitcode.com/GitHub_Trending/co/CodexBar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考