原理
Electron通过将Chromium和Node.js合并到同⼀个运⾏时环境中,并将其打包为Mac, Windows和Linux系统下的应⽤来实现这⼀⽬的。
Electron本质就是提供了一个浏览器的壳子,用于运行我们的web应用。
目前浏览器采用多线程架构:
其中:
-
浏览器主进程,实现浏览器的主要UI、负责和文件、网络等等操作系统底层接口对接通信。
-
渲染进程,每一个
tab
独立开一个渲染进程,核心进行web代码解析和渲染工作。 -
插件进程,负责浏览器插件的控制。
-
GPU进程,独立的进程负责处理GPU图像的渲染绘制。
这样设计架构的好处主要有两个:
-
保证每个tab独立进程,这样在某一个页面
crash
的时候,仅仅影响当前的Tab,而不至于让整个浏览器崩溃。这提升了软件的健壮性和用户体验。 -
有利于实现沙箱隔离的安全机制,基于进程可以很方便地控制不同页面之间的安全访问策略,确保每个
renderer
进程在自己单独的沙箱环境内安全地运行。
Electron
本身参考了这个架构的实现,将各个GUI窗口通过renderer
进程实现,交由chromium
来加载渲染,主进程集成Node.js
,负责与系统API交互,处理核心事务。
技术集成
Electron是一个集成项目,允许开发者使用前端技术开发桌面端应用。它做了如下几个重要的工作:
-
定制 Chromium,并把定制版本的 Chromium 集成在 Electron 内部;
-
定制 Node.js,并把定制版本的 Node.js 集成在 Electron 内部;
-
通过消息轮询机智打通 Node.js 和 Chromium 的消息循环;
-
通过 electron 的内置模块向开发者提供桌面应用开发必备的API;
其中Chromium 基础能力可以让应用渲染HTML(CSS)页面,可以执行页面的JavaScript脚 本,让应用可以在Cookie、LocalStorage或IndexedDB 中存取数据。除此之外,Electron还允许 开发者突破同源策略的限制:伪装请求,截获响应,修改session等。
Node.js 基础能力可以让开发者读写本地磁盘的文件、通过socket访问网络、创建和控制子进程等。除此之外,还修改了Node的加解密机制让Chromium的BoringSSL和Node的OpenSSL兼容的 更好,让Node.js可以加载asar压缩包内的文件等。
Electron 内置模块可以让开发者创建操作系统的托盘图标、访问操作系统的剪切板、屏幕信息、发送系统通知等,除此之外还提供了崩溃报告收集能力、性能问题追踪能力等。
另外,Electron继承了Chromium的多进程架构Q,也是分一个主进程多个渲染进程的。
大致目录
Electron
├── build/ - 构建相关
├── buildflags/ - feature flag
├── chromium_src/ - chromium的一份拷贝(源码仅包含build文件)
├── default_app/ - 默认启动时的app程序
├── docs/ -文档
├── lib/ - 使用JS/TS编写的模块
| ├── browser/ - 主进程初始化相关
| | ├── api/ - 主进程暴露的API,通过_linkedBinding调用C++模块
| ├── common/ - 主进程和渲染进程共用代码
| | └── api/ - 主进程和渲染进程共同暴露的API
| ├── renderer/ - 渲染进程初始化相关
| | ├── api/ - 渲染进程API
| | └── web-view/ - webview相关逻辑
├── patches/ - 关于依赖的一些patch,主要是chromium、node、v8的
├── shell/ - C++编写的模块
| ├── app/ - 入口
| ├── browser/ - 主进程相关
| | ├── ui/ - 系统UI组件的一些实现
| | ├── api/ - 主进程API实现
| | ├── net/ - 网络相关实现
| | ├── mac/ - Mac系统下的一些实现(OC实现)
| ├── renderer/ - 渲染进程相关
| | └── api/ - 渲染进程API实现
| └── common/ - 主进程渲染进程通用实现
| └── api/ - 主进程渲染进程通用API实现
└── BUILD.gn - 构建入口
进程
主进程
每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,这意味着它具有 require
模块和使用所有 Node.js API 的能力。
在 Electron
中,启动项目时运行的 main.js
脚本就是我们说的主进程。在主进程运行的脚本可以以创建 web
页面的形式展示 GUI
。
一个 Electron
应用有且只有一个主进程。并且创建窗口等所有系统事件都要在主进程中进行。
窗口管理
主进程的主要目的是使用 [BrowserWindow](https://link.juejin.cn/?target=https%3A%2F%2Fwww.electronjs.org%2Fzh%2Fdocs%2Flatest%2Fapi%2Fbrowser-window)
模块创建和管理应用程序窗口。
渲染器进程
每个 Electron 应用都会为每个打开的 BrowserWindow
( 与每个网页嵌入 ) 生成一个单独的渲染器进程。 洽如其名,渲染器负责 渲染 网页内容。 所以实际上,运行于渲染器进程中的代码是须遵照网页标准的。
这也意味着渲染器无权直接访问 require
或其他 Node.js API。 为了在渲染器中直接包含 NPM 模块,您必须使用与在 web 开发時相同的打包工具 (例如 webpack
或 parcel
)。
主进程和渲染进程之间的区别
主进程使用 BrowserWindow
实例创建网页。每个 BrowserWindow
实例都在自己的渲染进程中运行。当一个 BrowserWindow
实例被销毁后,相应的渲染进程也会被终止。
主进程管理所有页面和与之对应的渲染进程。每个渲染进程都是相互独立的,并且只关心他们自己的网页。
主进程和渲染进程之间通信
Electron
的主进程是在后台运行,对应 main.js
文件。而渲染进程是前端看到的,对应 index.html
文件。这个两个进程之间的通信首选 ipc
方式,因为它会在完成时返回,而不会阻止同一进程中的其他操作。
- LocalStorage, window.postMessage
在前端开发中,鉴于浏览器对本地数据有严格的访问限制,所以一般通过该两种方式进行窗口间的数据通讯,该方式同样适用于 Electron 开发中。然而因为 API 设计目的仅仅是为了前端窗口间简单的数据传输,大量以及频繁的数据通讯会导致应用结构松散,同时传输效率也值得怀疑。
- IPC (Inner-Process Communication)
Electron 中提供了 ipcRender
、ipcMain
作为主进程以及渲染进程间通讯的桥梁,该方式属于 Electron 特有传输方式,不适用于其他前端开发场景。Electron 沿用 Chromium 中的 IPC 方式,不同于 socket、http 等通讯方式,Chromium 使用的是命名管道 IPC ,能够提供更高的效率以及安全性。
渲染进程发异步消息给主进程
// 渲染进程脚本
const { ipcRenderer } = require('electron')
// 发送异步消息
btns[0].addEventListener('click', () => {
ipcRenderer.send('msg1', '这是一条来自于异步的消息')
})
// 监听消息
ipcRenderer.on('msg1Re', (ev, data) => {
console.info(data)
})
// 主进程脚本
const { ipcMain } = require('electron')
ipcMain.on('msg1', (ev, data) => {
console.info(data)
// 发送消息给渲染进程
ev.sender.send('msg1Re', '这是一条来自主进程的反馈消息')
})
渲染进程发同步消息给主进程
// 渲染进程脚本
const { ipcRenderer } = require('electron')
// 发送同步消息
btns[1].addEventListener('click', () => {
const result = ipcRenderer.sendSync('msg2', '这是一条来自于同步的消息')
console.info(result)
})
// 主进程脚本
const { ipcMain } = require('electron')
ipcMain.on('msg2', (ev, data) => {
console.info(data)
// 反馈消息
ev.returnValue = '这是一条来自主进程的同步反馈消息'
})
主进程发消息给渲染进程
// 主线程脚本
BrowserWindow.getFocusedWindow().webContents.send(
'mtp',
'主进程发送消息给渲染进程'
)
// 渲染进程脚本
ipcRenderer.on('mtp', (ev, data) => {
console.info(data)
})
渲染进程间通信
基于本地存储的渲染进程间通信
即采用 localStorage
机制
借助主进程,进行不同渲染进程间的通信
// 发起消息的渲染进程
ipcRenderer.send('mti', '这是条来自于 modal 的消息')
// 主进程
ipcMain.on('mti', (ev, data) => {
// 通过 id 获取到对应的渲染进程,然后消息传递
BrowserWindow.fromId(mainId).webContents.send('mti2', data)
})
// 接收消息的渲染进程
ipcRenderer.on('mti2', (ev, data) => {
console.info(data)
})
使用 sendTo
前提是要知道另一渲染窗口的 webContents 对应的 id 这边采用 global 存储窗口 ID
const modalMain = new BrowserWindow({
width: 200,
height: 200,
parent: BrowserWindow.fromId(mainId), // 这样关闭父窗口,则子窗口会一并关闭
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
})
global.sharedObject = {
modalMainWebContentsId: modalMain.webContents.id
}
然后在渲染进程里通过 getGlobal 来获取该 ID 值,并通过 sendTo 来发送消息
let sharedObject = getGlobal('sharedObject')
let modalMainWebContentsId = sharedObject.modalMainWebContentsId
ipcRenderer.sendTo(modalMainWebContentsId, 'do-some-work', 1)
这样其他渲染进程就通过监听 do-some-work 来获取消息
ipcRenderer.on('do-some-work', (e, data) => {
console.info(data)
})
实践
参看教程:
无vite版本:https://juejin.cn/post/6983843979133468708
vite版本: https://blog.csdn.net/yanxinyun1990/article/details/130944508
网友评论