1. 什么是 Electron?
2. Electron 的优势
3. Electron 技术架构
3.1. 技术架构
3.2. 进程模型

4. 搭建⼀个⼯程
{
"name": "test",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron ." //start命令⽤于启动整个应⽤
},
"author": "tianyu", //为后续能顺利打包,此处要写明作者。
"license": "ISC",
"description": "this is a electron demo", //为后续能顺利打包,此处要编写描述。
}
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()
})
npm start

Electron
客户端,桌面应用,安装在电脑里面用的,而不是手机里面用的,比如视频编辑和头像处理软件
这两个需要补全,否则无法打包
- 入口点 应当是
main.js
(您很快就会创建它) - author、license 和 description 可以是任何值,但在稍后的packaging中是必需的。
初次打开项目,会有默认的文件
autoHideMenuBar: true //自动隐藏菜单栏
alwaysOnTop: true
一打开页面从左面的x:0,y:0开始
ctrl+shift+i 调出控制台
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')
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; st
yle-src 'self' 'unsafe-inline'; img-src 'self' data:;">
6. 完善窗⼝⾏为
// 当所有窗⼝都关闭时
app.on('window-all-closed', () => {
// 如果所处平台不是mac(darwin),则退出应⽤。
if (process.platform !== 'darwin') app.quit()
})
// 当app准备好后,执⾏createWindow创建窗⼝
app.on('ready',()=>{
createWindow()
// 当应⽤被激活时
app.on('activate', () => {
//如果当前应⽤没有窗⼝,则创建⼀个新的窗⼝
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
7. 配置⾃动重启
npm i nodemon -D
scripts": {
"start": "nodemon --exec electron ."
},
{
"ignore": [
"node_modules",
"dist"
],
"restartable": "r",
"watch": ["*.*"],
"ext": "html,js,css"
}
8. 主进程与渲染进程
下图是 Chrome 浏览器的程序架构,图来⾃于Chrome 漫画。
Electron 应⽤的结构与上图⾮常相似,在 Electron 中主要控制两类进程:主进程、渲染器进程。
8.1. 主进程
8.2. 渲染进程
9. Preload 脚本
const {contextBridge} = require('electron')
// 暴露数据给渲染进程
contextBridge.exposeInMainWorld('myAPI',{
n:666,
version:process.version
})
const win = new BrowserWindow({
/*******/
webPreferences:{
preload:path.resolve(__dirname,'./preload.js')
}
/*******/
})
<body>
<h1>你好啊!</h1>
<button id="btn">在⽤户的D盘创建⼀个hello.txt</button>
<script type="text/javascript" src="./render.js"></script>
</body>
btn.addEventListener('click',()=>{
console.log(myAPI.version)
document.body.innerHTML += `<h2>${myAPI.version}</h2>`
})
渲染进程(n个)与主进程(1个,node环境下运行)
预加载脚本是在渲染进程执行的,只能执行一部分api
预加载先执行,渲染进程后执行
preload.js
contextBridge.exposeInMainWorld('abc',{
xyz:100
})
预加载脚本不能访问__dirname,会报错
preload.js
contextBridge.exposeInMainWorld('myAPI',{
version: process.version,
xyz: __dirname
})
render.js
btn1.onclick = () =>{
// alert('你点我了',process.version)
console.log(myAPI.xyz); //报错如下
}
10. 进程通信(IPC)
- IPC 全称为: InterProcess Communication ,即:进程通信。
- IPC 是 Electron 中最为核⼼的内容,它是从 UI 调⽤原⽣ API 的唯⼀⽅法!
- Electron 中,主要使⽤ ipcMain和 ipcRenderer来定义“通道”,进⾏进程通信。
10.1. 渲染进程➡️主进程(单向)
渲染进程向主进程单向通信
1. ⻚⾯中添加相关元素, render.js 中添加对应脚本
index.html
<input id="content" type="text"><br><br>
<button id="btn">在⽤户的D盘创建⼀个hello.txt</button>
render.js
const btn = document.getElementById('btn')
const content = document.getElementById('content')
btn.addEventListener('click',()=>{
console.log(content.value)
myAPI.saveFile(content.value)
})
2. preload.js 中使⽤ ipcRenderer.send('信道',参数) 发送消息,与主进程通信。
preload.js
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
/*******/
saveFile(str){
// 渲染进程给主进程发送⼀个消息
ipcRenderer.send('create-file',str)
}
})
// ⽤于创建窗⼝
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. 渲染进程↔主进程(双向)
<button id="btn">读取⽤户D盘的hello.txt</button>
render.js
const btn = document.getElementById('btn')
btn.addEventListener('click',async()=>{
let data =
document.body.innerHTML += `<h2>${data}</h2>`
})
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
/*******/
readFile (path){
return ipcRenderer.invoke('read-file')
}
})
// ⽤于创建窗⼝
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. 主进程到➡️渲染进程
window.onload = ()=>{
myAPI.getMessage(logMessage)
}
function logMessage(event,str){
console.log(event,str)
}
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
/*******/
getMessage: (callback) => {
return ipcRenderer.on('message', callback);
}
})
// ⽤于创建窗⼝
function createWindow() {
/**********/
// 加载⼀个本地⻚⾯
win.loadFile(path.resolve(__dirname,'./pages/index.html'))
// 创建⼀个定时器
setTimeout(() => {
win.webContents.send('message','你好啊!')
}, 6000);
}
11. 打包应⽤
npm install electron-builder -D
备注: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

