news 2026/9/13 7:11:20

CodexBar 的 CQuickJS:在 SwiftPM 包中植入 quickjs-ng 最小可嵌入 JavaScript 引擎的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CodexBar 的 CQuickJS:在 SwiftPM 包中植入 quickjs-ng 最小可嵌入 JavaScript 引擎的完整指南

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目录下的QuickJSProviderPluginEngineQuickJSTypeScriptTranspiler等 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.hquickjs-atom.hquickjs-opcode.hquickjs-c-atomics.hlibregexp.hlibregexp-opcode.hlibunicode.hlibunicode-table.hcutils.hdtoa.hlist.hbuiltin-array-fromasync.hbuiltin-iterator-zip.hbuiltin-iterator-zip-keyed.h
  • 1 份上游 MIT 许可证LICENSE)。

说明:Sources/CQuickJS目录下还有两个不属于这 19 个上游文件的本地文件——CQuickJSHost.cinclude/CQuickJSHost.h,它们是 CodexBar 自研的宿主层封装(详见第四节)。

有意排除的部件

README 明确列出了故意不打包的内容:qjs/qjsc两个命令行工具、REPL(交互式解释器)、libc 模块(如osstd等内置模块)、官方示例、测试以及上游构建系统文件。这是"最小可嵌入"定位的直接体现:CodexBar 只关心引擎本身(runtime/context/eval/内存限制/中断钩子),不需要任何 CLI 外壳或构建脚本。

源码保持未修改

README 强调 "The sources are unmodified"——四个.c与 14 个头文件与上游v0.15.1逐字节一致(这也是check模式用cmp逐文件比对的基础,见第六节)。所有适配工作都被隔离在 SwiftPM 的编译定义与链接设置中,而不是直接改动上游源码,这样既便于升级,也便于审计"本地到底改了什么"。

三、SwiftPM 集成:C target 的编译与链接配置

Package.swiftCQuickJStarget 的完整定义如下(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.mdLICENSE不属于编译单元,显式排除可避免 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 上链接数学库libmdtoa.cquickjs.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字段interrupteddeadline_nanoseconds
  • CQJSWatchdogInstall:通过JS_SetContextOpaque把看门狗挂到 context 上,再通过JS_SetInterruptHandler(runtime, CQJSInterruptHandler, watchdog)注册中断处理器;
  • CQJSWatchdogArm(timeout_milliseconds):先清除中断标志,再用clock_gettime(CLOCK_MONOTONIC, ...)取单调时钟并累加出截止时刻,写入原子变量;
  • 中断处理器每次被 QuickJS 调用时,检查interrupted标志或"当前时刻是否已过截止时刻",命中即返回 1 触发引擎中断;
  • CQJSWatchdogInterruptCQJSWatchdogIsInterrupted:前者允许外部线程(如 Swift 侧)主动设置中断标志——这是引擎在串行工作线程之外唯一的线程安全逃生口;后者供 Swift 层在捕获异常时区分"超时"与"普通脚本错误"。

4.2 宿主函数分派:magic 编号桥接 Swift

为了让 Swift 能向 JS 暴露任意宿主函数,CQJSNewHostFunction使用 QuickJS 的JS_NewCFunctionMagic(CQuickJSHost.c)创建"魔法函数":每个函数绑定一个int32_t magic编号,所有函数共享同一个 C 分发器CQJSHostDispatcher(CQuickJSHost.c)。分发器从 context 的 opaque 指针取回看门狗,再把magicargcargv原样转交给 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 MiBstatic 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):defineProvidersettingGethttpcookieHeadercacheGetcacheSetlognextDailyResetpctamountFromPercentisDetailLabel——涵盖配置读取、网络请求、浏览器 Cookie 桥接、进程内缓存、日志、时区重置计算与百分比换算;
  • 插件若返回 Promise,引擎会循环调用JS_ExecutePendingJob驱动微任务队列直至 settle(源码 L429-L452),实现宿主侧同步等待异步 JS 结果。

仓库内置的 Provider 插件脚本位于 Sources/CodexBarCore/Resources/Plugins(如openai.jsopenrouter.jszai.jsdeepgram.js等),它们正是运行在该引擎之上。

5.2 TypeScript 转译:Sucrase 跑在 QuickJS 里

Sources/CodexBarCore/Plugins/QuickJSTypeScriptTranspiler.swift 展示了 CQuickJS 的第二个用途:在引擎内加载打包好的SucraseSources/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 个.hLICENSE,然后用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_VERSIONEXPECTED_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 管理上的完整思路:

  1. 固定版本与校验和:锁死 quickjs-ng v0.15.1 与 SHA-256,构建可复现、内容可审计;
  2. 最小裁剪:只保留 4 个翻译单元 + 14 个头文件(19 个文件、约 2.6 MiB),剔除 CLI、REPL、libc 模块、示例、测试与构建系统;
  3. 零修改 + 配置外置:上游源码不改动,编译宏(_GNU_SOURCE)与链接库(Linux 的libm)全部收敛在 Package.swift;
  4. 宿主层补齐缺口:用CQuickJSHost的看门狗与 magic 分派,把"超时中断"与"宿主函数注入"两大能力接到 Swift 侧;
  5. 消费端落实边界:64 MiB 内存上限、按线程栈比例推导的 JS 栈上限、超时看门狗、串行工作线程约束,共同构成用户插件运行的受控沙箱;
  6. 脚本化再生成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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 7:08:37

模型量化实战:从浮点到INT8的系统性重构与RKNN避坑指南

1. 为什么你训练完的模型在树莓派上跑不动?——量化不是“压缩”,而是重新设计计算契约 我第一次把PyTorch训好的ResNet-50模型塞进RK3399开发板时,满心期待能实时跑通目标检测。结果呢?GPU内存直接爆掉,推理一帧要47秒…

作者头像 李华
网站建设 2026/9/13 7:07:56

垂直行业切入实战:制造企业工艺流程图抽取的需求验证全过程

垂直行业切入实战:制造企业工艺流程图抽取的需求验证全过程在寻找产品市场契合点(PMF)的探索中,很多 AI 创业团队容易陷入“做通用水平工具(Horizontal Tools)”的执念中,总想做一个能同时搞定合…

作者头像 李华
网站建设 2026/9/13 7:03:05

学术写作AI工具对比:千笔与知文AI功能评测

1. 项目概述:学术写作AI工具横评去年帮表弟改毕业论文时,我意外发现现在专科生写论文已经用上了专业AI工具。作为在学术期刊工作过五年的编辑,我花了三周时间深度测试了市面上两款热门学术写作工具——千笔专业学术智能体和知文AI。这两款工具…

作者头像 李华
网站建设 2026/9/13 7:01:21

Python if语句详解:从基础语法到高级应用

1. Python分支语句if的核心价值与应用场景在编程世界中,流程控制就像交通信号灯指挥车辆行驶一样,决定了代码的执行路径。if语句作为Python中最基础却最强大的分支控制工具,能让程序根据条件判断自主选择执行路径。想象一下自动售货机的工作机…

作者头像 李华