Electron 30 的 WebContentsView-替代 BrowserView 之后多视图应用怎么写

Electron 30 把用了多年的 BrowserView 退役了,替代品是 WebContentsView + BaseWindow。不是革新性新能力,是回归 Chromium Views API 标准的工程清理。本文给 4 方对比 + 4 处迁移 diff + 3 个真实迁移坑。

我最近在重写一个老 Electron 应用------它原来用了七八个 BrowserView 拼出"多 tab + 侧边栏 + 浮窗"的界面。结果一升级到 Electron 30,到处都是 deprecated 警告:BrowserView 这个用了多年的 API 终于被换掉了。

替代品是 WebContentsView + BaseWindow 这套新组合。Electron 官方有迁移指南,但偏简略,我自己边写边踩了几个坑------这篇把新模型的设计意图、迁移细节、容易踩的地方一次讲完,省得后来者再走一遍。

为什么要换:BrowserView 的历史包袱

BrowserView 是 Electron 早期为了"在 BrowserWindow 里嵌入子页面"做的一个变通方案。从 Chromium 的视角看它一直是个怪胎------既不是标准的 <iframe>,也不是 Chromium Views API 的标准产物,而是 Electron 自己实现的一层胶水。

具体表现:

  • BrowserView 不在 DOM 树里,但它的 webContents 又是完整的 Chromium 渲染管线产物
  • BrowserView 的定位、尺寸、层级,全靠主进程显式调用 setBounds 管理------和 BrowserWindow 的关系是"挂载"而不是"组合"
  • 多个 BrowserView 叠加时层级管理混乱,需要手动 setTopBrowserView

这套设计支撑 Electron 跑了好几年,但维护成本越来越高------尤其当 Chromium 自己在演进 Views API 时,BrowserView 这个非标实现总是要单独适配。Electron 团队决定推倒重来:让"嵌入式视图"这件事回归 Chromium Views API 标准路径。

新模型:BaseWindow + WebContentsView

WebContentsView 是 Chromium Views API 的一等公民。它做的事和 BrowserView 类似------在主进程里持有一个 web 渲染单元,由主进程显式控制位置和尺寸------但底层实现完全对接 Chromium Views,未来跟着 Chromium 升级而不用单独适配。

配套的 BaseWindow 是新的窗口基类。BrowserWindow 现在实际上是 BaseWindow + 内置主 WebContentsView 的语法糖;如果你要做更复杂的多视图组合,直接用 BaseWindow 然后手动添加 N 个 WebContentsView

代码示意:

javascript 复制代码
const { app, BaseWindow, WebContentsView } = require('electron');

app.whenReady().then(() => {
  const win = new BaseWindow({ width: 1200, height: 800 });

  // 主视图
  const mainView = new WebContentsView();
  win.contentView.addChildView(mainView);
  mainView.setBounds({ x: 0, y: 0, width: 800, height: 800 });
  mainView.webContents.loadURL('https://chenguangliang.com');

  // 侧边栏视图
  const sidebarView = new WebContentsView();
  win.contentView.addChildView(sidebarView);
  sidebarView.setBounds({ x: 800, y: 0, width: 400, height: 800 });
  sidebarView.webContents.loadFile('sidebar.html');

  // 浮动小窗
  const popupView = new WebContentsView();
  win.contentView.addChildView(popupView);
  popupView.setBounds({ x: 500, y: 600, width: 300, height: 150 });
  popupView.webContents.loadFile('popup.html');
});

三个独立的 WebContentsView 组合在一个 BaseWindow 里,每个有自己独立的 webContents------这意味着独立的 V8 上下文、独立的 cookie 域(如果你配置不同的 partition)、独立的 DevTools。

这种"主进程组合多个视图"的能力,原来要靠多个 BrowserView 凑出来,现在是 first-class API。

与 iframe / webview / BrowserView 的真实差异

新人最容易混淆的是这几个方案的边界。我整理一下:

