本节摘要:这是第 10 章最精巧的一节。同一个 React 单页应用,要同时跑在浏览器和 Electron 桌面,行为还得一致——怎么做到?靠一层 Platform 抽象:前端不直接调浏览器 API 或 Electron IPC,而是通过 Platform Provider 间接调,这个 Provider 在两个环境有不同实现对上层透明。本节讲清这套抽象,以及它为什么让「一套代码、两个环境」成立。
浏览器和 Electron 桌面,对前端来说是两个不同的运行环境:
| 操作 | 浏览器怎么做 | 桌面怎么做 |
|---|---|---|
| 打开链接 | window.open |
IPC 调主进程的开链接 |
| 重启应用 | (浏览器没这概念) | IPC 调主进程的重启 |
| 通知 | 浏览器通知 API | IPC 调主进程的通知 |
| 存储偏好 | localStorage | 可能走主进程的持久存储 |
| 检查更新 | (浏览器自动) | IPC 调主进程检查更新 |
| fetch | 直接 fetch | 可能走主进程(desktopFetch,绕 CORS) |
如果前端代码里直接写 window.open 或直接调 IPC,会出问题:
window.open 的预期行为)。OpenWork 的解法是引入一个 Platform 接口,前端通过它访问「环境相关能力」,不直接调具体 API:
Platform 接口(抽象): openLink, restart, notify, storage, checkUpdate, fetch, ... ▼ 浏览器环境实现:用浏览器 API ▼ 桌面环境实现:用 IPC 前端代码:platform.openLink(...) ──► 不关心是哪个环境
也就是说,前端代码只调 platform.某方法,不关心底层是浏览器 API 还是 IPC。Platform 在两个环境有不同的实现,启动时注入对的那个。
Platform 接口大致含这些:
| 能力 | 说明 |
|---|---|
platform |
当前环境("web" / "desktop") |
os |
操作系统(桌面才有) |
capabilities |
环境能力集 |
openLink |
打开链接 |
restart |
重启应用 |
notify |
通知 |
storage |
偏好存储 |
checkUpdate / update |
检查/执行更新 |
fetch |
发请求(可能走主进程) |
getDefaultServerUrl / setDefaultServerUrl |
默认服务端地址管理 |
这些能力覆盖了「环境相关」的操作。前端需要任何环境相关能力,都从 Platform 拿,不直接调底层。
Platform 的实现选择发生在启动时。有个 createDefaultPlatform() 函数,根据当前环境创建对的 Platform:
createDefaultPlatform() │ ▼ 判断 isDesktopRuntime()? │ ├─ 是(桌面)──► 创建桌面 Platform(用 IPC 实现) │ ├─ openLink → IPC openDesktopUrl │ ├─ restart → IPC relaunchDesktopApp │ ├─ notify → IPC desktopNotificationShow │ └─ ... │ └─ 否(浏览器)──► 创建 web Platform(用 window API 实现) ├─ openLink → window.open ├─ restart → (不支持 / 刷新页面) ├─ notify → 浏览器通知 └─ ... │ ▼ 用 PlatformProvider 注入 │ ▼ 上层通过 usePlatform() 拿到
注入后,上层用 usePlatform() 钩子拿到 Platform,调它的方法。上层不知道、也不用知道当前是哪个环境。
这是本节的核心。有了 Platform 抽象:
platform.某方法,一份代码。领域代码(一份): platform.openLink("https://...") │ ├─ 浏览器: window.open ──► 新标签打开 └─ 桌面: IPC ──► 主进程用系统浏览器打开 │ 结果:都「打开了链接」,用户体验一致
💡 「一套代码、两个环境」的本质:不是「写两份代码分别跑」,而是「一份业务代码 + 两份环境实现」。差异被隔离在环境实现里,业务代码不知道差异存在。
这套抽象还有个额外红利——加新环境很容易。假设未来要支持移动端:
加移动端 Platform 实现(用移动端 API) │ ▼ createDefaultPlatform() 加分支:移动端 ──► 移动 Platform │ ▼ 业务代码一行不改
因为业务代码只依赖 Platform 接口,不依赖具体环境。加新环境就是加一个新 Platform 实现,业务无感。这是「为未来留缝」的工程智慧(类似 OpenCode 教程第 7 章 Location 为多节点留缝)。
Platform 还带 capabilities(能力集)——声明当前环境支持什么。这让前端能「探测环境能力」:
if (platform.capabilities.hasUpdateCheck) { // 这个环境支持检查更新,显示更新入口 } else { // 不支持,隐藏 }
这比「按环境名判断」(if desktop)更优雅——它判断的是「能力」而非「环境」。比如某能力在桌面和某浏览器都有,用 capabilities 判断就都对。
Platform 讲清了,下一节讲具体领域模块——会话/产物/语音/终端。