从 Electron 迁移
webview 的 WindowTem 对标 Electron TS 版 WindowTem,迁移路径直接:TS 桌面主进程 → C++,渲染层前端基本原样复用。
概念对照
| Electron | webview |
|---|---|
BrowserWindow | Window(App::createWindow) |
WindowTem(TS 模板基类) | WindowTem(C++ 模板基类) |
ipcMain.handle / ipcRenderer.invoke | bridge.call / on |
webContents.send | win->send / broadcast |
preload.ts 暴露 window.bridge | runtime.js 自动注入 window.bridge |
app.on('second-instance') | onSecondInstance |
BrowserWindow 选项 | Window::Opts |
通道迁移
Electron IPC 通道名照搬,把 ipcMain.handle 换成 on:
ts
// Electron(TS)
ipcMain.handle('getProject', (_e, id) => loadProject(id))
win.webContents.send('projectReady', data)cpp
// webview(C++)
tem->on("getProject", [](int id) { return loadProject(id); });
tem->send("projectReady", data);前端 ipcRenderer.invoke → bridge.call:
js
// Electron
const p = await ipcRenderer.invoke('getProject', 42)
// webview(契约一致)
const p = await bridge.call('getProject', 42)WindowTem 迁移
TS 子类的 initBridge / emitNames 几乎逐行搬:
ts
// TS
class MainWindow extends WindowTem {
emits = ['downloadCb']
initBridge() {
this.on('addTzm', this.addTzm)
this.on('useTzm', this.useTzm)
}
}cpp
// C++
class MainWindow : public webview::WindowTem {
public:
using webview::WindowTem::WindowTem;
void initBridge() override {
emits({"downloadCb"});
on("addTzm", &MainWindow::addTzm);
on("useTzm", &MainWindow::useTzm);
}
};反射差异
TS 可字符串反射 this[name] 自动循环绑定。C++ 不行——WindowTem 不支持反射,槽必须逐个 on(name, &Cls::method) 显式绑。
数值字段:bigint 序列化
Electron 常用 bigint 字段。webview JSON 约定:bigint 字段以字符串序列化(对齐 TS 行为)。改 C++ 序列化时保持此约定,否则前后端不兼容。
js
// 前端:收到的 bigint 是字符串
const id = String(data.id) // 已是 "123..."资源迁移
Electron loadFile('dist/index.html') → webview 打包模式:
cpp
opts.url = L"src/assets/html/index.html"; // 约定目录前端 dist 拷进 src/assets/html/,构建期自动嵌入 exe。详见 嵌入 Vue 工程。
去掉的东西
preload.ts:不再需要,runtime.js自动注入window.bridgeelectronnpm 依赖:整个渲染层可脱离 ElectroncontextBridge:直接用window.bridge- Node 内置模块(
fs/path):文件操作走 C++ RPC 槽,前端不直接碰
fs / getPath 迁移
框架内置 getPath + userData 白名单 fs(WindowTem 自动绑),前端 Node fs/app.getPath 直接对应:
| Electron | 本框架 |
|---|---|
app.getPath('userData') | await bridge.call('getPath', 'userData') |
fs.promises.readFile(p) | await bridge.call('fs.readFile', p)(返 base64,atob 解) |
fs.promises.writeFile(p, buf) | await bridge.call('fs.writeFile', p, btoa(buf)) |
fs.promises.mkdir(p,{recursive}) | await bridge.call('fs.mkdir', p) |
fs.promises.readdir(p) | await bridge.call('fs.listDir', p) → {entries} |
fs.promises.unlink/rmdir | await bridge.call('fs.remove', p)(空目录/文件) |
白名单约束:fs 操作强制限 %APPDATA%\<exe>\ 子树,越界返 {error:"out of userData"}。系统目录读写用 App::showSaveDialog/openExternal。详见 路径与文件系统。