electron是基于node.js开发的桌面端应用开发工具
npm init
init
初始化命令会提示您在项目初始化配置中设置一些值 为本教程的目的,有几条规则需要遵循:
entry point
应为main.js
.author
与description
可为任意值,但对于应用打包↗是必填项。
你的 package.json
文件应该像这样:
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"author": "Jane Doe",
"license": "MIT"
}
然后安装electron包
npm install electron --save-dev
—save-dev作用: npm 会将该包添加到 devDependencies
部分的 package.json
文件中。这意味着该依赖包仅在开发环境中需要,而在生产环境中不需要,可以有效隔离环境。 使用 —save(或省略该标志),npm 会将依赖包添加到 dependencies
部分,这意味着该包在生产环境中也是必需的。 npm 用不了也可以用其他的 pnpm
,cnpm(这个确实可以使用)
"scripts": {
"start": "electron --no-sandbox"
},
对于wsl中,因为是root用户,需要使用 --no-sandbox
来解决,作用是关闭沙盒,会影响安全性 这样做可能会引入安全风险,因为禁用沙箱会降低隔离性。 其他情况下不需要 --no-sandbox
。
语法
const {app, BrowserWindow } = require('electron')
function createWindow() {
const win = new BrowserWindow({
width:800,
height:600,
autoHideMenuBar: true,
alwaysOnTop:true,
x:0,
y:0
})
win.loadFile('./a.html')
}
app.on('ready', ()=>{
createWindow()
})
解析
const { app, BrowserWindow } = require('electron')
注册一个app和BrowserWindow, app是项目的入口,BrowserWindow创建一个窗口。
app.on('ready', ()=>{
createWindow()
})
这里在app,状态为ready时,触发回调函数,这里为箭头函数,函数里调用了createWindow()函数
function createWindow() {
const win = new BrowserWindow({
width:800,
height:600,
autoHideMenuBar: true,
alwaysOnTop:true,
x:0,
y:0
})
win.loadFile('./a.html')
}
自定义createWindow()函数,创建一个BrowserWindow的实例化,并设置配置项。width:宽度,height:高度,x,y:窗口出现的位置,autoHideMenuBar:自动隐藏菜单栏,alwaysOnTop:始终页面图层置顶 更多设置在Electron-BrowserWindow↗
win.loadFile(’./a.html’)这里加载一个本地页面 win.loadURL(”)可以加载一个网页
![[Pasted image 20241223230357.png]]
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;">
安全策略,引入外部资源必须是同源的,引入的样式表必须是同源的或是行内样式,引入的图片必须是同源的或是使用data:URI来嵌入图像。 [[js_基础1#使用data URI实现插入图片内容]]
系统差别的细节化操作
app.on('window-all-close', ()={
if (process.platform !== 'darwin') app.quit()
})
这里判断app的状态,当所有页面都关闭时,触发回调函数。如果平台不是苹果,卸载(关闭)app
app.on('activate', ()=>{
if (BroserWindow.getAllWindows().length === 0) createwindow()
})
(苹果系统) 当应用被激活时,如果目前没有窗口,就创建一个窗口。
配置自动重启
cnpm i nodemon -D
下载nodemon,配置自动重启项
{
"ignore": [
"node_modules",
"dist"
],
"restartable": "r",
"watch": ["*.*"],
"ext": "html,js,css"
}
这里解释一下各个配置项。 ignore,忽略node_modules和dist文件夹。 watch,监视 ext,指明文件类型为html,js,css restartable,使用字母r可以重启
这里只有main.js为主进程,其他js文件均为渲染进程 在主加载进程与渲染进程中,还有一个预加载脚本,协调两个进程脚本
预加载脚本
预加载脚本需要在main.js里面修改。
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow() {
const win = new BrowserWindow({
width:800,
height:600,
autoHideMenuBar: true,
alwaysOnTop:true,
webPreferences:{
preload:path.resolve(__dirname,'./preload.js')
}
})
win.loadFile('pages/index.html')
}
const path = require('path')
webPreferences:{
preload:path.resolve(__dirname,'./preload.js')
}
这里使用了相对路径,找到了当前文件夹下的preload.js文件。并作为预加载脚本,在启动渲染进程时运行。
部分解决:使用预加载脚本
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
version:process.version
})
const btn1 = document.getElementById('btn1')
btn1.onclick = ()=>{
alert(myAPI.version)
}
解释一下这里的两个脚本。preload.js即预加载脚本。通过 const { contextBridge } = require('electron')
来引入contextBridge。 然后通过 contextBridge.exposeInMainWorld
方法来实现部分通信的传递。例如这里可以通过定义 myAPI
来向render.js传递内部的信息。这里process.version是调出了node的版本信息。而在render.js中,只需要通过定义好的 myAPI
中的 version
即可调用出对应的信息。
问题:这里只能传递一部分的信息,无法完整做到进程通信。
进程通信
进程通信主要指主进程与渲染进程之间的通信关系。而这种通信主要通过预加载脚本进行实现,在预加载脚本中,
渲染向主进程单向通信
这里举一个例子
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
version:process.version,
saveFile:(data)=>{
ipcRenderer.send('file-save', data)
}
})
这里是预加载脚本,需要介绍的是 ipcRenderer
,这个是负责通信传递的,在上文提到的 contextBridge
通信方法下,自定义新的方法——saveFile,接收一个data参数,交给 ipcRenderer
的send方法,进行传递, send
方法接收两个参数,信道与数据,信道在后面的 ipcMain
中还要使用
const btn1 = document.getElementById('btn1')
const input = document.getElementById('input')
const btn2 = document.getElementById('btn2')
btn2.onclick = ()=>{
myAPI.saveFile(input.value)
}
在render.js即渲染进程中,只需要像之前那样,调用 contextBridge
中定义好的方法,通过定义的 myAPI
的 saveFile
方法传递参数
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')
function writeFile(_, data) {
fs.writeFileSync('./hello.txt', data)
}
function createWindow() {
const win = new BrowserWindow({
width:800,
height:600,
autoHideMenuBar: true,
webPreferences:{
preload:path.resolve(__dirname,'./preload.js')
}
})
ipcMain.on('file-save', writeFile)
win.loadFile('pages/index.html')
}
在main.js即主进程中,需要介绍的是 ipcMain
,这是对应 ipcRender
用来接收数据的,ipcMain.on('file-save', writeFile)
,这里 ipcMain.on
接收两个参数,一个是上面提到的信道,另一个是回调函数。接着,是回调函数的部分,这里的回调函数接收两个参数,event和data。前者是js基本功部分暂时不考虑,后者是我们需要传递的data数据。 这样一个单项传递就可以结束了。
渲染进程向主进程双向通信
这里拓展上面的例子,从文件写入后进行读取并返回
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')
function writeFile(_, data) {
fs.writeFileSync('./hello.txt', data)
}
function readFile() {
return fs.readFileSync('./hello.txt').toString()
}
function createWindow() {
const win = new BrowserWindow({
width:800,
height:600,
autoHideMenuBar: true,
webPreferences:{
preload:path.resolve(__dirname,'./preload.js')
}
})
ipcMain.on('file-save', writeFile)
ipcMain.handle('file-read',readFile)
win.loadFile('pages/index.html')
}
这是mian.js即主进程。因为对应预加载脚本中使用的是 ipcRenderer
的 invoke
方法。所以这里需要对应使用 ipcMain.handle
来进行处理。然后是这个回调函数,这里不仅需要取出对应的内容,还需要将这个数据传递给渲染进程。所以这里使用 return
返回内容。而对应这个返回的内容,将在下面讲解
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
version:process.version,
saveFile:(data)=>{
ipcRenderer.send('file-save', data)
},
readFile(){
return ipcRenderer.invoke('file-read')
}
})
这是预加载脚本,需要注意的是,这里的 readFile
方法中使用的是 invoke
同时,这里的这个方法会返回一个data数据,即上文提到的从文件中取出的内容。而且,这里 invoke方法
返回的数据是一个 Promise
数据,需要使用异步操作来进行处理才能拿到最后的数据。为了文件整洁,将异步操作留在渲染进程处理
const btn1 = document.getElementById('btn1')
const input = document.getElementById('input')
const btn2 = document.getElementById('btn2')
const btn3 = document.getElementById('btn3')
btn1.onclick = ()=>{
alert(myAPI.version)
}
btn2.onclick = ()=>{
myAPI.saveFile(input.value)
}
btn3.onclick = async ()=>{
let data = await myAPI.readFile()
alert(data)
}
渲染进程中只有简单的操作,使用异步操作读取到对应的数据即可。
主进程到渲染进程
可以不要使用,使用上面的双向即可。 原理是从主进程使用 send
方法,然后在渲染进程中 .on
来接收
渲染进程向渲染进程
不能直接传递,需要主进程作为中间人进行传递。渲->主->渲
打包
"scripts": {
"start": "nodemon --exec electron --no-sandbox .",
"build": "electron-build"
},
"build": {
"appId": "com.yang", //程序唯一标识符
"win": { //windows配置
"icon": "", //图标
"target": [
{
"target": "nsis", //使用nsis作为安装程序格式,生成的是exe安装程序(非msi)
"arch": ["x64"] // 生成64位安装包
}
]
},
"nsis": {
"oneclick": false, //设置位false,是安装程序显示安装向导界面,而不是一键安装
"perMachine": true, //允许每一台机器安装一次,而不是每一个用户安装一次
"allowToChangeInstallationDirectory": true // 允许用户选择安装位置
}
},
scripts部分修改,build部分添加,在执行时删掉注释。