Electron入门及原理浅析
原理
Electron通过将Chromium和Node.js合并到同⼀个运⾏时环境中,并将其打包为Mac, Windows和Linux系统下的应⽤来实现这⼀⽬的。
Electron本质就是提供了一个浏览器的壳子,用于运行我们的web应用,但是我们的代码具有更强大的功能。 JavaScript 可以访问文件系统,用户 shell 等。 这允许您构建更高质量的本机应用程序,但是内在的安全风险会随着授予您的代码的额外权力而增加。同时也内置了Nodejs环境,因此我们的页面也可以调用node的api。
进程
两种进程:主进程和渲染进程(类似于浏览器,一个tab窗口就是一个渲染进程控制,然后会有一个主进程管理所有的这些渲染进程)
主进程
每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,这意味着它具有 require
模块和使用所有 Node.js API 的能力。
窗口管理
主进程的主要目的是使用 BrowserWindow
模块创建和管理应用程序窗口。
BrowserWindow
类的每个实例创建一个应用程序窗口,且在单独的渲染器进程中加载一个网页。 您可从主进程用 window 的 webContent
对象与网页内容进行交互。
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')
const contents = win.webContents
console.log(contents)
由于 BrowserWindow
模块是一个 EventEmitter
, 所以您也可以为各种用户事件 ( 例如,最小化 或 最大化您的窗口 ) 添加处理程序。
当一个 BrowserWindow
实例被销毁时,与其相应的渲染器进程也会被终止。
生命周期
原生api
为了使 Electron 的功能不仅仅限于对网页内容的封装,主进程也添加了自定义的 API 来与用户的作业系统进行交互。 Electron 有着多种控制原生桌面功能的模块,例如菜单、对话框以及托盘图标。
关于 Electron 主进程模块的完整列表,请参阅我们的 API 文档。
职责
- 创建渲染进程(可多个)
- 控制了应⽤⽣命周期(启动、退出APP以及对APP做⼀些事件监听)
- 调⽤系统底层功能、调⽤原⽣资源
- 可调⽤的API:
-
- Node.js API
- Electron提供的主进程API(包括⼀些系统功能和Electron附加功能) electron主进程api
渲染器进程
每个 Electron 应用都会为每个打开的 BrowserWindow
( 与每个网页嵌入 ) 生成一个单独的渲染器进程。 洽如其名,渲染器负责 渲染 网页内容。 所以实际上,运行于渲染器进程中的代码是须遵照网页标准的 (至少就目前使用的 Chromium 而言是如此) 。
此外,这也意味着渲染器无权直接访问 require
或其他 Node.js API。 为了在渲染器中直接包含 NPM 模块,您必须使用与在 web 开发時相同的打包工具 (例如 webpack
或 parcel
)。(渲染进程理解为就是一个浏览器环境,所以我们的web代码是要经过编译打包才能正常运行)
职责:
- ⽤HTML和CSS渲染界⾯
- ⽤JavaScript做⼀些界⾯交互
- 可调⽤的API:
-
- DOM API Node.js
- API Electron提供的electron渲染进程api
预加载脚本
预加载(preload)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。 这些脚本虽运行于渲染器的环境中,却因能访问 Node.js API 而拥有了更多的权限。
主进程:
const { BrowserWindow } = require('electron')
//...
const win = new BrowserWindow({
webPreferences: {
preload: 'path/to/preload.js'
}
})
由于预加载脚本与渲染器共享同一个全局 Window
接口,并且可以访问 Node.js API,因此它通过在 window
全局中暴露任意您的网络内容可以随后使用的 API 来增强渲染器。
上下文隔离
上下文隔离是一项功能,可确保您的 preload
脚本和Electron的内部逻辑都在与您在 webContents
中加载的网站不同的上下文中运行。这对确保安全性很重要,因为它有助于防止网站访问electron内部或您的预加载脚本可以访问的强大API。
虽然预加载脚本与其所附加的渲染器在全局共享着一个 window
变量,但您并不能从中直接附加任何变数到 window
之中,因为 上下文隔离 是默认的。
上下文隔离 意味着预加载脚本与渲染器的主要运行环境是隔离开来的(虽然都是在渲染器环境中运行),以避免泄漏任何具特权的 API 到您的网页内容代码中,同理,也避免在网页修改了全局变量会影响预加载脚本。(目的就是为了避免网页和预加载脚本的相互影响,限制网页的权限)
取而代之,我们可以通过 contextBridge
模块来安全地实现交互
// 预加载脚本
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
doThing: () => ipcRenderer.send('do-a-thing')
}
)
// 渲染进程
window.electron.doThing()
进程通信
- LocalStorage, window.postMessage
在前端开发中,鉴于浏览器对本地数据有严格的访问限制,所以一般通过该两种方式进行窗口间的数据通讯,该方式同样适用于 Electron 开发中。然而因为 API 设计目的仅仅是为了前端窗口间简单的数据传输,大量以及频繁的数据通讯会导致应用结构松散,同时传输效率也值得怀疑。
- IPC (Inner-Process Communication)
Electron 中提供了 ipcRender
、ipcMain
作为主进程以及渲染进程间通讯的桥梁,该方式属于 Electron 特有传输方式,不适用于其他前端开发场景。Electron 沿用 Chromium 中的 IPC 方式,不同于 socket、http 等通讯方式,Chromium 使用的是命名管道 IPC ,能够提供更高的效率以及安全性。
- Remote( 不建议使用 )
Remote 模块为渲染进程和主进程通信提供了⼀种简单⽅法。 在Electron中, GUI 相关的模块 (如 dialog、menu 等) 仅在主进程中可⽤, 在渲染进程中不可 ⽤。 为了在渲染进程中使⽤它们, ipc 模块是向主进程发送进程间消息所必需的。 使⽤ remote 模块, 你可以调⽤ main 进程对象的⽅法, ⽽不必显式发送进程间消息 。
remote模块要慎用。Electron团队为什么要干掉remote模块
// 主进程
global.sharedObject = {
someProperty: 'default value'
}
// 渲染进程1
require('electron').remote.getGlobal('sharedObject').someProperty = 'new value'
// 渲染进程2
console.log(require('electron').remote.getGlobal('sharedObject').someProperty)
注意事项
模块加载
在渲染进程当中,使用node模块不能使用import方式引用。因为渲染进程属于浏览器端没有node的环境,就算集成了node的环境,import是会被webpack等构建工具解析作为node_modules依赖被打包进来。
- 方法一:需要使用require并且在主进程开启nodeIntegration:
// 渲染进程
const { ipcRenderer } = window.require('electron');
// 主进程
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
});
- 方法二:修改webpack配置,target设置为electron-renderer, 如果是vue.config.js:
module.exports = {
chainWebpack: (config) => {
config.target('electron-renderer');
},
};
然后在渲染进程直接使用import来引用我们的模块即可
谨慎地加载模块
我们需要谨慎加载 npm 模块,因为一个模块可能包含了超出实际所需的功能,而 require 模块消耗的时间相当可观。
加载模块是令人吃惊的繁重的操作,尤其是在Windows上。 当你的应用开始,不应该让用户等待当时不需要的操作。
优化手段:
- 打包压缩代码
- tree-shaking
- 延迟加载,初始化时只加载重要模块
- 减少不必要的依赖活模块
- 减少磁盘io
......
避免阻塞主进程
主进程是你应用的所有其他进程的父进程,也是和操作系统交互的关键进程。在任何情况下你都不应阻塞此进程或者运行时间长的用户界面线程。 阻塞UI线程意味着您的整个应用程序将冻结直到主进程准备好继续处理。
手段:
- 多进程处理cpu密集型
- 避免使用同步ipc和remote模块
- node核心模块,使用异步的方法
......
不要为远程内容启用 Node.js 集成
不管是使用BrowserWindow
,BrowserView
或者 <webview>
,最重要的就是绝对不要启用 Node.js 集成。 其目的是限制您授予远程内容的权限, 从而使攻击者在您的网站上执行 JavaScript 时难以伤害用户。
无可避免的白屏
即使 Electron 通常从本地文件系统加载 JavaScript 代码,没有网络加载延迟,我们还是需要继续和页面白屏做斗争,因为 JavaScript 等资源的加载、解析和执行还是有相当大的代价。作为一个桌面端应用,细微的白屏延迟用户都可以感觉的到。我们要尽量让用户感觉不到这是一个 Web 页面。
我们可以采用常规的优化web项目手段去优化我们的electron应用
IPC通信的优化
涉及到多页面/窗口的 Electron 应用,IPC 会非常频繁,搞不好会成为性能瓶颈。
而且要避免使用同步ipc通信,同步的ipc通信会阻塞主进程/渲染进程。
参考资料
转载自:https://juejin.cn/post/7029590294647537671