扩展
渲染进程与渲染进程通信借助于主进程
总体代码:
main.js
console.log('main');
// BrowserWindow ⽤于创建窗⼝
const { app, BrowserWindow, ipcMain } = require("electron");
const path = require('path')
const fs = require('fs')
function writeFile(_,data){
console.log(_,data);
fs.writeFileSync('D:/hello.txt',data)
}
function readFile(){
const res = fs.readFileSync('D:/hello.txt').toString()
// console.log('###', res);
return res
}
const createWindow = () => {
// ⽤于创建窗⼝
const win = new BrowserWindow({
width: 800,
height: 600,
autoHideMenuBar: true,
// x: 0,
// y: 0,
// alwaysOnTop: true,
webPreferences:{
preload:path.resolve(__dirname,'./preload.js')
}
});
ipcMain.on('file-save', writeFile),
ipcMain.handle('file-read', readFile),
//win.loadURL('http://www.atguigu.com')
win.loadFile("./pages/index.html");
};
// console.log(process.version); //node
// console.log(process.versions.chrome); //chrome
// console.log(process.versions.node); //node
// console.log(process.versions.electron); //electron
// 当app准备好后,执⾏createWindow创建窗⼝
app.on("ready", () => {
console.log('应用准备完毕了');
createWindow()
// 当应⽤被激活时
app.on("activate", () => {
// 如果当前应⽤没有窗⼝,则创建⼀个新的窗⼝
if (BrowserWindow.getAllWindows().length === 0) createWindo();
});
});
// 当所有窗⼝都关闭时(Windows & Linux)
app.on("window-all-closed", () => {
// // 如果所处平台不是mac(darwin),则退出应⽤, app.quit()
if (process.platform !== "darwin") app.quit();
});
preload.js
// 预加载教程,作为主进程与渲染进程的桥梁
console.log('preload',process.version);
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
version: process.version,
// xyz: __dirname //报错
saveFile:(data)=>{
// ipcRenderer.send(信道, 数据)
ipcRenderer.send('file-save', data) //对应主进程的on
},
readFile(){
// invoke的返回值永远是promise
// let x = await ipcRenderer.invoke('file-read') //对应主进程的handle
// console.log('@@@@@@',x);
return ipcRenderer.invoke('file-read') //对应主进程的handle
}
})
nodemon.json
{
"ignore": [
"node_modules",
"dist"
],
"restartable": "r",
"watch": ["*.*"],
"ext": "html,js,css"
}
package.json
{
"name": "electron_test",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "nodemon --exec electron .",
"test": "echo \"Error: no test specified\" && exit 1",
"build": "electron-builder"
},
"build": {
"appId": "com.atguigu.video",
"win": {
"icon": "./logo.ico",
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
]
},
"nsis": {
"oneClick": false,
"perMachine": true,
"allowToChangeInstallationDirectory": true
}
},
"author": "wen",
"license": "ISC",
"description": "this is a electron demo",
"devDependencies": {
"electron": "^36.1.0",
"electron-builder": "^26.0.12",
"nodemon": "^3.1.10"
}
}
pages/render.js
console.log('render');
const btn1 = document.getElementById('btn1')
const btn2 = document.getElementById('btn2')
const btn3 = document.getElementById('btn3')
const input = document.getElementById('input')
btn1.onclick = () =>{
// alert('你点我了',process.version)
console.log(myAPI.version);
}
btn2.onclick = () =>{
// 文件内容是input.value,输入什么就是什么
// 调用之后就找主进程,通过预加载脚本来找
myAPI.saveFile(input.value)
}
btn3.onclick = async () =>{
let data = await myAPI.readFile(input.value)
alert(data)
}
pages/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;">
<link rel="stylesheet" href="./index.css">
<title>index</title>
</head>
<body>
<h1>欢迎学习Electron开发!!!!!</h1>
<button id="btn1">点我</button>
<br/>
<br/>
<hr>
<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>
pages/index.css
h1{
background-color: gray;
color: orange;
}
打包之后文件
双击.exe文件可以安装
安装之后的文件