Electron 核心原理与实践
快速创建 Electron 项目
- electron-forge
- 原理:基于浏览器的架构
环境安装
- Node.js: 22.x
- pnpm i electron@latest --save-dev
- 热更新工具:nodemon
基础示例
js
import { app, BrowserWindow } from 'electron'
// 创建一个窗口
const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 相对于当前文件的路径
preload: path.join(__dirname, 'preload.js'), // 加载预加载脚本
},
})
mainWindow.loadFile('index.html')
// 打开开发工具
mainWindow.webContents.openDevTools()
}
// 创建另外一个新的窗口
const createSecondWindow = () => {
const secondWindow = new BrowserWindow({
width: 400,
height: 300,
parent: mainWindow, // 浮动在父窗口
})
secondWindow.loadFile('second.html')
}
app.on('ready', createWindow)核心基础
- 进程 app:控制主进程,控制应用程序的生命周期
- 线程:渲染进程运行页面,主进程管理窗口和系统 API
- 预加载脚本 (preload):在渲染进程加载之前执行,用于安全地暴露主进程 API
进程间通信 (IPC)
单向通信:渲染进程 → 主进程
js
// preload.js
const { contextBridge, ipcRenderer } = require('electron')
// 暴露版本信息到渲染进程
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
})
// 方式1:渲染进程发送消息到主进程
ipcRenderer.send('message', 'ping')
// 渲染进程监听主进程的回复
ipcRenderer.on('reply', (event, arg) => {
console.log(arg) // 输出: 'pong'
})
// 方式2:通过 contextBridge 暴露方法
contextBridge.exposeInMainWorld('electron', {
setIpcRenderer: (msg) => {
ipcRenderer.send('message', msg)
},
})js
// 主进程接受渲染进程发送的消息
const { ipcMain } = require('electron')
ipcMain.on('message', (event, arg) => {
console.log(arg) // 输出: 'ping'
event.reply('reply', 'pong') // 回复渲染进程
})单向通信:主进程 → 渲染进程
js
// 主进程给渲染进程发消息
const { ipcMain, ipcRenderer } = require('electron')
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
win.webContents.send('message', 'ping') // 发送消息到渲染进程
// 在 preload 渲染进程监听主进程的回复
ipcRenderer.on('message', (event, arg) => {
console.log(arg) // 输出: 'pong'
})双向通信:invoke / handle
js
// 渲染进程给主进程发送消息:invoke 发送消息并等待主进程回复
ipcRenderer.invoke('message', 'ping').then((response) => {
console.log(response) // 输出: 'pong'
})
// 主进程接受渲染进程发送的消息: handle 响应
ipcMain.handle('message', (event, arg) => {
console.log(arg) // 输出: 'ping'
return 'pong' // 回复渲染进程
})沙箱与安全
- 渲染进程是一个独立的环境,不能直接访问主进程的变量和函数
- 渲染进程只能通过预加载脚本(preload.js)来与主进程通信
- 配置
nodeIntegration: true可以让渲染进程直接访问 Node.js API(不推荐)
js
// 主进程
const { app, BrowserWindow } = require('electron')
const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true, // 开启 nodeIntegration(不推荐)
},
})
mainWindow.loadFile('index.html')
}remote 模块
- 渲染进程可以通过 remote 模块来访问主进程的变量和函数
- 比如渲染进程可以通过 remote 模块来访问主进程的 BrowserWindow 类
js
// 渲染进程
const { remote } = require('electron')
const BrowserWindow = remote.BrowserWindow创建多个窗口
js
// 主进程
const { app, BrowserWindow } = require('electron')
const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
mainWindow.loadFile('index.html')
}
// 创建另外一个新的窗口
const createSecondWindow = () => {
const secondWindow = new BrowserWindow({
width: 400,
height: 300,
parent: mainWindow, // 浮动在父窗口,不加的时候是一个互不相干的窗口
modal: true, // 模态窗口,阻塞父窗口
frame: false, // 无边框窗口
})
secondWindow.loadFile('second.html')
}
app.on('ready', createWindow)
// 当所有窗口都关闭时退出应用
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})