Electron
Electron是什么
electron可以使用前端技术开发桌面应用,跨平台性,开发一套应用,可以打包到三个平台。
electron结合Chromium(谷歌内核)和 Node.js 和Native Api
当使用 Electron 时,很重要的一点是要理解 Electron 不是一个 Web 浏览器。 它允许您使用熟悉的 Web 技术构建功能丰富的桌面应用程序
官网:简介 | Electron
Electron广泛应用
使用Electron框架开发的知名软件。
Visual Studio Code、GithubDesktop、Postman、DockerDesktop...
Electron进程模型
主进程(main.js)。运行在node环境
一个应用只会有一个主进程,负责应用的生命周期、展示原生窗口、执行特殊操作和管理渲染进程。
渲染进程(render.js ),运行在浏览器环境
一个应用可以有多个渲染进程,渲染器进程负责展示图形内容。
快速上手
环境安装
node环境(v20.13.1)、npm环境(10.5.2)
测试安装是否成功,按下【win+R】键,输入cmd,打开cmd窗口
输入:node -v // 显示node.js版本
npm -v // 显示npm版本
npm config set registry https://registry.npmmirror.com/
npm i electron -D
main.js
main 文件是 Electron 应用的入口。 这个文件控制 主程序 (main process),它运行在 Node.js 环境里,负责控制您应用的生命周期、显示原生界面、执行特殊操作并管理渲染器进程 (renderer processes)
const { app, BrowserWindow, ipcMain, Notification, Menu } = require('electron/main')
const path = require('node:path')
const fs = require('fs')function writeFile(_, data) {fs.writeFileSync('D:/hello.txt', data)
}function readFile() {const res = fs.readFileSync('D:/hello.txt').toString();return res
}
const createWindow = () => {const win = new BrowserWindow({width: 1000,height: 800,icon: path.join(__dirname, 'favicon.ico'),title: '简单网页',webPreferences: {preload: path.join(__dirname, 'preload.js')}})ipcMain.on('file-save', writeFile)ipcMain.handle('file-read', readFile)//自定义菜单项let menuTemp = [{label: '文件',submenu: [{label: '打开文件',click() {console.log('打开一个具体的文件')}},{ label: '打开文件夹' },{label: '关于',role: 'about'}]},{ label: '编辑' }]//生成自定义菜单let menu = Menu.buildFromTemplate(menuTemp)Menu.setApplicationMenu(menu)win.loadFile('index.html')// 创建并显示通知const notification = new Notification({title: '主进程通知',body: '恭喜你,学会了求雨之术,风来~雨来~'}).show();// 确保在窗口创建后调用 openDevTools//win.webContents.on('did-finish-load', () => {// win.webContents.openDevTools();//});// 定时发送时间给渲染进程(每1秒)setInterval(() => {if (win && !win.isDestroyed()) {win.webContents.send('main-time', new Date().toLocaleTimeString());}}, 1000);
}
app.whenReady().then(createWindow)
index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';" />
</head><body><div id="time">当前时间:加载中...</div><div class="hint">注意:输入内容,可以保存到d:/hello.txt,点击读取,可以读取该文件内容</div><input id="input" type="text"><button id="btn2">向D盘输入hello.txt</button><br><br><hr><button id="btn3">读取D盘hello.txt</button><script type="text/javascript" src="./render.js"></script>
</body></html>
render.js
const timeElement = document.getElementById('time');
const btn2 = document.getElementById('btn2');
const btn3 = document.getElementById('btn3');
const input = document.getElementById('input');btn2.onclick = () => {myAPI.saveFile(input.value)
}btn3.onclick = async () => {let data = await myAPI.readFile()alert(data)
}// 监听主进程发送的时间消息
myAPI.onMainTime((time) => {timeElement.textContent = `当前时间:${time}`;
});
preload.js
preload.js执行时机:预加载脚本在渲染器加载网页之前注入
预加载脚本preload.js(中间人),它在渲染进程运行(浏览器环境),人家也能访问一部分的node api。
const { contextBridge, ipcRenderer } = require('electron')contextBridge.exposeInMainWorld('myAPI', {saveFile: (data) => {ipcRenderer.send('file-save', data)},readFile: () => {return ipcRenderer.invoke('file-read')},// 监听主进程发送的时间消息onMainTime: (callback) => {ipcRenderer.on('main-time', (event, time) => callback(time))}
})
ipc进程通信
remote模块
Electron 官方已经明确表示,remote
模块是一个遗留的 API,不建议在新项目中使用。 他们鼓励开发者迁移到 Context Bridge。
remote
模块安全风险: 正如之前提到的,remote
模块允许渲染进程直接访问主进程的几乎所有对象,这带来了严重的安全风险。 恶意代码可能会利用remote
模块来执行任意代码,甚至控制整个应用程序。
替代方案: Context Bridge (结合 ipcRenderer
和 ipcMain
) 提供了更安全、更可控的进程间通信方式。
[渲染进程]调用[主进程] 无返回
需求:比如在页面上向d盘的文件写内容
主进程 ipcMain.on,写在app.whenReady().then()中
ipcMain.on('file-save', writeFile)function writeFile(_, data) {fs.writeFileSync('D:/hello.txt', data)
}
ipcRender.send 通过预加载脚本暴露了一个单行的 saveFile函数。
const { contextBridge, ipcRenderer } = require('electron')contextBridge.exposeInMainWorld('myAPI', {saveFile: (data) => {ipcRenderer.send('file-save', data)}})
最后在渲染器进程,也就是HTML页面就可以使用暴露出来的saveFile方法了
const btn2 = document.getElementById('btn2');
const input = document.getElementById('input');btn2.onclick = ()=> {myAPI.saveFile(input.value)
}
[渲染进程]调用[主进程] 有返回
双向 IPC 的一个常见应用是从渲染器进程码调用代主进程模块并等待结果,更适合请求-响应模式
需求:在页面上读取d盘的文件内容,点击按钮弹出d盘hello.txt文件内容
主进程 ipcMain.handle,写在app.whenReady().then()中
ipcMain.handle('file-read', readFile)function readFile() {const res = fs.readFileSync('D:/hello.txt').toString();return res
}
ipcRender.invoke 通过预加载脚本暴露了一个单行的 readFile函数。
const { contextBridge, ipcRenderer } = require('electron')contextBridge.exposeInMainWorld('myAPI', {readFile: () => {return ipcRenderer.invoke('file-read')}})
最后在渲染器进程,也就是HTML页面就可以使用暴露出来的readFile方法了
const btn3 = document.getElementById('btn3');
btn3.onclick = async () => {let data = await myAPI.readFile()alert(data)
}
[主进程]调用[渲染进程]
需求:页面显示当前时间,每1秒更新一次,来自主进程定时推送
主进程:webContents.send
发送消息
// 定时发送时间给渲染进程(每1秒)setInterval(() => {if (win && !win.isDestroyed()) {win.webContents.send('main-time', new Date().toLocaleTimeString());}}, 1000);
ipcRenderer.on
预加载脚本监听主进程消息,
暴露出onMainTime函数给渲染进程
callback本质是渲染进程向预加载脚本“注册”的一个“数据接收器”,让预加载脚本能把主进程的数据“交给”渲染进程处理。
const { contextBridge, ipcRenderer } = require('electron')contextBridge.exposeInMainWorld('myAPI', {// 监听主进程发送的时间消息onMainTime: (callback) => {ipcRenderer.on('main-time', (event, time) => callback(time))}
})
渲染进程
渲染进程调用onMainTime时传递了一个callback
const timeElement = document.getElementById('time');//监听主进程发送的时间消息
myAPI.onMainTime((time) => {timeElement.textContent = `当前时间:${time}`;
});
打包应用
electron-builder
需要梯子
安装打包工具
npm install electron-builder -D
打包应用 electron-builder(做很多自定义的东西)
愉快执行:npm run build,安装包在dist目录,74MB,安装后包含node和chromiun
npm run build
electron-forge
安装打包工具
npm install --save-dev @electron-forge/cli
npx electron-forge import
执行打包
npm run make
执行成功后,默认输出目录为out/make
,里面会生成平台对应的分发文件,例如:
- Windows:
out/make/squirrel.windows/x64/my-app-1.0.0 Setup.exe
(安装包)、my-app-1.0.0-full.nupkg
(更新包)。 - macOS:
out/make/dmg/my-app-1.0.0.dmg
(磁盘镜像)、out/make/zip/my-app-1.0.0.zip
(压缩包)。 - Linux:
out/make/deb/x64/my-app_1.0.0_amd64.deb
(Debian包)、out/make/rpm/x64/my-app-1.0.0-1.x86_64.rpm
(RPM包)。