news 2026/9/8 21:02:27

Bruno 架构解析:Monorepo 布局、请求执行管线与 QuickJS 脚本沙箱

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Bruno 架构解析:Monorepo 布局、请求执行管线与 QuickJS 脚本沙箱

Bruno 架构解析:Monorepo 布局、请求执行管线与 QuickJS 脚本沙箱

【免费下载链接】brunoOpensource IDE For Exploring and Testing API's (lightweight alternative to Postman/Insomnia)项目地址: https://gitcode.com/GitHub_Trending/br/bruno

Bruno 是一个以"文件即 API 集合"为核心理念的开源 API 客户端(Postman/Insomnia 的轻量替代)。本文基于仓库内的架构参考文档.claude/reference/architecture.md,结合各包的实际源码与package.json,完整梳理 Bruno 的 npm workspaces monorepo 布局、跨进程请求执行管线(渲染进程 → Electron 主进程 → axios/gRPC/WebSocket)、bruno-js的双模脚本沙箱(QuickJS / Node VM)、.bru/.yml双文件格式系统,以及关键依赖的版本约束。读完后,你将能够准确定位任意功能所属的包、判断编辑某个包后是否需要重新构建、并理解一次请求从发起到断言执行的完整调用链。

一、Monorepo 结构:17 个 workspace 的分工

Bruno 使用 npm workspaces 组织代码,根 package.json 的workspaces字段声明了packages/下的 16 个包目录(含bruno-sqlite),另有仓库根级的 tests/(Playwright e2e 测试)与 playwright/(测试夹具与辅助函数)。

各包的定位与 npm 包名如下:

