1. Electron是什么
Electron是一个跨平台桌面应用开发框架,开发者可以使用:HTML,CSS,JavaScript等Web技术来构建桌面应用程序,它的本质是结合了Chromium和Node.js,现在广泛用于桌面应用程序开发。2. Electron 的优势
1. 可跨平台:同⼀套代码可以构建出能在:Windows、macOS、Linux 上运⾏的应⽤程序。 2. 上⼿容易:使⽤ Web 技术就可以轻松完成开发桌⾯应⽤程序。 3. 底层权限:允许应⽤程序访问⽂件系统、操作系统等底层功能,从⽽实现复杂的系统交互。 4. 社区⽀持:拥有⼀个庞⼤且活跃的社区,开发者可以轻松找到⽂档、教程和开源库3. Electron 技术架构
3.1. 技术架构
3.2. 进程模型
4. 搭建⼀个⼯程
● 初始化⼀个包,并提填写好 package.json 中的必要信息及启动命令。{
"name":"test",
"version":"1.0.0",
"main":"main.js",
"scripts": {
"start": "electron ." //start命令⽤于启动整个应⽤
},
"author": "tianyu", //为后续能顺利打包,此处要写明作者。
"license": "ISC",
"description": "this is a electron demo", //为后续能顺利打包,此处要编写描述。
}
● 安装 electron 作为开发依赖。
npm i electron -D
● 在 main.js 中编写代码,创建⼀个基本窗⼝
/*
main.js运⾏在应⽤的主进程上,⽆法访问Web相关API,主要负责:控制⽣命周期、显示界⾯、控制渲染进程等其他操作
*/
const { app, BrowserWindow } = require('electron')
// ⽤于创建窗⼝
function createWindow() {
const win = new BrowserWindow({
width: 800, // 窗⼝宽度 height: 600, // 窗⼝⾼度
autoHideMenuBar: true, // ⾃动隐藏菜单栏
alwaysOnTop: true, // 置顶
x: 0, // 窗⼝位置x坐标
y: 0 // 窗⼝位置y坐标
})
// 加载⼀个远程⻚⾯
win.loadURL('http://www.atguigu.com')
}
// 当app准备好后,执⾏createWindow创建窗⼝
app.on('ready',()=>{
createWindow()
})
关于 BrowserWindow 的更多配置项,请参考:BrowserWindow实例属性
● 启动应⽤查看效果
npm start
● 效果如下:
**5.** 加载本地⻚⾯
● 创建 pages/index.html 编写内容:<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>index</title>
</head>
<body>
<h1>你好啊!</h1>
</body>
</html>
● 修改 mian.js 加载本地⻚⾯
// 加载⼀个本地⻚⾯
win.loadFile('./pages/index.html')
● 此时开发者工具会报出一个安全警告,需要修改index.html,配置CSP(Content-Security-Policy)
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; st yle-src 'self' 'unsafe-inline'; img-src 'self' data:;">
上述配置的说明
1. default-src __‘self’__****
default-src :配置加载策略,适⽤于所有未在其它指令中明确指定的资源类型。
self :仅允许从同源的资源加载,禁⽌从不受信任的外部来源加载,提⾼安全性。
2. style-src ‘self’ 'unsafe-*****inline’__*****
style-src :指定样式表(CSS)的加载策略。
self :仅允许从同源的资源加载,禁⽌从不受信任的外部来源加载,提⾼安全性。
unsafe-inline :允许在HTML⽂档内使⽤内联样式。
3. img-src ‘self’ data:
img-src :指定图像资源的加载策略。
self :表示仅允许从同源加载图像。
data: :允许使⽤ data: URI 来嵌⼊图像。这种URI模式允许将图像数据直接嵌
⼊到HTML或CSS中,⽽不是通过外部链接引⽤。
关于 CSP 的详细说明请参考:MDN-Content-Security-Policy、Electron Security
**6.** **完善窗⼝⾏为**
1,Windows 和 Linux 平台窗⼝特点是:关闭所有窗⼝时退出应⽤// 当所有窗⼝都关闭时
app.on('window-all-closed', () => {
// 如果所处平台不是mac(darwin),则退出应⽤。
if (process.platform !== 'darwin') app.quit()
})
2,mac 应⽤即使在没有打开任何窗⼝的情况下也继续运⾏,并且在没有窗⼝可⽤的情况下激活应⽤时会打开新的窗⼝。
// 当app准备好后,执⾏createWindow创建窗⼝
app.on('ready',()=>{
createWindow()
// 当应⽤被激活时
app.on('activate', () => {
//如果当前应⽤没有窗⼝,则创建⼀个新的窗⼝
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
**7.** 配置自动重启
1. 安装 Nodemonnpm i nodemon -D
- 修改 package.json 命令
"scripts": {
"start": "nodemon --exec electron ."
},
- 配置 nodemon.json 规则
{
"ignore": [
"node_modules",
"dist"
],
"restartable": "r",
"watch": ["*.*"],
"ext": "html,js,css"
}
配置好以后,当代码修改后,应⽤就会⾃动重启了
**8.** 主进程与渲染进程
下图是 Chrome 浏览器的程序架构,图来⾃于[Chrome 漫画](https://www.google.com/googlebooks/chrome/)。Electron 应⽤的结构与上图⾮常相似,在 Electron 中主要控制两类进程:主进程、渲染器进程
8.1. 主进程
每个 Electron 应⽤都有⼀个单⼀的主进程,作为应⽤程序的⼊⼝点。 主进程在 Node.js 环境中运⾏,它具有 require 模块和使⽤所有 Node.js API 的能⼒,主进程的核⼼就是:**使用BrowserWindow来创建和管理窗口**8.2. 渲染进程
每个 BrowserWindow 实例都对应⼀个单独的渲染器进程,运⾏在渲染器进程中的代码,必须遵守⽹⻚标准,这也就意味着:**渲染器进程无权直接访问 **`require`** 或使用任何 **`Node.js`** 的 **`API`问题产⽣:处于渲染器进程的⽤户界⾯,该怎样才与 Node.js 和 Electron 的原⽣桌⾯功能进⾏交互呢?
**9.** Preload 脚本
预加载(Preload)脚本是运⾏在渲染进程中的, 但它是在**网页内容加载之前**执⾏的,这意味着它具有⽐普通渲染器代码更⾼的权限,可以访问 Node.js 的 API,同时⼜可以与⽹⻚内容进⾏安全的交互。简单说:它是 Node.js 和 Web API 的桥梁,Preload 脚本可以安全地将部分 Node.js 功能暴露给⽹⻚,从⽽减少安全⻛险。
需求:点击按钮后,在网页呈现当前的 Node 版本。
具体⽂件结构与编码如下:
- 创建预加载脚本
preload.js
,内容如下:
const {contextBridge} = require('electron')
// 暴露数据给渲染进程
contextBridge.exposeInMainWorld('myAPI',{
n:666,
version:process.version
})
- 在主线程中引⼊
preload.js
const win = new BrowserWindow({
webPreferences:{
preload:path.resolve( dirname,'./preload.js')
}
})
- 在 html ⻚⾯中编写对应按钮,并创建专⻔编写⽹⻚脚本的
render.js
,随后引⼊。
<body>
<h1>你好啊!</h1>
<button id="btn">在⽤户的D盘创建⼀个hello.txt</button>
<script type="text/javascript" src="./render.js"></script>
</body>
- 在渲染进程中使⽤
version
btn.addEventListener('click',()=>{
console.log(myAPI.version)
document.body.innerHTML += `<h2>${myAPI.version}</h2>`
})
- 整体⽂件结构如下:
10.进程通信(IPC)
> 值得注意的是: > > 上⽂中的 `preload.js` ,⽆法使⽤全部 `Node` 的 `API` ,⽐如:不能使⽤ `Node` 中的 `fs` 模块,但主进程( `main.js` )是可以的,这时就需要_**进程通信**_了。简单说:要 > > 让 `preload.js` 通知 `main.js` 去调⽤ `fs` 模块去⼲活。 >关于 Electron
进程通信,我们要知道:
● IPC
全称为: InterProcess Communication
,即:进程通信。
● IPC
是 Electron
中最为核⼼的内容,它是从 UI
调⽤原⽣ API
的唯⼀⽅法!
Electron
中,主要使⽤ ipcMain 和 ipcRenderer 来定义“通道”,进⾏进程通信。
**10.1.** 渲染进程➡️主进程(单向)
概述:在$ {\color{red}渲染器进程} $中 [ipcRenderer.send](https://www.electronjs.org/zh/docs/latest/api/ipc-renderer) 发送消息,在$ {\color{red}主进程} $中使⽤ [ipcMain.on](https://www.electronjs.org/zh/docs/latest/api/ipc-main) 接收消息。常⽤于:$ {\color{red}在Web中调用主进程的API} $,例如下⾯的这个需求::::info
需求:点击按钮后,在⽤户的 D 盘创建⼀个 hello.txt ⽂件,⽂件内容来⾃于⽤户输⼊。
:::
- ⻚⾯中添加相关元素,
render.js
中添加对应脚本
<input id="content" type="text"><br><br>
<button id="btn">在⽤户的D盘创建⼀个hello.txt</button>
const btn = document.getElementById('btn')
const content = document.getElementById('content')
btn.addEventListener('click',()=>{
console.log(content.value)
myAPI.saveFile(content.value)
})
preload.js
中使⽤ ipcRenderer.send(‘信道’,参数) 发送消息,与主进程通信。
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
saveFile(str){
// 渲染进程给主进程发送⼀个消息
ipcRenderer.send('create-file',str)
}
})
- 主进程中,在加载⻚⾯之前,使⽤ ipcMain.on(‘信道’,回调) 配置对应回调函数,接收消息。
// ⽤于创建窗⼝
function createWindow() {
// 主进程注册对应回调
ipcMain.on('create-file',createFile)
// 加载⼀个本地⻚⾯
win.loadFile(path.resolve( dirname,'./pages/index.html'))
}
//创建⽂件
function createFile(event,data){
fs.writeFileSync('D:/hello.txt',data)
}
**10.2.** 渲染进程↔主进程(双向)
概述:$ {\color{red}渲染进程} $通过[ipcRenderer.invoke](https://www.electronjs.org/zh/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args) 发送消息,$ {\color{red}主进程} $使⽤ [ipcMain.handle](https://www.electronjs.org/zh/docs/latest/api/ipc-main#ipcmainhandlechannel-listener) 接收并处理消息。备注:
ipcRender.invoke
的返回值是Promise
实例。
常⽤于:从渲染器进程调用主进程方法并等待结果,例如下⾯的这个需求:
:::info
需求:点击按钮从 D 盘读取 hello.txt
中的内容,并将结果呈现在页面上。
:::
1. ⻚⾯中添加相关元素,<font style="color:rgb(37,37,37);">render.js</font>
中添加对应脚本
<button id="btn">读取⽤户D盘的hello.txt</button>
const btn = document.getElementById('btn')
btn.addEventListener('click',async()=>{
let data = await myAPI.readFile();
document.body.innerHTML += `<h2>${data}</h2>`
})
2. <font style="color:rgb(37,37,37);">preload.js</font>
中使⽤ipcRenderer.invoke('信道',参数) 发送消息,与主进程通信。
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
/*******/
readFile (path){
return ipcRenderer.invoke('read-file')
}
})
3. 主进程中,在加载⻚⾯之前,使⽤ipcMain.handle('信道',回调) 接收消息,并配置回调函数。
// ⽤于创建窗⼝
function createWindow() {
/**********/
// 主进程注册对应回调
ipcMain.handle('read-file',readFile)
// 加载⼀个本地⻚⾯
win.loadFile(path.resolve( dirname,'./pages/index.html'))
}
//读取⽂件
function readFile(event,path){
return fs.readFileSync(path).toString()
}
10.3. 主进程到➡渲染进程
概述: 主进程 使⽤ win.webContents.send 发送消息, 渲染进程 通过 ipcRenderer.on 处理消息,常⽤于:从主进程主动发送消息给渲染进程 ,例如下⾯的这个需求::::info
需求:应⽤加载 6 秒钟后,主动给渲染进程发送⼀个消息,内容是:你好啊!
:::
1. ⻚⾯中添加相关元素,render.js 中添加对应脚本
window.onload = ()=>{
myAPI.getMessage(logMessage)
}
function logMessage(event,str){
console.log(event,str)
}
2. <font style="color:rgb(37,37,37);">preload.js</font>
中使⽤ipcRenderer.on ('信道',回调) 接收消息,并配置回调函数。
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
/*******/
getMessage: (callback) => {
return ipcRenderer.on('message', callback);
}
})
3. 主进程中,在合适的时候,使⽤win.webContents.send('信道',数据) 发送消息。
// ⽤于创建窗⼝
function createWindow() {
/**********/
// 加载⼀个本地⻚⾯
win.loadFile(path.resolve( dirname,'./pages/index.html'))
// 创建⼀个定时器
setTimeout(() => {
win.webContents.send('message','你好啊!')
}, 6000);
}
10.4. 渲染进程➡渲染进程
概述: 渲染进程A 使用 `ipcRenderer.send` 发送消息,在 主进程 中使用 `iocMain.on` 接收 渲染进程A 的消息。 主进程 接收到 渲染进程A 的消息后使用 `win.webContents.send` 向 渲染进程B 发送消息, 渲染进程B 使用 `ipcRenderer.on` 处理消息。例如,如下需求上面的
win
为使用const win = new BrowserWindow({...})
创建 渲染进程B 返回的对象
:::info
需求:当前应用有两个窗口,indexA.html
和 indexB.html
。indexA.html
中点击按钮将值传递给 indexB.html
并展示
:::
indexA.html
中触发按钮
<input type="text" id="name" name="name">
<butto id='btn'> 同步 </butto>
const name = document.getElementById('name');
const btn = document.getElementById('btn');
btn.addEventListener('click',()=>{
console.log(name.value)
myAPI.saveName(name.value)
})
preload.js
中使用ipcRenderer.send()
发送消息,与 主进程 通信
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
saveName(str){
// 渲染进程给主进程发送⼀个消息
ipcRenderer.send('create-name',str)
}
})
- 主进程 中,在加载 渲染进程A 之前,使⽤
ipcMain.on
配置对应回调函数,接收消息。并且在配置的回调函数中,使用win.webContents.send
向 渲染进程B 发送消息,传递数据
const { app, BrowserWindow } = require('electron')
let winB = new BrowserWindow({
width: 800, // 窗⼝宽度 height: 600, // 窗⼝⾼度
autoHideMenuBar: true, // ⾃动隐藏菜单栏
alwaysOnTop: true, // 置顶
x: 0, // 窗⼝位置x坐标
y: 0 // 窗⼝位置y坐标
});
winB.loadFile('./pages/indexB.html');
let winA = new BrowserWindow({
width: 800, // 窗⼝宽度 height: 600, // 窗⼝⾼度
autoHideMenuBar: true, // ⾃动隐藏菜单栏
alwaysOnTop: true, // 置顶
x: 0, // 窗⼝位置x坐标
y: 0 // 窗⼝位置y坐标
});
// 主进程注册对应回调
ipcMain.on('create-name',(event,data)=>{
// 触发渲染进程B的监听事件
winB.webContents.send('message',data)
})
winA.loadFile('./pages/indexA.html');
preload.js
中使用ipcRenderer.on
接收消息,并配置回调函数
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
saveName(str){
// 渲染进程给主进程发送⼀个消息
ipcRenderer.send('create-name',str)
},
getName(callback){
return ipcRenderer.on('message', callback);
},
})
indexB.html
中接收消息,显示数据
window.onload = ()=>{
myAPI.getName(logMessage)
}
function logMessage(event,str){
console.log(event,str)
}
11. 打包应用
使⽤` electron-builder ` 打包应⽤1. 安装 electron-builder :
npm install electron-builder -D
2. 在<font style="color:rgb(37,37,37);">package.json</font>
中进⾏相关配置,具体配置如下:
备注:json ⽂件不⽀持注释,使⽤时请去掉所有注释。
{
"name": "video-tools", // 应⽤程序的名称
"version": "1.0.0", // 应⽤程序的版本
"main": "main.js", // 应⽤程序的⼊⼝⽂件
"scripts": {
"start": "electron .", // 使⽤ `electron .` 命令启动应⽤程序
"build": "electron-builder" // 使⽤ `electron-builder` 打包应⽤程序,⽣成安装包
},
"build": {
"appId": "com.atguigu.video", // 应⽤程序的唯⼀标识符
// 打包windows平台安装包的具体配置
"win": {
"icon":"./logo.ico", //应⽤图标
"target": [
{
"target": "nsis", // 指定使⽤ NSIS 作为安装程序格式
"arch": ["x64"] // ⽣成 64 位安装包
}
]
},
"nsis": {
"oneClick": false, // 设置为 `false` 使安装程序显示安装向导界⾯,⽽不是⼀键安装
"perMachine": true, // 允许每台机器安装⼀次,⽽不是每个⽤户都安装
"allowToChangeInstallationDirectory": true // 允许⽤户在安装过程中选择安装⽬录
}
},
"devDependencies": {
"electron": "^30.0.0", // 开发依赖中的 Electron 版本
"electron-builder": "^24.13.3" // 开发依赖中的 `electron-builder` 版本
},
"author": "tianyu", // 作者信息
"license": "ISC", // 许可证信息
"description": "A video processing program based on Electron" // 应⽤程序的描述
}
3. 执⾏打包命令
npm run build
12. electron-vite
electron-vite 是 ⼀个新 型构 建⼯具,旨 在为 [Electron](https://www.electronjs.org/) 提 供更 快 、 更 精 简的体 验 。 主 要由 五 部 分 组 成 :● ⼀套构建指令,它使⽤ Vite 打包你的代码,并且它能够处理 Electron 的独特环境,包括 Node.js 和浏览器环境。
● 集中配置主进程、渲染器和预加载脚本的Vite 配置,并针对Electron 的独特环境进⾏预配置。
● 为渲染器提供快速模块热替换(HMR)⽀持,为主进程和预加载脚本提供热重载⽀持,极⼤地提⾼了开发效率。
● 优化Electron 主进程资源处理。
● 使⽤ V8 字节码保护源代码。
electron-vite 快速、简单且功能强⼤,旨在开箱即⽤。官⽹地址:https://cn-evite.netlify.app/