方案 在 DOM 中? 由谁控制 进程隔离 适用场景
<iframe> renderer 同进程(同域)/ 跨进程(跨域 + site-isolation) 嵌入站内或可信第三方页面
<webview> renderer 独立进程 嵌入完全不可信的第三方页面
BrowserView(旧) 主进程 独立进程 主进程拼多个原生视图
WebContentsView(新) 主进程 独立进程 主进程拼多个原生视图,BrowserView 的现代替代

WebContentsView 接替了 BrowserView 的位置------iframe / webview 那两个没变。如果你应用里用的是 iframe 或 webview,不需要迁移。

什么时候选 WebContentsView 而不是 iframe?我的判断:

  • 要嵌入完全独立的 web 页面(比如自家 SaaS web 端 + 桌面壳)→ WebContentsView
  • 要做多 tab / 多面板的 IDE 类应用(VS Code、Cursor 这种)→ WebContentsView
  • 要给嵌入页面单独的 cookie / session / 扩展集合 → WebContentsView(用不同 partition)
  • 要嵌入 trusted 站内内容(设置页、模态层) → iframe 简单够用
  • 要嵌入完全不可信第三方 → webview 沙箱更彻底

迁移代码:从 BrowserView 改到 WebContentsView

实际迁移大约 80% 是机械替换,剩下 20% 是几个 API 改动。

改动 1:构造和添加方式

javascript 复制代码
// 旧(BrowserView)
const view = new BrowserView({ webPreferences: { ... } });
mainWindow.setBrowserView(view);            // 设为主 view
// 或
mainWindow.addBrowserView(view);            // 添加额外 view

// 新(WebContentsView)
const view = new WebContentsView({ webPreferences: { ... } });
mainWindow.contentView.addChildView(view);  // 统一用 addChildView

注意:setBrowserViewaddBrowserView 的语义差别被消除了。新 API 全部走 contentView.addChildView()------这是 Views API 标准模式。

改动 2:自动尺寸跟随

javascript 复制代码
// 旧(BrowserView 的 setAutoResize)
view.setAutoResize({ width: true, height: true });

// 新(手动监听 resize 事件)
mainWindow.on('resize', () => {
  const [w, h] = mainWindow.getContentSize();
  view.setBounds({ x: 0, y: 0, width: w, height: h });
});

这是迁移里最容易漏掉的一步。setAutoResize 没了,要自己处理 resize 事件。如果你的应用有侧边栏 + 主视图 + 底栏的复杂布局,建议封装一个 LayoutManager 类管理所有 view 的 bounds------比散在各处 resize listener 好维护。

改动 3:transparent 背景

javascript 复制代码
// 旧
view.setBackgroundColor('#00000000');  // 部分平台不生效

// 新
view.setBackgroundColor('#00000000');  // alpha=00 全平台生效

代码一样,但据我自己实测,新 API 在跨平台透明背景上的表现比旧 BrowserView 更稳------原来 Windows 上偶尔不生效的问题在我的项目里消失了(具体修复细节未在官方 changelog 看到明确条目,按你的实测为准)。

改动 4:webContents 访问

javascript 复制代码
// 旧 + 新都一样
view.webContents.loadURL(...);
view.webContents.openDevTools();

view.webContents 这个属性没变------你之前所有 IPC 通信、监听器、preload 配置都不用动。这是迁移成本最低的一档。

几个我踩过的坑

光读官方迁移指南是不够的。我踩了三个坑,记下来给后来者:

坑 1:addChildView 的顺序就是层级

addChildView 后加的会盖在 先加的上面(Z-index 顺序)。所以浮动小窗 / 模态层要最后加。如果你迁移时按"主视图 → 侧边栏 → 模态浮窗"的顺序,巧合是对的;但如果按"模态 → 主视图 → 侧边栏"的顺序加,模态会被盖到最底下。

旧的 setTopBrowserView 没了,要重排顺序得 removeChildView + addChildView 重新加。

坑 2:DevTools 现在独立窗口

