介绍
Electron 基于 Chromium + Node.js
基本目录结构:
-index.html
-main.js
-render.js
-preload.js
-package.jsonelectron中主要分为 主进程和渲染进程。
- 主进程:main.js, 可在package.json中配置,相当于后台服务
- 渲染进程:每个窗口对应 1 个渲染进程,前端网页。
- 渲染脚本:render.js,是给渲染进程准备的js,用于与主进程通信,渲染数据等。一个窗口可以加载自己的渲染脚本,也可以多个窗口共享同一个渲染脚本。
- preload.js: 用于main与render之间的通信。两者之间也能直接通信,不安全
进程数量对应情况:
- 主进程:固定1个
- 渲染进程:每个窗口 1 个默认。每个渲染进程都有 Chromium 的渲染能力,所以可以理解为 3 个内核实例。Chromium 内部还有 GPU 进程和网络进程,它们可能是共享的
- GPU进程/网络进程等:不同窗口共享或独立,Chromium 内部使用
调用加载顺序:
1.package.json中配置指定main.js文件
入口相当于main函数:app.whenReady().then(createWindow);
main函数中创建BrowserWindow,注册 ipcMain 事件,加载index.html等操作
2.preload.js在browserwindow创建时,加载index.html之前执行,可访问 Node API 和 ipcRenderer,然后通过 contextBridge 向页面暴露有限 API。
3.渲染进程:index.html被加载渲染进程就启动,html+js(render.js)在渲染进程的DOM中执行。启用了 preload,页面脚本可以通过 window.api(或你暴露的对象)调用主进程通信接口。main与render通信
1.send和on
render调用main
render.js中:
const { ipcRenderer } = require('electron')
ipcRenderer.send('toMain', 'Hello from renderer')
main.js中:
const { ipcMain } = require('electron')
ipcMain.on('toMain', (event, message) => {
console.log('Received message from renderer:', message)
// 回复 renderer 进程
event.reply('fromMain', 'Hello from main')
})main调用render
// main.js
const { BrowserWindow } = require('electron')
// 拿到窗口实例
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true
}
})
// 发送消息到 renderer
win.webContents.send('fromMain', 'Hello Renderer!')
// renderer.js
const { ipcRenderer } = require('electron')
// 监听来自 main 的消息
ipcRenderer.on('fromMain', (event, msg) => {
console.log('收到主进程消息:', msg)
})2.invoke/handle
render调main
//main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
app.whenReady().then(() => {
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true
}
})
win.loadFile('index.html')
// 注册一个可被 renderer 调用的接口
ipcMain.handle('getVersion', async (event) => {
return app.getVersion()
})
})
//render.js
async () => {
const result = await ipcRenderer.invoke('getAppPath')
console.log("主进程返回:", result)
alert(result)
}main调render
//render.js
const { ipcRenderer } = require('electron')
// 渲染进程注册可被调用的方法
ipcRenderer.handle('sayHello', async (event, name) => {
console.log("Main 调用了 Renderer:", name)
return `Hello, ${name}, from Renderer!`
})
//main.js
app.whenReady().then(() => {
win = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
})
win.loadFile('index.html')
// 2 秒后调用渲染进程的方法
setTimeout(async () => {
const result = await win.webContents.invoke('sayHello', 'Main Process')
console.log('Renderer 返回:', result)
}, 2000)
})3.preload中间层
官方推荐 contextIsolation: true + preload.js 的做法。
简言之:render到main使用preload封装后暴露的方式,main到render依然在preload中封装(send/on形式)通信
双向通信demo
main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
app.whenReady().then(() => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
})
win.loadFile('index.html')
// Renderer 调用 Main
ipcMain.handle('getAppVersion', async () => {
return app.getVersion()
})
// Main 主动推送给 Renderer
setTimeout(() => {
win.webContents.send('notifyRenderer', 'Hello Renderer!')
}, 2000)
})
preload.js
const { contextBridge, ipcRenderer } = require('electron')
// 安全暴露 API 给 Renderer
contextBridge.exposeInMainWorld('api', {
// Renderer 调用 Main
getAppVersion: () => ipcRenderer.invoke('getAppVersion'),
// Main 主动推送事件订阅
onNotify: (callback) => ipcRenderer.on('notifyRenderer', (event, message) => {
callback(message)
}),
// Renderer 发送消息给 Main
sendMessage: (channel, message) => {
const validChannels = ['toMain']
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, message)
}
}
})
index.html
<!DOCTYPE html>
<html>
<body>
<h1>Electron Preload 双向通信</h1>
<button id="versionBtn">获取 App 版本</button>
<button id="sendBtn">发送消息给 Main</button>
<script>
// 调用 Main
document.getElementById('versionBtn').addEventListener('click', async () => {
const version = await window.api.getAppVersion()
alert('App Version: ' + version)
})
// 订阅 Main 的主动推送
window.api.onNotify((msg) => {
console.log('收到主进程消息:', msg)
})
// 发送消息给 Main
document.getElementById('sendBtn').addEventListener('click', () => {
window.api.sendMessage('toMain', 'Hello Main!')
})
</script>
</body>
</html>