官网:https://www.electronjs.org/zh/
安装依赖
初始化package.json
pnpm init
安装依赖
pnpm add -D electron
安装报错解决方案:https://blog.csdn.net/qq_38463737/article/details/140277803
1、打开npm的配置文件
# cmd 运行打开配置文件
npm config edit
2、在空白地方添加淘宝镜像,下面三个(缺什么补什么,但要是同一个公司单位的镜像)
registry=https://registry.npmmirror.com
electron_mirror=https://cdn.npmmirror.com/binaries/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
手动配置完重新安装即可
pnpm add -D electron
启动一个简单的项目
1、修改package.json
文件中的main
与scripts
配置段
其中author和description为必填
main 主进程脚本,这指向跟目录的main.js
start 为启动命令:electron .
{
"name": "Electron-补习所",
"version": "1.0.0",
"description": "electron-test",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"keywords": [],
"author": "wangfan",
"license": "ISC",
"devDependencies": {
"electron": "^32.1.2"
}
}
2、根目录新建main.js
const { app, BrowserWindow } = require("electron");
// 监听app的ready事件
app.on("ready", () => {
// 创建一个窗口
const win = new BrowserWindow({
width: 500, // 窗口宽度
height: 500, // 窗口高度
autoHideMenuBar: true, // 隐藏默认菜单
});
win.loadURL('https://www.electronjs.org/zh/'); // 加载线上url页面
// BrowserWindow的更多配置可参考文档:https://www.electronjs.org/zh/docs/latest/api/browser-window
});
这样一个简单的项目就启动了。
加载本地页面
根目录新建pages
文件夹,在里面写入index.html
、index.css
,正常的写一些内容main.js
文件修改,通过loadFile
方法加载本地页面
const { app, BrowserWindow } = require("electron");
// 监听app的ready事件
app.on("ready", () => {
const win = new BrowserWindow({
width: 500,
height: 500,
autoHideMenuBar: true,
alwaysOnTop: true
});
win.loadFile('./pages/index.html'); // 加载页面-指定路径
});
启动项目:pnpm run start
开发者工具,打开后会有一个内容安全策略警告
解决方法是配置CSP
,具体配置可上MDN
上查看:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP
配置完后重启项目,内容安全策略警告就会消失。
完善窗口行为
这里我将new BrowserWindow
封装为一个函数createWindow
,当应用ready(准备完毕)
后,调用createWindow
创建一个窗口。
const { app, BrowserWindow } = require("electron");
// 创建一个窗口-封装
function createWindow() {
// 创建一个窗口
const win = new BrowserWindow({
width: 800, // 窗口宽度
height: 500, // 窗口高度
autoHideMenuBar: true, // 隐藏默认菜单
});
win.loadFile("./pages/index.html"); // 加载页面
// BrowserWindow的更多配置可参考文档:https://www.electronjs.org/zh/docs/latest/api/browser-window
}
// 监听app的ready事件
app.on("ready", () => {
createWindow();
// window所有窗口关闭时,并且不是苹果系统,退出应用-管理窗口的生命周期
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
});
// 应用被激活时,窗口数量为0,自动创建一个窗口-管理窗口的生命周期
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
窗口行为分两种,windows
系统和mac
系统,在windows
系统中,所有窗口关闭后自动关闭应用,而mac
系统则是自动缩小到任务栏不会关闭应用。
所以这里需要做窗口行为管理,也就是官网所说的管理窗口的生命周期
自动启动应用
项目每次主进程(main.js
)修改后,应用都需要手动重启,非常麻烦,我们可以安装nodemon
来自动重启项目
pnpm i nodemon -D
在package.json
中配置
{
// 省略其它配置......
"scripts": {
"start": "nodemon --exec electron ." // 通过nodemon --exec来启动electron .
}
}
这样每次修改主进程内容后应用会自动重启
另外你也可以在文件中具体配置nodemon
,来实现主进程、页面内容修改后自动重启功能。
根目录新建:nodemon.json
{
"ignore": ["node modules", "dist"],
"restartable": "r",
"watch": ["*.*"],
"ext": "html,js,css"
}
主进程与渲染进程
在electron
中,主进程只有一个,渲染进程可以有n
个main.js
就是主进程,主进程是node
环境运行的
在html
中运行的js
就是渲染进程,渲染进程是在web
环境中运行的
新建一个js
文件,在index.html
中引入它
修改js
文件
// render.js
let btn = document.getElementById("btn1");
btn.addEventListener("click", () => {
alert("弹窗");
});
主进程(node
环境)与渲染进程(web
环境)作用的环境不同,他们之间是隔离开的,web
端不能使用nodeAPI
而node
也无法使用webAPI
。
web
端由于是沙箱环境,如果你想在web
端获取一些系统、环境、硬件的信息,可能就需要借助node
能力与系统交互,那么electron
就为我们提供了主进程与渲染进程之间的通信。
主进程与渲染进程之间通过预加载脚本通信,预加载脚本在渲染端(web环境
)运行,虽然预加载脚本在web
端运行,但是它可以访问一部分的nodeAPI
,预加载脚本就是主进程与渲染进程之间的桥梁。
根目录新建预加载脚本:preload.js
这样我们的目录结构就一目了然了,pages
文件夹下管理渲染进程,preload.js
管理预加载脚本,main.js
管理主进程
我们随便在preload.js
中写点东西打印
// preload.js
console.log('预加载脚本')
然后在主进程中引入预加载脚本,预加载脚本只能使用绝对路径,这里使用node
模块path
引入根目录下的preload.js
// main.js
const { app, BrowserWindow } = require("electron");
const path = require("path");
// 创建一个窗口-封装
function createWindow() {
const win = new BrowserWindow({
// 省略其它配置项...
webPreferences: {
preload: path.resolve(__dirname,'./preload.js'), // 加载预加载脚本,绝对路径
}
});
win.loadFile("./pages/index.html"); // 加载页面
}
// 监听app的ready事 件
app.on("ready", () => {
createWindow();
});
// 省略其它配置项...
启动项目后会发现窗口打印了预加载脚本的输出语句
应用的运行顺序是:主进程 -> 预加载脚本 -> 渲染进程
现在我们来看预加载脚本如何与渲染进程通信的
使用 contextBridge
来选择要从预加载脚本中暴露哪些 API
,通过exposeInMainWorld
向渲染器进程暴露一个全局的 window.abc
变量。
// preload.js
const { contextBridge } = require("electron");
// 向渲染进程暴露全局myAPI变量
contextBridge.exposeInMainWorld("myAPI", {
mytext: "这是暴露的变量,预加载进程可使用部分nodeAPI",
version: process.version
});
然后在渲染进程中打印window
你会发现暴露的myAPI
直接挂载在window
身上
若是挂在在window
身上,你可以直接获取myAPI
变量
btn.addEventListener("click", () => {
// alert("弹窗");
// console.log(window);
console.log(myAPI);
});
这样就做到了渲染进程访问预加载脚本的能力。
进程通讯(IPC)-重要
需求:点击按钮,在系统D盘创建一个hello.text文件,文件内容来自用户输入
渲染进程向主进程通信(单项)
先编写页面
<!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';"
/>
<title>Document</title>
<link rel="stylesheet" href="./index.css" />
</head>
<body>
<h1>欢迎光临</h1>
<button id="btn1">查看弹窗</button>
<br>
<br>
<hr>
<button id="setBtn">向D盘写入hello.txt</button>
<input type="text" id="input">
</body>
<script src="./render.js"></script>
</html>
// render.js
let btn = document.getElementById("btn1");
let setBtn = document.getElementById("setBtn");
let input = document.getElementById("input");
btn.addEventListener("click", () => {
console.log(myAPI);
});
// 设置写入按钮的点击事件
setBtn.onclick = () => {
// 调用预加载脚本中定义的函数saveFile并传入input的输入内容
myAPI.saveFile(input.value)
};
编写预加载脚本
在预加载脚本中,通过ipcRenderer
将消息发送到主进程创建的监听器
// preload.js
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("myAPI", {
mytext: "这是暴露的变量",
saveFile: (data) => {
// 通过ipcRenderer.send向主进程发送消息
ipcRenderer.send("file-save", data);
},
});
编写主进程
在主进程中,通过ipcMain
监听预加载脚本发送的消息,监听的时机在加载页面之前。
1、创建窗口
2、订阅预加载脚本
3、加载页面
// main.js
const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");
const fs = require("fs"); // 引入node的fs模块操纵文件
// 自定义的函数,用于写入文件
// 回调函数有两个参数:一个IpcMainEvent结构和传入的变量
// 但是这里event用不上,所以用_占位
function writeFile(_, data) {
// 在用户D盘下写入hello.txt文件,文件内容为data
fs.writeFileSync("D:/hello.txt", data);
}
// 创建一个窗口-封装
function createWindow() {
// 创建一个窗口
const win = new BrowserWindow({
width: 800, // 窗口宽度
height: 500, // 窗口高度
autoHideMenuBar: true, // 隐藏默认菜单
webPreferences: {
preload: path.resolve(__dirname, "./preload.js"), // 加载预加载脚本,绝对路径
},
});
// 订阅预加载脚本,在加载页面之前,调用自定义函数writeFile
ipcMain.on("file-save", writeFile);
win.loadFile("./pages/index.html"); // 加载页面
}
// 监听app的ready事 件
app.on("ready", () => {
createWindow();
// 省略其它...
});
根据上面的步骤,就可以在系统D盘
创建一个hello.txt
文件,内容为输入框的内容。
渲染进程向主进程通信(双向)
读取我们之前写入的hello.txt
文件内容
在页面中写好结构
<!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';"
/>
<title>Document</title>
<link rel="stylesheet" href="./index.css" />
</head>
<body>
<h1>欢迎光临</h1>
<button id="btn1">查看弹窗</button>
<br>
<br>
<hr>
<button id="setBtn">向D盘写入hello.txt</button>
<input type="text" id="input">
<br>
<br>
<hr>
<button id="getBtn">读取D盘中hello.txt的内容</button>
</body>
<script src="./render.js"></script>
</html>
修改render.js
的内容,同样的,调用预加载脚本中的自定义函数
// render.js
let btn = document.getElementById("btn1");
let setBtn = document.getElementById("setBtn");
let input = document.getElementById("input");
let getBtn = document.getElementById("getBtn");
btn.addEventListener("click", () => {
console.log(myAPI);
});
setBtn.onclick = () => {
console.log(input.value);
myAPI.saveFile(input.value);
};
// 读取D盘中hello.txt的内容
// 实际上ipcRenderer.invoke返回的是一个Promise,所以使用async-await获取
getBtn.onclick = async () => {
let txt = await myAPI.readFile();
console.log(txt);
};
在预加载脚本中,通过invoke
来向主进程暴露请求
// preload.js
const { contextBridge, ipcRenderer } = require("electron");
console.log("预加载脚本");
contextBridge.exposeInMainWorld("myAPI", {
mytext: "这是暴露的变量",
saveFile: (data) => {
ipcRenderer.send("file-save", data);
},
// 自定义读取函数
readFile: () => {
// 通过ipcRenderer.invoke向主进程发送请求
// 并将请求回来的值返回出去,ipcRenderer.invoke返回的是一个Promise
return ipcRenderer.invoke("file-read");
},
});
在主进程中监听invoke
请求并执行自定义函数操作,最后将结果返回
通过ipcMain.handle
监听invoke
事件
const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");
const fs = require("fs");
function writeFile(_, data) {
fs.writeFileSync("D:/hello.txt", data);
}
// 自定义函数,读取D盘下的hello.txt文件内容,并返回
function readFile() {
// 利用node的fs模块操作文件,readFileSync读取文件,toString将buffer转为字符串返回
return fs.readFileSync("D:/hello.txt").toString();
}
// 创建一个窗口-封装
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 500,
autoHideMenuBar: true,
webPreferences: {
preload: path.resolve(__dirname, "./preload.js"), // 加载预加载脚本,绝对路径
},
});
// 订阅预加载脚本,在加载页面之前
// 监听预加载脚本send事件
ipcMain.on("file-save", writeFile);
// 监听预加载脚本invoke事件
ipcMain.handle("file-read", readFile);
win.loadFile("./pages/index.html"); // 加载页面
}
// 监听app的ready事 件
app.on("ready", () => {
createWindow();
});
打包应用
pnpm install electron-builder -D
在package.json
中配置
{
"name": "original-electron",
"version": "1.0.0",
"description": "original electron",
"main": "main.js",
"scripts": {
"start": "nodemon --exec electron .",
"build": "electron-builder" // 打包命令
},
"build": {
"appId": "myelectron-app", // 应用程序唯一标识符
"win": {
"icon": "./logo.ico", // 应用图标
"target": [
{
"target": "nsis", // 指定使用 NSIS 作为安装程序格式
"arch": ["x64"] // 生成64位安装包
}
]
},
"nsis": {
"oneClick": false, // 设置为 false 使安装程序显示安装向导界面,而不是一键安装
"perMachine": true, // 允许每台机器安装一次,而不是每个用户都安装
"allowToChangeInstallationDirectory": true // 允许用户在安装过程中选择安装目录
}
},
}
运行打包命令
pnpm run build
目录中会生成dist
文件夹,内部就打包的产物
参考文档:
安装依赖失败:https://blog.csdn.net/qq_38463737/article/details/140277803
官网:https://www.electronjs.org/zh/
electron-vite脚手架:https://cn.electron-vite.org/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。