旧 BrowserView 的 DevTools 默认是 docked 模式。WebContentsView 的 DevTools 默认会单独开新窗口------这不是 bug 是 feature,但用习惯了的话第一次会懵。要 docked 模式:

javascript 复制代码
view.webContents.openDevTools({ mode: 'detach' });  // 独立窗口
view.webContents.openDevTools({ mode: 'bottom' }); // 底部 docked

坑 3:preload 路径一律用绝对路径

这条不是 WebContentsView 特有的------Electron 一直要求 webPreferences.preload 必须是绝对路径 。但旧 BrowserView 代码里如果你侥幸用了相对路径(某些版本下有兜底解析行为),迁移时建议统一改成 path.join(__dirname, 'preload.js')。这条算迁移时顺手清理的技术债,不算 WebContentsView 引入的新问题。

性能:会变更好吗

诚实回答:单视图场景几乎没差别。WebContentsView 的实际渲染管线和 BrowserView 一样底层用 Chromium,性能差异在 1% 以内。

差异在多视图场景

  • 旧 BrowserView 多视图叠加时主进程要做大量 z-index 计算和 IPC 协调
  • 新 WebContentsView 走 Chromium Views API 标准合成路径,多视图叠加效率更高

实测我那个有 7 个视图的老应用迁移后:内存占用约降 8%,启动速度约快 200ms。样本只有一个项目,仅供参考------不是颠覆性提升,但白送的优化值得拿。

给还没迁移的人的建议

如果你现在还在 Electron < 30,迁移时机看产品节奏:

  • 明年才升 Electron 主版本:不急,先继续用 BrowserView,但写新代码尽量用 WebContentsView 避免增加迁移债
  • 本季度要升 Electron 30+:现在就把迁移做完,deprecated 警告不解决迟早会成阻塞项
  • 本来就在 Electron 30+ 但还在用 BrowserView:抓紧迁------Electron 官方未公布具体移除版本,但按 deprecated → 移除通常 2-4 个大版本的节奏看,建议 Electron 32 之前清干净

迁移本身工作量不大,一个有 5-10 个 view 的中型项目大约半天到一天能搞定。真正的成本是测试------多视图布局的边角 case 很多,建议把所有 view 切换、resize、模态弹出的场景过一遍。

一句话总结

WebContentsView 不是革新性的新能力,是 Electron 把 BrowserView 这套自家实现退役、回归 Chromium Views API 标准的工程清理。对开发者:API 更一致、未来更稳定、多视图组合性能略好;对维护者:少了一层非标胶水,跟着 Chromium 升级少踩坑。

不算性感的更新,但是把基础设施收拾干净的那种------值得迁,但不用激动。


延伸阅读

原文首发于 chenguangliang.com

相关推荐
怕浪猫10 小时前
Electron 开发实战(八):多媒体处理全解|音视频播放、录屏、FFmpeg 实战
前端·javascript·electron
AI科技星1 天前
万有引力G与真空介电常数ε0全维度完整关系式汇编(基于v=c螺旋时空理论)
c语言·开发语言·前端·javascript·网络·汇编·electron
数据法师1 天前
Alger Music Player 技术深度解析:基于 Electron + Vue 3 的开源网易云第三方客户端
vue.js·electron·开源
怕浪猫2 天前
Electron 开发实战(七):网络通信与 API 集成全解
前端·javascript·electron
只会写bug的靓仔2 天前
我把 Electron+Go 的 LOL 战绩工具重写成 Tauri+Rust,安装包从 128 MB 砍到 5 MB
golang·rust·electron
怕浪猫3 天前
Electron 开发实战(六):系统交互与原生功能实战全解
前端·javascript·electron
techdashen4 天前
拆开任意 Electron 应用:从 Windows 安装包到 Discord 的私有更新协议
javascript·windows·electron
三声三视4 天前
Electron 窗口状态保存,我在鸿蒙 PC 上放弃了 electron-store
electron·arkts·鸿蒙
sTone873756 天前
Electron 进程架构模型
前端·electron