Electron 基础原理

双进程模型

主进程

单一应用入口(package.json main 字段)
在 Node.js 环境运行 (具有 require 模块和使用 Node API 能力)
负责

  • 创建和管理 窗口(BrowserWindow)、对话框(dialog)、菜单(Menu)、托盘图标(Tray)等调用 Electron 系统 API 的功能
  • 通过 ipcMain(主) / ipcRenderer(渲染) 模块与所有渲染进程进行双向通信,是 IPC 通信中枢

可用 window 的 webContents 对象

1
2
3
4
5
6
7
const { BrowserWindow } = require('electron')

const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')

const contents = win.webContents
console.log(contents)

渲染进程

每个 Electron 应用为每个打开的 BrowserWindow 生成单独的渲染进程
支持多实例(窗口),每个窗口对应独立的渲染进程
负责

  • 显示网页内容(HTML/CSS/JS)
  • 执行前端框架(Vue/React)

预加载脚本

通过在 BroserWindow 构造方法,把脚本附着于渲染器,从而添加到主进程

1
2
3
4
5
6
7
8
const { BrowserWindow } = require('electron')
// ...
const win = new BrowserWindow({
webPreferences: {
preload: 'path/to/preload.js'
}
})
// ...

与浏览器共享一个全局 Window 接口
可使用 Node.js API
可通过在全局 window 对象中暴露任意 API 供给渲染器使用

IPC 通信机制

主进程 -> 渲染进程

主进程: webContents.send(channel, …args) 向指定渲染进程发送消息
渲染进程:ipcMain.on(channel, listener) 监听消息

渲染进程 -> 主进程

渲染进程:ipcRenderer.send(channel, …arg) 向指定渲染进程发送消息
主进程: ipcMain.on(channel, listener) 监听消息

双向通信

主进程: ipcMain.handle
渲染进程:ipcRenderer.invoke

协作方式

(主进程)创建窗口

调用 new BrowserWindow() 创建新窗口

  • 指定要渲染的页面文件
  • 挂载脚本附着到渲染进程

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const { app, BrowserWindow } = require('electron')

function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js') // 预加载脚本
}
})

win.loadFile('index.html') // 页面文件
}

app.whenReady().then(() => {
createWindow() // 创建窗口
})

(主进程/渲染进程)进程通信

example(主->渲):通知渲染进程更新UI

main.js — webContents.send(channel, …args)

1
2
3
4
5
6
7
8
const { app, BrowserWindow } = require('electron')

let mainWindow

app.on('activate', () => {
if(BrowserWindow.getAllWindows().length === 0) createWindow()
mainWindow.webContent.send('app-activated')
})

renderer.js(UI层) — ipcMain.on(channel, listener)

1
2
3
4
5
const { app, BrowserWindow } = require('electron')

ipcRenderer.on('app-activated', () => {
document.body.style.backgroundColor = 'lightblue'
})

example(双向):按钮 -> 文件选择对话框

renderer.js (按钮) — ipcRenderer.invoke(channel, …arg)

1
2
3
4
5
6
7
const { ipcRenderer } = require('electron')

const handleClick = () => {
const result = ipcRenderer.invoke('open-dialog')
}

<button @click="handleClick"></button>

main.js (开文件选择框) — ipcMain.handle(channel, listener)

1
2
3
4
5
6
7
8
const { ipcMain, dialog } = request('electron')

ipcMain.handle('open-dialog', async (event) => {
const result = await dialog.showOpenDialog({
properties: ['openFile']
})
return result
})