目录npm 包名职责
packages/bruno-app/@usebruno/appReact 渲染进程(rsbuild、Redux Toolkit、styled-components)
packages/bruno-electron/bruno未加 scope,其余均为@usebruno/*Electron 主进程,网络请求的实际执行者(electron-builder 打包)
packages/bruno-js/用户脚本沙箱(QuickJS +node:vm
packages/bruno-common/@usebruno/common共享工具,依赖 DAG 的底座(rollup →dist/cjs+dist/esm
packages/bruno-converters/@usebruno/converters导入/导出(Postman、Insomnia、OpenAPI 等)
packages/bruno-requests/@usebruno/requestsHTTP/gRPC/WS 请求的共享构件(rollup)
packages/bruno-filestore/@usebruno/filestore.bru/.yml序列化(rollup +tsc --emitDeclarationOnly
packages/bruno-query/@usebruno/queryJSONPath 风格查询引擎(rollup)
packages/bruno-lang/Bru DSL 语法:v1(arcsecond,遗留)+ v2(ohm-js,当前)
packages/bruno-schema/@usebruno/schemaYup 运行时校验(collections/requests)
packages/bruno-schema-types/@usebruno/schema-types仅 TypeScript 类型定义(tsc,types-only)
packages/bruno-graphql-docs/@usebruno/graphql-docsGraphQL 文档生成器(rollup)
packages/bruno-toml/@iarna/toml的薄封装(当前未在任何活跃数据路径上使用)
packages/bruno-cli/命令行执行器,直接从src/运行
packages/bruno-tests/测试服务器(express:HTTP/HTTPS/proxy/GraphQL)
packages/bruno-docs/@usebruno/docs占位 stub(仅 package.json + readme,无 src/)

补充:当前仓库的 workspaces 中还新增了packages/bruno-sqlite@usebruno/sqlite),已被bruno-appbruno-electron作为依赖引入,承担本地 SQLite 持久化相关能力,架构文档的包列表略早于该包的纳入。

构建工具差异:哪些包需要重新构建

每个包的构建工具不同,这直接决定"编辑后要不要重跑构建":

  • rollup(产出dist/cjs+dist/esm):bruno-common、bruno-converters、bruno-requests、bruno-query、bruno-graphql-docs;其中bruno-filestore额外执行tsc --emitDeclarationOnly(已核实其 build 脚本为rollup -c && tsc --emitDeclarationOnly -p tsconfig.build.json)。
  • 仅 tsc:bruno-schema-types(build 脚本为tsc -p tsconfig.json)。
  • rsbuild:bruno-app。
  • 无构建步骤(直接消费src/:bruno-js、bruno-lang、bruno-schema、bruno-toml、bruno-cli、bruno-electron、bruno-tests、bruno-docs。

因此编辑后必须重新构建的 7 个包(因为它们产出dist/)是:bruno-common、bruno-requests、bruno-filestore、bruno-converters、bruno-query、bruno-graphql-docs、bruno-schema-types。编辑 bruno-js / bruno-lang / bruno-schema / bruno-toml 则无需重建——这些包以源码形式被直接消费。

依赖方向与所有权边界

内部@usebruno/*依赖 DAG 的所有权护栏(bruno-common 保持浏览器安全、bruno-js 不依赖 Electron API、bruno-schemabruno-schema-types的分工、禁止向上依赖)定义在自动附加的规则文件.claude/rules/architecture.md中,区域级规则(.claude/rules/electron-ipc.md.claude/rules/redux-store.md.claude/rules/dsl-changes.md)则深入各主题细节。架构参考文档本身定位是"地图",具体约束以上述规则文件为准。

二、请求执行管线:从渲染进程到断言

一次"发送请求"动作的完整链路(以 Electron 应用为准):

  1. 渲染进程发起 IPC:渲染进程调用ipcMain.handle('send-http-request', …),处理器位于 bruno-electron/src/ipc/network/index.js。
  2. 变量插值ipc/network/interpolate-vars.js调用@usebruno/commoninterpolate完成{{variable}}替换。
  3. Pre-request 脚本:通过ScriptRuntime执行(bruno-js/src/runtime/script-runtime.js)。
  4. 构建并发送请求:HTTP 走 axios,另有 gRPC(@grpc/grpc-js)与 WebSocket 客户端。认证拦截器(如addDigestInterceptorapplyOAuth1ToRequest)、cookie 处理、scripting.buildScriptedEntry、grpc/ws 辅助等构件来自@usebruno/requests。需要注意:bruno-requests 是"请求构件库"而非编排器,真正的编排发生在ipc/network目录下(prepare-request.jsaxios-instance.jsprepare-grpc-request.jsws-event-handlers.js等文件共同完成这一层)。
  5. 响应后处理:变量提取由VarsRuntime负责,测试由TestRuntime负责,断言由AssertRuntime负责——三者与ScriptRuntime彼此独立的 runtime,均位于 bruno-js/src/runtime/(该目录现有script-runtime.jstest-runtime.jsassert-runtime.jsvars-runtime.jsscripted-entries.js五个文件)。
  6. 结果回传:结果以数据对象形式返回渲染进程,失败时对象上携带error字段——handler 不 reject Promise,这是 Electron IPC 的错误约定(详见.claude/rules/electron-ipc.md)。

ipc/network目录本身就是这条管线的源码级索引:

packages/bruno-electron/src/ipc/network/ index.js # send-http-request 等 IPC handler 总入口 interpolate-vars.js # 第 2 步:变量插值 prepare-request.js # 第 4 步:请求准备 axios-instance.js # HTTP 客户端封装 prepare-grpc-request.js # gRPC 分支 ws-event-handlers.js # WebSocket 分支 grpc-event-handlers.js # gRPC 事件处理 awsv4auth-helper.js # AWS v4 签名认证

三、脚本沙箱(bruno-js):QuickJS 与 Node VM 双模

用户脚本(pre-request / post-response / tests)在两种沙箱中执行:

  • QuickJS(默认,"safe" 模式):基于quickjs-emscripten的 WebAssembly 沙箱,实现位于 bruno-js/src/sandbox/quickjs/。用户脚本被包裹在异步闭包中,setTimeout被替换为基于bru.sleep的异步实现,从而在纯 QuickJS 环境中提供计时语义;
  • Node VM("developer" 模式)node:vmrunInContext,位于 bruno-js/src/sandbox/node-vm/,可访问更完整的 Node 能力。

运行时的选择是集中式的。getJsSandboxRuntime读取集合的securityConfig.jsSandboxMode配置完成映射:'safe'(默认)→ QuickJS,'developer'→ Node VM。主进程侧的实现(bruno-electron/src/ipc/network/index.js)为:

const getJsSandboxRuntime = (collection) => { const securityConfig = get(collection, 'securityConfig', {}); if (securityConfig.jsSandboxMode === 'developer') { return 'nodevm'; } // default runtime is `quickjs` return 'quickjs'; };

CLI 侧(bruno-cli/src/commands/run.js)也实现了同名函数,保证命令行执行与桌面应用行为一致。架构文档明确要求:新增沙箱模式逻辑必须走getJsSandboxRuntime,不要重复推导映射关系

两种沙箱的脚本包裹前缀与行号偏移量统一维护在 bruno-js/src/utils/sandbox.js:NODEVM_SCRIPT_WRAPPER_OFFSET/QUICKJS_SCRIPT_WRAPPER_OFFSET由前缀字符串的换行数计算得出,供错误格式化工具把 VM 报告的行号映射回.bru/.yml源码行。

脚本上下文中可用的全局对象(由script-runtime.js注入):

  • brureq,以及res(仅 post-response 阶段);
  • testexpect(chai)、assert(chai);
  • console

四、文件系统设计:.bru 与 .yml 双格式

  • bruno-filestore是解析/序列化的中心入口(bruno-filestore/src/index.ts),向下委托给formats/bruformats/yml两个格式化模块。
  • 磁盘上存在两种格式.bru(Bruno 自有 DSL)与.yml(OpenCollection YAML)。新创建的集合默认使用.ymlDEFAULT_COLLECTION_FORMAT = 'yml',该常量定义于bruno-apputils/common/constants并被"新建集合/保存临时请求"等 UI 流程引用)。
  • 当前.bru语法是 v2(ohm-js),位于 bruno-lang/v2/src/,filestore 通过bruToJsonV2/jsonToBruV2使用它;v1(arcsecond,位于 bruno-lang/v1/src/)为遗留实现。
  • 集合直接存放在文件系统上,由 Electron 主进程用chokidar监听变更。

任何对磁盘形状(on-disk shape)的变更,都需要遵循.claude/rules/dsl-changes.md中定义的流程。

五、应用侧:Redux Store 与 Providers

渲染进程的状态管理采用 Redux Toolkit。权威 slice 清单见 bruno-app/src/providers/ReduxStore/index.js 中的reducer映射(约 11 个 slice,其中collections/目录是体量最大的一块)。中间件约定与副作用边界由.claude/rules/redux-store.md规定。

Provider 层(bruno-app/src/providers/)当前包含:App/ReduxStore/Theme/Hotkeys/Toaster/PromptVariables/等目录,分别负责应用生命周期、全局状态、主题、快捷键、轻提示与交互式变量输入。

六、核心数据模型类型(bruno-schema-types)

核心类型集中在 bruno-schema-types/src/,包内只有类型定义(build 仅执行tsc),供全仓库共享。理解 Bruno 数据模型必须先掌握以下事实:

  • 请求是一个联合类型Request = HttpRequest | GrpcRequest | WebSocketRequestrequests/index.ts)。任何处理"一个请求"的代码都必须同时考虑这三种形态——这正是 Bruno 相比纯 HTTP 客户端在类型层面的关键约束。
  • 其他中心类型:Collectioncollection/collection.ts)、Item(请求或文件夹,以type字段判别,collection/item.ts)、Auth(以mode为判别字段的联合类型,common/auth.ts)、KeyValue(headers/params/assertions 的通用键值对,common/key-value.ts)、EnvironmentScript

运行时校验则由独立的bruno-schema(Yup)承担——"类型定义"与"运行时校验"分属两个包,这一分工是所有权边界的一部分。

七、关键依赖版本:以当前 package.json 为准

架构文档特别提醒:多个核心依赖被钉在低于最新的大版本上,不要假设它们是最新 API。以下版本已逐一在当前仓库各package.json中核实(适用前提:以本次仓库快照为准):

前端(bruno-app,见 packages/bruno-app/package.json)

依赖当前版本注意
react / react-dom19.0.0
@reduxjs/toolkit^1.8.0v1,不是 v2
react-redux^7.2.9v7
styled-components^5.3.3不是 v6
codemirror5.65.2CodeMirror 5,不是 scoped 的@codemirror/*
tailwindcss^3.4.1v3
@rsbuild/core^1.7.6devDependency

桌面端(bruno-electron,见 packages/bruno-electron/package.json)

依赖当前版本
electron~37.6.1(devDependency)
electron-builder^24.13.3(devDependency)
chokidar^3.5.3
@grpc/grpc-js^1.14.4
js-yaml4.3.1
electron-store^8.1.0

ws是 bruno-requests / bruno-tests 的依赖,不是bruno-electron 的直接依赖。

解析层(bruno-lang)arcsecond ^5(v1,遗留)、ohm-js ^16.6(v2,当前);bruno-toml封装@iarna/toml但当前未被使用。

根 package.json 的硬钉(overrides):package.json 中rollup被钉在3.30.0axios被钉在1.18.0(另有tarpbkdf2等安全相关固定)。在叶子包中提升这些版本号不会生效——必须改根级 override。

TypeScript 版本不统一:bruno-common 为^5.8.3,bruno-schema-types 为^5.0.0,而 bruno-converters / bruno-filestore / bruno-graphql-docs / bruno-query / bruno-requests 均为^4.8.4;根目录没有任何 TS 依赖。跨包编写类型代码时不能假设全局一致的 TS 语言特性。

八、测试资产:验证架构事实的入口

理解这套架构后,仓库中的测试资产是最直接的验证入口:

  • packages/bruno-js/tests/:沙箱生命周期、QuickJS 陷阱隔离(quickjs-trap-containment.spec.js)、teardown、脚本化条目等单测;
  • packages/bruno-electron/tests/:IPC 层测试(network/目录覆盖请求准备、mock、代理等);
  • tests/ + playwright.config.ts:Playwright e2e,按项目分组运行(test:e2etest:e2e:ssltest:e2e:authtest:e2e:mock-server等脚本见根 package.json);
  • packages/bruno-tests/:express 测试服务器,支撑 auth、redirects、sse、graphql 等端到端场景。

结语

Bruno 的架构可以概括为一条清晰的主轴:bruno-app(渲染)与bruno-electron(主进程)构成 Electron 双进程外壳,ipc/network是请求编排的唯一中枢,bruno-requests提供可复用构件,bruno-js的四个独立 runtime 分别承担 pre-request、变量提取、测试与断言,bruno-filestore+bruno-lang守护.bru/.yml双格式,bruno-schema-types则以HttpRequest | GrpcRequest | WebSocketRequest三态联合类型约束全仓库的请求处理代码。开发时牢记三条护栏:编辑 7 个产dist/的包后必须重新构建、依赖升级要改根级 override 而非叶子包、所有沙箱模式逻辑必须收敛到getJsSandboxRuntime

【免费下载链接】brunoOpensource IDE For Exploring and Testing API's (lightweight alternative to Postman/Insomnia)项目地址: https://gitcode.com/GitHub_Trending/br/bruno

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

ConvertX 自托管文件转换站:5分钟Docker部署,1000+格式随便转

ConvertX 自托管文件转换站:5分钟Docker部署,1000格式随便转 【免费下载链接】ConvertX 💾 Self-hosted online file converter. Supports 1000 formats ⚙️ 项目地址: https://gitcode.com/GitHub_Trending/co/ConvertX 工作群里丢来…

作者头像 李华
网站建设 2026/9/8 20:59:16

别再手写后台页面了:amis 低代码框架入门指南

别再手写后台页面了:amis 低代码框架入门指南 【免费下载链接】amis 前端低代码框架,通过 JSON 配置就能生成各种页面。 项目地址: https://gitcode.com/GitHub_Trending/am/amis 做后台管理系统,大部分时间都花在表单、表格和详情页上…

作者头像 李华
网站建设 2026/9/8 20:57:02

Ip2region|离线IP地址定位,10微秒出结果

Ip2region|离线IP地址定位,10微秒出结果 【免费下载链接】ip2region Ip2region is an offline IP-to-Region localization library and IP data management framework with both IPv4 and IPv6 supports, 10-microsecond level query efficiency, xdb se…

作者头像 李华
网站建设 2026/9/8 20:54:03

深入理解magnitude:从向量范数到机器学习与信号处理

1. magnitude 是什么:从物理量级到算法世界的核心尺度第一次接触 magnitude 这个词,是在大学物理课上。老师讲地震震级、星等亮度、声音分贝,所有这些看似无关的概念,底层都用同一个数学骨架——对数尺度下的量级比较。后来做了几…

作者头像 李华