本节摘要:这是第 10 章的收尾。桌面环境下,前端要调主进程的 IPC(第 9 章),但裸调 IPC 写起来繁琐。OpenWork 用一个 desktopBridge Proxy——把对主进程的调用包装成「像调方法一样」的代理对象。本节讲清这个 Proxy 的工作原理,以及它如何与第 9 章的 IPC 命令注册表对接。
第 9 章讲过,桌面环境下前端有些操作要走主进程(窗口、文件、运行时、通知等)。这些操作通过 IPC 命令调用:
前端: invokeDesktop("openDialog", { ... }) │ ▼ IPC 到主进程 │ 主进程: 找到 "openDialog" 命令执行 │ ▼ 结果返回
但这种「invokeDesktop(命令名, 参数)」的写法,用起来有点繁琐——每次都要写字符串命令名,IDE 也不补全。
desktopBridge Proxy 把这种调用包装成代理对象,你访问它的属性就像「调方法」:
// 没有 Proxy 的写法(繁琐): await invokeDesktop("openDialog", { ... }); await invokeDesktop("readFile", { path: "..." }); await invokeDesktop("restartEngine"); // 有 Proxy 的写法(优雅): await desktopBridge.openDialog({ ... }); await desktopBridge.readFile({ path: "..." }); await desktopBridge.restartEngine();
两种写法等价,但后者读起来像「直接调方法」,更清晰、IDE 能补全(因为类型来自第 9 章的共享类型包)。
desktopBridge 是一个 Proxy 对象(JavaScript 的 Proxy 特性)。它的魔法在于 get 拦截——你访问任何属性,它都返回一个函数,这个函数把属性名当命令名调 IPC:
// 概念性示例:desktopBridge Proxy 的实现 const desktopBridge = new Proxy({}, { get(target, prop) { // 缓存:如果之前生成过这个方法,直接用 if (target[prop]) return target[prop]; // 否则生成一个函数:把 prop 当命令名调 IPC target[prop] = (...args) => invokeDesktop(prop, ...args); return target[prop]; } });
所以 desktopBridge.openDialog(...) 实际是 invokeDesktop("openDialog", ...),desktopBridge.readFile(...) 是 invokeDesktop("readFile", ...)。Proxy 自动把「属性访问」翻译成「IPC 命令调用」。
💡 Proxy 的价值:它让「调 IPC」变得像「调本地方法」——代码清晰、IDE 友好、不用记字符串命令名。这是「用语言特性消除样板代码」的好例子。
desktopBridge 的类型来自第 9 章的共享类型包(DesktopCommandMap)。这个类型定义了所有可用命令及其参数/返回类型:
// 共享类型包定义(概念性) type DesktopCommandMap = { openDialog: { args: {...}, result: {...} }; readFile: { args: { path: string }, result: string }; restartEngine: { args: void, result: void }; ... }; // desktopBridge 的类型就是这个映射 type DesktopBridge = { [K in keyof DesktopCommandMap]: (args: DesktopCommandMap[K]["args"]) => Promise<DesktopCommandMap[K]["result"]>; };
所以 desktopBridge.readFile({ path }) 的参数和返回都有类型检查——传错参数编译报错。这是第 9 章强校验契约在前端的体现。
经核实,desktopBridge 的实现在 app/ 框架无关层(不是 react-app/)。这符合第 01 节的分层规则——IPC 客户端是「纯逻辑」,不依赖 react,所以放框架无关层。
src/app/lib/desktop.ts ← desktopBridge 实现在这里(框架无关层) │ ▼ 被内核/领域引用 │ ▼ 但通过 Platform 抽象访问(第 02 节),不直接 import
注意:虽然 desktopBridge 在框架无关层,但领域通常不直接 import 它——而是通过 Platform 抽象间接用。这保持了「领域不直接依赖具体实现」的分层纯洁。
desktopBridge 还暴露一个 desktopFetch(第 9 章提过)——让前端能走主进程发跨域请求:
// 回环地址直发,跨域走主进程 if (isLoopback(url)) { return fetch(url); // 直发(快) } else { return desktopBridge.__fetch(url); // 走主进程(绕 CORS) }
desktopFetch 内部判断:回环直发(性能),跨域走主进程(绕限制)。这是 desktopBridge 提供的「特殊 IPC 用法」。
读完第 10 章四节,你掌握了 OpenWork 前端的完整图景:
| 节 | 讲了什么 |
|---|---|
| 01 三层分层 | app(逻辑)/ kernel(Provider)/ shell(骨架)/ domains(业务) |
| 02 Platform 抽象 | 一份业务 + 两份环境实现,跨浏览器与桌面 |
| 03 领域模块 | session/workspace 等,通过 Provider 访问后端 |
| 04 desktopBridge | Proxy 把 IPC 包装成方法调用 + 类型安全 |
核心认知:前端用「严格分层 + Platform 抽象 + Provider 访问 + Proxy 包装」实现了「一套代码跨环境、可维护、类型安全」。第 11 章我们看前端如何发起「分享」——Connect Link。
第 10 章结束。下一章讲 Connect Link——连接分享与多端接入。