cc-haha/docs/desktop/07-electron-migration-research.md
程序员阿江(Relakkes) 386a41e606 feat(desktop): enable Electron migration path
Introduce the Electron desktop shell alongside the existing React renderer and local Bun server boundary. The migration keeps the DesktopHost contract explicit across Tauri, Electron, and browser runtimes while adding Electron main/preload services for dialogs, shell, notifications, updates, tray/window lifecycle, terminal, preview WebContentsView, app mode, and release/package validation.

The commit also carries the latest local main desktop command updates, including agent slash entries and hidden-by-default markdown thinking details, so the packaged Electron build matches the current main UX surface.

Constraint: React renderer, local Bun server, REST/WebSocket, and sidecar boundaries must remain reusable during the migration
Constraint: macOS dev packages are ad-hoc signed and cannot prove Developer ID notarization or Gatekeeper release launch
Rejected: Browser-only smoke validation | it cannot exercise native dialogs, keychain prompts, notification behavior, or packaged app startup
Confidence: medium
Scope-risk: broad
Directive: Do not remove Tauri host support until signed Electron release artifacts pass native OS smoke on macOS, Windows, and Linux
Tested: bun run check:desktop
Tested: cd desktop && bun run check:electron
Tested: CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron📦dir
Tested: bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron
Tested: Computer Use read packaged Electron app window at desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app
Not-tested: Developer ID signed/notarized Gatekeeper launch
Not-tested: Real OS notification click-to-session action
Not-tested: Windows and Linux packaged app smoke on real hosts
2026-06-01 22:43:16 +08:00

13 KiB
Raw Permalink Blame History

Tauri 迁移 Electron 调研索引

日期2026-05-31 范围:桌面端壳层从 Tauri 2 迁移到 ElectronReact/Vite 渲染层尽量复用。 状态:调研与迁移方案,不是最终实现说明。

结论

迁移方向可行,但不能按“把 Tauri 项目换成 Electron 模板”处理。当前桌面端把 Tauri 2 当作完整 host runtime 使用:它负责自动更新、系统通知、文件选择、外链打开、窗口/托盘/菜单、子进程 sidecar、PTY 终端、子 WebView 预览面板、应用模式配置、窗口状态持久化和权限白名单。React 页面主体可以复用,但必须先把 desktop/src 里的 @tauri-apps/* 直接调用收敛为一层 desktopHost adapter然后再并行保留 Tauri 实现和新增 Electron 实现。

推荐目标架构是:

desktop/src React renderer
  -> desktop/src/lib/desktopHost/* typed host adapter
  -> Tauri implementation during transition
  -> Electron preload contextBridge implementation
  -> Electron main process services
       - sidecar manager
       - updater service
       - notification service
       - dialog/open service
       - window/tray/menu service
       - terminal/pty service
       - preview WebContentsView service

这样可以让前端继续用同一套 Zustand store、API client、WebSocket 和组件测试,同时把高风险系统能力迁移压缩到 adapter contract 和 Electron main/preload 层。不要把本地 Bun server 合并进 Electron main也不要把 renderer 从 HTTP/WebSocket 改成全 IPC现有 desktop/src/api/*chatStoreworkspacePanelStoreteamStore 已经围绕 local server contract 做了重连、去重、流式 flush 和多客户端观察,这些成熟行为应作为迁移边界保留。

外部依据

本次调研以官方文档和 GitHub issue 为准Twitter/X 搜索结果信噪比较低,未作为决策依据。

  • Tauri README 明确 Tauri 通过 WRY 使用系统 WebViewmacOS/iOS 是 WKWebViewWindows 是 WebView2Linux 是 WebKitGTK同时 Tauri 提供 bundler、自更新、托盘和原生通知等桌面能力。参考tauri-apps/tauri
  • Tauri 2 updater 插件使用 endpoint/static JSON 和 capability permission危险命令默认阻断必须在 capabilities 里放行。参考:Tauri Updater
  • Tauri capability 是按 window/webview 授权的权限集合,权限名使用 plugin:permission 形式。参考:Tauri Capability
  • Electron 推荐使用 preload + contextBridge,不要把 ipcRenderer 原样暴露给 renderercontextIsolation 从 Electron 12 起默认启用。参考:Electron Context IsolationElectron Security
  • Electron 内置 autoUpdater 不支持 Linux如果要覆盖 macOS/Windows/Linux更适合用 electron-builder + electron-updater。参考:Electron autoUpdaterelectron-builder Auto Update
  • Electron BrowserView 已弃用,应使用 WebContentsView 承载内嵌浏览器/预览面板。参考:Electron BrowserView
  • Tauri 社区 issue 里也能看到系统 WebView 的真实世界差异,例如 macOS WebView 特定异常和 WebKit memory/performance 争议。参考:tauri #13141tauri #5889

本仓库现状

桌面三层

  • React/Vite rendererdesktop/src
  • Tauri hostdesktop/src-tauri
  • 本地 server/CLI sidecardesktop/sidecars/claude-sidecar.ts 打包成 desktop/src-tauri/binaries/claude-sidecar-*,再由 Tauri host 启动。

迁移后仍应保留这个三层关系,只把中间 host 从 Tauri 改成 Electron

  • React renderer 继续调用 desktop/src/api/client.tssessionsApiteamsApi、WebSocket manager。
  • Electron main 负责启动和监管 local server sidecar。
  • renderer 通过 getServerUrl 得到 local server URL而不是直接通过 IPC 读写会话数据。

关键入口:

  • desktop/src-tauri/src/lib.rs 注册 Tauri plugins、commands、tray/menu、sidecar、terminal、update 准备、通知 bridge、窗口生命周期。
  • desktop/src-tauri/capabilities/default.json 声明 Tauri 2 权限。
  • desktop/src-tauri/tauri.conf.json 声明窗口、CSP、updater、bundle、externalBin、icons。
  • desktop/src/lib/desktopRuntime.ts 解析 Tauri runtime 并获取 local server URL。
  • desktop/src/stores/updateStore.ts 管理自动更新状态机。
  • desktop/src/lib/desktopNotifications.ts 管理通知权限、发送、点击回跳和系统设置入口。
  • desktop/src/api/terminal.ts 通过 Tauri command + event 管理 PTY。
  • desktop/src/lib/previewBridge.tsdesktop/src-tauri/src/webview_panel.rs 管理内嵌预览 WebView。

当前性能背景

SCROLL_PERF_INVESTIGATION.md 的关键判断是:卡顿主要来自 macOS Tauri 使用 WKWebView而 Electron 固定 Chromium/Blink。已合并的 content-visibility:auto 是 WebKit 侧缓解方案,但如果目标是接近 CodeX/官方 Claude Code 的一致滚动性能Electron 是合理方向。迁移收益是可控渲染栈和跨平台一致性;成本是安装体积、内存、签名更新链路和安全边界全部要重建。

Tauri 2 能力盘点与 Electron 对照

能力 当前 Tauri 位置 Electron 目标 迁移风险
自动更新 tauri.conf.json plugins.updaterupdateStore.tsprepare_for_update_installtauri.release-ci.json electron-builder + electron-updatermain process 暴露 check/download/install/relaunch IPC 签名、feed metadata、Windows sidecar 文件锁、Linux update 支持不能照搬
通知 tauri-plugin-notificationdesktopNotifications.tsmacos_notifications.m Electron Notification + main process click routing必要时保留 macOS native bridge macOS 权限状态和点击回跳语义必须回归测试
文件/目录选择 @tauri-apps/plugin-dialog.open/save dialog.showOpenDialog/showSaveDialog 从 Tauri capability 变成自管 IPC 白名单,路径和 options 要验证
外链/系统打开 @tauri-apps/plugin-shell.open shell.openExternal/openPath 必须限制 URL scheme避免 renderer 任意打开危险目标
sidecar Tauri shell plugin + start_server_sidecar / spawn_and_track_adapters_sidecar Electron main child_process.spawn + sidecar manager 动态端口、启动日志、adapter 重启、Windows kill 逻辑必须等价
PTY 终端 Rust portable-pty commands + Tauri events Electron main 继续用 Node/Rust sidecar/原生模块之一提供 PTY 如果从 Rust 移到 Nodeshell 解析和 Windows 行为会变
窗口/托盘/菜单 setup_system_tray、macOS MenuBuilderRunEvent BrowserWindowTrayMenuapp lifecycle 关闭隐藏到托盘、macOS reopen、窗口位置恢复必须逐平台验证
窗口控制/拖拽 getCurrentWindow().minimize/toggleMaximize/close/startDragging preload bridge 到 BrowserWindow actions 自定义标题栏和拖拽区域需要真实窗口 smoke
子 WebView 预览 Tauri WebviewBuilder.add_child + preview_* commands WebContentsViewrenderer 只发 typed IPC BrowserView 已弃用URL 白名单和消息 JSON 校验必须保留
单实例 tauri_plugin_single_instance app.requestSingleInstanceLock() 第二实例参数、显示主窗口、协议启动要补测试
app mode / portable config get_app_modeset_app_modedetect_portable_dirCLAUDE_CONFIG_DIR main process config service 这是持久化形状变更,要跑 persistence upgrade gate
zoom set_app_zoom webContents.setZoomFactor 范围 clamp 和设置页行为要保持
权限模型 capabilities/default.json 自定义 IPC capability registry 这是安全边界,不能让 renderer 直接拿 Node/Electron API

React 可复用边界

可复用:

  • desktop/src/components/** 中大部分聊天、设置、任务、workspace、markdown、browser UI。
  • desktop/src/stores/** 的业务状态机,前提是依赖 host 能力的 store 改成调用 adapter。
  • desktop/src/api/** 的 HTTP/WebSocket client。
  • desktop/scripts/build-preview-agent.tsdesktop/scripts/build-sidecars.ts 的 sidecar 编译逻辑。
  • scripts/quality-gate/** 的报告和 lane 框架。

必须抽象:

  • runtime 探测:isTauriRuntime() 需要替换为 getDesktopHostKind(),支持 browsertaurielectron
  • command 调用:invoke('...') 改为 typed host methods。
  • event 订阅Tauri listen(...) 改为 typed desktopHost.events.on(...)
  • shell/dialog/notification/update/window/preview/terminal全部通过 adapter 暴露。

不应复用为最终形态:

  • desktop/src-tauri/** 的 Tauri 配置和 Rust host crate。
  • Tauri capability JSON。Electron 需要等价的 IPC allowlist但不是同一格式。
  • tauri-apps/tauri-actiontauri build CI 步骤。

必须避免的重写:

  • 不重写 desktop/src/api/websocket.ts 的重连、退避、ping 和 pending queue。
  • 不重写 chatStore 的历史加载去重和 streaming flush。
  • 不重写 workspace 请求的 latest-request 防陈旧覆盖。
  • 不把 session/team/workspace API 从 HTTP/WS 改成 Electron IPC。

推荐迁移路径

1. 冻结基线

先在现有 Tauri 实现上跑并记录:

bun run check:policy
bun run check:desktop
bun run check:server
bun run check:coverage
bun run quality:gate --mode baseline --allow-live --provider-model <provider:model[:label]>

目的不是证明迁移完成,而是固定“现有能力本来是什么状态”。

2. 新增 host adapter但 Tauri 仍是唯一实现

新建建议路径:

  • desktop/src/lib/desktopHost/types.ts
  • desktop/src/lib/desktopHost/index.ts
  • desktop/src/lib/desktopHost/tauriHost.ts
  • desktop/src/lib/desktopHost/browserHost.ts
  • desktop/src/lib/desktopHost/__tests__/contract.test.ts

先把 renderer 中直接 import @tauri-apps/* 的位置迁走。完成标准:

rg "@tauri-apps|invoke\\(|getCurrentWindow" desktop/src

生产代码中除 desktopHost/tauriHost.ts 外不应再命中。

3. 增加 Electron 壳

建议新增:

  • desktop/electron/main.ts
  • desktop/electron/preload.ts
  • desktop/electron/services/sidecarManager.ts
  • desktop/electron/services/updater.ts
  • desktop/electron/services/notifications.ts
  • desktop/electron/services/dialogs.ts
  • desktop/electron/services/windows.ts
  • desktop/electron/services/preview.ts
  • desktop/electron/ipc/capabilities.ts

renderer 通过 preload 暴露的 window.desktopHost 调用,不直接使用 Electron API。

4. 构建与发布改造

保留 Vite build、sidecar build、质量门禁骨架替换 Tauri build

  • check:native 改名或重定义为 Electron main/preload compile + package config check。
  • 新增 check:electron
  • release-desktop.ymltauri-action 迁到 electron-builder 的 macOS/Windows/Linux matrix。
  • scripts/release.ts 从更新 tauri.conf.json / Cargo.toml 改为更新 Electron package/build config。

5. 逐能力迁移和验收

每迁移一个能力都要满足三层证据:

  1. contract/unit test 通过;
  2. Electron main/preload 实现测试通过;
  3. 打包 app smoke 通过。

不要等到全部迁移完才第一次打开 Electron app。

安全设计要求

Electron 迁移最大的安全风险不是 Electron 本身,而是从 Tauri 声明式 capability 退化成“renderer 想要什么就 IPC 什么”。必须建立等价能力层:

  • renderer 不暴露 Node.js、ipcRenderershelldialogchild_process
  • preload 只暴露固定函数,不暴露通用 invoke/send。
  • IPC channel 必须枚举payload 用 schema 校验。
  • shell open 只允许 https:, http:, mailto: 和明确需要的本地 path 打开场景。
  • child process 只允许 sidecar 和 PTY 服务,参数白名单要保留 Tauri capability 中的约束。
  • preview URL 继续只允许 http/https
  • 更新安装前必须停止 sidecar失败后恢复可重试状态。

文档索引