什么是Electron
- 一款应用广泛的跨平台的桌面应用开发框架。
- Electron的本质是结合了 Chromium 与Node.js。
- 使用HTML、CSS、JS 等Web技术构建桌面应用程序。
- .vue,.tsx,.less,.ts也可以使用
Electron 流程模型
- 主进程是纯node环境,可以访问__dirname,fs模块等,不能访问alert(),window等
- 渲染进程可以访问alert(),window等,不能访问__dirname,fs模块等
- 一个主进程可以管理多个渲染进程,渲染进程能够与主进程沟通
- 主进程可以调用原生api
创建应用程序
Electron 应用程序遵循与其他 Node.js 项目相同的结构。 首先创建一个文件夹并初始化 npm 包。
bash
mkdir my-electron-app && cd my-electron-app
npm init
init
初始化命令会提示您在项目初始化配置中设置一些值 为本教程的目的,有几条规则需要遵循:
entry point
应为main.js
.author
与description
可为任意值,但对于应用打包是必填项。
你的 package.json
文件应该像这样:
javascript
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"author": "Jane Doe",
"license": "MIT"
}
然后,将 electron
包安装到应用的开发依赖中。
bash
npm install --save-dev electron
创建main.js
就可以通过npm start运行程序
简单程序制作
展示网页信息
javascript
//main.js
const {app,BrowserWindow} = require('electron')
app.on('ready',()=>{// 当app准备好的时候调用回调
const win = new BrowserWindow({//创建窗口
width:800,
height:600,
autoHideMenuBar:true,//隐藏默认配置菜单
x:0,
y:0,//在什么位置打开
alwaysOnTop:true//一直置顶,不被遮挡
})
win.loadURL('https://blog.csdn.net/m0_56772756?type=blog')//加载远程页面
})
展示个人页面
html
//html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<h1>学习electron开发</h1>
</body>
</html>
//index.css
h1{
background-color: aquamarine ;
color: purple;
}
javascript
const {app,BrowserWindow} = require('electron')
app.on('ready',()=>{// 当app准备好的时候调用回调
const win = new BrowserWindow({//创建窗口
width:800,
height:600,
autoHideMenuBar:true,//隐藏默认配置菜单
x:0,
y:0,//在什么位置打开
alwaysOnTop:true//一直置顶,不被遮挡
})
// win.loadURL('http://www.atguigu.com')//加载页面
// 加载文件
win.loadFile('./pages/index.html')
})
npm start执行程序
终端出现警告(可忽略)
内容安全策略
在页面中运行ctrl+shift+i查看开发者工具
出现警告内容安全策略(CSP)给html文件添加meta标签,配置CSP(Content-Security-Policy)
html
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self';style-src 'self' 'unsafe-inline';img-src 'self' ">
部分配置说明
- default-src 'self'
- default-src:配置加载策略,适用于所有未在其它指令中明确指定的资源类型。
- self:仅允许从同源的资源加载,禁止从不受信任的外部来源加载,提高安全性。
- style-src 'self' ' unsafe-inline
- style-src:指定样式表(CSS)的加载策略。
- self:仅允许从同源的资源加载,禁止从不受信任的外部来源加载,提高安全性。
- unsafe-inline :允许在HTML文档内使用内联样式。
- img-src 'self' data:
- img-src:指定图像资源的加载策略。
- self:表示仅允许从同源加载图像。
- data::允许使用data: URI来嵌入图像。这种URI模式允许将图像数据直接嵌入到HTML或CSS中,而不是通过外部链接引用。
了解更多查看:内容安全策略(CSP)
完善窗口行为
- Windows和Linux平台窗口特点是:关闭所有窗口时退出应用。
javascript
app.on('window-all-closed', () => {//当所有窗口都关闭的时候
if (process.platform !== 'darwin') app.quit()//如果不是苹果系统Mac(darwin),退出应用
})
- mac应用即使在没有打开任何窗口的情况下也继续运行,并且在没有窗口可用的情况下激活应用时会打开新的窗口。
javascript
//当app准备好后,执行createwindow创建窗口
app.on( 'ready' ,()=>{
createwindow()//当应用被激活时
app.on( 'activate', ()=>{
//如果当前应用没有窗口,则创建一个新的窗口
if (Browserwindow.getAllwindows().length === 0) createwindow()
})
})
更多窗口行为查看:BrowserWindow | Electron (electronjs.org)
配置自动重启
bash
npm i nodemon -d
修改package.json配置
javascript
"start": "nodemon --exec electron ."
此配置只实现了mian.js的修改触发自动重启,页面的修改不能实现自动重启
配置nodemon.json规则
javascript
{
"'ignore":[
"node_modules",
"dist"
],
"restartable":"r",
"watch":["*.*"],
"ext":"html,js,css"
}
"restartable":"r",是配置短命令 ,在终端命令行中输入r实现程序自动重启
配置好以后,当代码修改后,应用就会自动重启了。
electron控制台打印乱码
修改package.json文件
javascript
"start": "chcp 65001 && nodemon --exec electron ."
主进程与渲染进程
主进程与渲染进程个分隔开
主进程
每个Electron应用都有一个唯一的主进程,作为应用程序的入口点(main.js)。主进程在Node.js环境中运行,它具有require模块和使用所有Node.js API的能力,主进程的核心就是:使用BrowserWindow来创建和管理窗口。无法访问浏览器函数。
主进程(main.js)不能访问window、alert之类的
渲染进程
每个BrowserWindow实例都对应一个单独的渲染器进程(多个渲染进程 ),运行在渲染器进程中的代码,必须遵守网页标准,这也就意味着:渲染器进程无权直接访问require或使用任何Node.js的API。
问题产生:处于渲染器进程的用户界面,该怎样才与Node.js和Electron的原生桌面功能进行交互呢?
Preload脚本
预加载(Preload)脚本是运行在渲染进程中的,但它是在网页内容加载之前执行的,这意味着它具有比普通渲染器代码更高的权限,可以访问Node.js的 API,同时又可以与网页内容进行安全的交互。
简单说:它是Node.js 和Web APl 的桥梁,Preload 脚本可以安全地将部分Node.js功能暴露给网页,从而减少安全风险。
在渲染进程(浏览器环境)中中运行
需求:点击按钮后,在页面呈现当前的Node 版本。
preload.js文件不能直接触发,需要在main.js中定义
执行顺序:主进程 -> 预加载脚本 -> 渲染进程
prload可访问api
可用的 API | 详细信息 |
---|---|
Electron 模块 | 渲染进程模块 |
Node.js 模块 | events、timers、url |
Polyfilled 的全局模块 | Buffer、process、clearImmediate、setImmediate |
暴露Electron 的 process.versions
对象给渲染器
javascript
//预加载脚本 preload.js
const { contextBridge } =require ('electron')
console.log('preload')
contextBridge.exposeInMainWorld('toRenderVersions',{//对外暴露一个toRenderVersions全局变量,是渲染进程可以获取
node:()=>process.versions.node,//node版本
chrome:()=>process.versions.chrome,
electron:()=>process.versions.electron
})
javascript
//main.js
const {app,BrowserWindow} = require('electron')
const path = require('node:path')//node的path方法
function createWindow(){
const win = new BrowserWindow({//创建窗口
width:800,
height:600,
autoHideMenuBar:true,//隐藏默认配置菜单
x:0,
y:0,//在什么位置打开
webPreferences:{// 网页功能设置
preload:path.join(__dirname,'./preload.js')
}
})
// 加载文件
win.loadFile('./pages/index.html')
}
app.on('ready',()=>{// 当app准备好的时候调用回调
console.log('应用准备完毕!!!')
createWindow()
app.on('activate', () => {//应用被激活的时候(仅针对苹果系统)
if (BrowserWindow.getAllWindows().length === 0) createWindow() // 当前窗口数量是0 ,没有窗口,就创建窗口
})
})
这里使用了两个Node.js概念:
现在渲染器能够全局访问 versions
了,让我们快快将里边的信息显示在窗口中。 这个变量不仅可以通过 window.versions
访问,也可以很简单地使用 versions
来访问。 新建一个 renderer.js
脚本, 使用 document.getElementById DOM API 来替换 id
属性为 info
的 HTML 元素的文本。
javascript
//render.js
// 执行在渲染进程中
const btn1 = document.getElementById('btn1')
btn1.onclick = ()=>{
alert('此应用的node版本'+toRenderVersions.node()+'chrome版本'+toRenderVersions.chrome()+'electron版本'+toRenderVersions.electron())
}
进程之间通信(IPC)
1. 渲染进程->主进程(单向)
概述:在渲染器进程中ipcRenderer.send发送消息在主进程中使用ipcMain.on接收消息。
常用于:在 Web中调用主进程的API,例如下面的这个需求:
需求:点击按钮后,在用户的D盘创建一个hello.txt文件,文件内容来自于用户输入。
1.页面中添加相关元素,render.js中添加对应脚本
html
<input id="input" type="text">
<button id="btn2"> 向d盘写入hello.txt</button>
javascript
const btn2=document.getElementById('btn2')
const input=document.getElementById('input')
btn2.onclick=()=>{
toRenderVersions.saveFile(input.value)
}
- preload.js 中使用ipcRenderer.invoke('信道',参数)发送消息,与主进程通信。
javascript
//预加载脚本
const { contextBridge ,ipcRenderer } =require ('electron')
contextBridge.exposeInMainWorld('toRenderVersions',{
saveFile:(data)=>{
// 传入参数:信道,数据
ipcRenderer.send('file-save',data)
}
})
3.主进程main.js使用ipcMain.on('信道',()=>{})收到消息,触发函数执行
javascript
const {app,BrowserWindow,ipcMain} = require('electron')
function writeFile(_,data){
fs.writeFileSync('D:/hello.txt',data)
}
function createWindow(){
const win = new BrowserWindow({//创建窗口
width:800,
height:600,
autoHideMenuBar:true,//隐藏默认配置菜单
webPreferences:{// 网页功能设置,web首选项
preload:path.join(__dirname,'./preload.js')//绝对路径
}
})
ipcMain.on('file-save',writeFile)//send对应on
win.loadFile('./pages/index.html')
}
2.主进程与渲染进程的双向通信
概述:渲染进程通过ipcRenderer.invoke 发送消停,主进程使用ipcMain.handle接收并处理消息。
备注:ipcRender.invoke的返回值是Promise实例
常用于:从渲染器进程调用主进程方法并等待结果,例如下面的这个需求:
需求:点击按钮从D盘读取hello.txt中的内容,并将结果呈现在页面上。
1.页面中添加相关元素,render.js中添加对应脚本
html
<button id="btn3">读取D盘中的hello.txt</button>
javascript
const btn3=document.getElementById('btn3')
btn3.onclick=async()=>{
let fileContent =await toRenderVersions.readFile()
console.log(fileContent)
alert(fileContent)
}
- preload.js中使用ipcRenderer.invoke( '信道',参数)发送消息,与主进程通信。
javascript
//预加载脚本
const { contextBridge ,ipcRenderer } =require ('electron')
contextBridge.exposeInMainWorld('toRenderVersions',{
readFile:()=>{
//传入参数:信道,(数据)
return ipcRenderer.invoke('file-read')//调用,渲染进程与主进程的双向通信
}
})
3.主进程main.js使用ipcMain.handle('信道',()=>{})接收消息,并执行函数返回数据
javascript
const {app,BrowserWindow,ipcMain} = require('electron')
const path = require('node:path')//node的path模块
const fs = require('fs')
function readFile(){
console.log( fs.readFileSync('D:/hello.txt'),fs.readFileSync('D:/hello.txt').toString())
return fs.readFileSync('D:/hello.txt').toString()
}
function createWindow(){
const win = new BrowserWindow({//创建窗口
width:800,
height:600,
autoHideMenuBar:true,//隐藏默认配置菜单
webPreferences:{// 网页功能设置,web首选项
preload:path.join(__dirname,'./preload.js')//绝对路径
}
})
ipcMain.handle('file-read',readFile)//invoke对应handle
win.loadFile('./pages/index.html')
}
3.主进程->渲染进程(单项通信)
概述:主进程使用win.webContents.send发送消息,渲染进程通过ipcRenderer.on处理消息,
常用于:从主进程主动发送消息给渲染进程,例如下面的这个需求:
需求:应用加载6秒钟后,主动给渲染进程发送一个消息,内容是:你好啊!
1.页面中添加相关元素,render.js 中添加对应脚本
javascript
window.onload=()=>{
toRenderVersions.getMessage(logMessage)
}
const logMessage = (event,str)=>{
console.log(event,str)
}
2.preload.js中使用ipcRenderer.on('信道',回调)接收消息,并配置回调函数.
javascript
getMessage:(callback)=>{
return ipcRenderer.on('message',callback)
}
3.主进程中,在合适的时候,使用win.webContents.send('信道',数据)发送消息。
javascript
// 加载了本地页面后,创建一个定时器
setTimeout(() => {
win.webContents.send('message','你好啊')
}, 6000);
渲染进程与渲染进程之间不能直接通信,需要通过主进程传递数据
打包应用
使用electron-builder打包应用
1.安装electron-builder :
bash
npm install electron-builder -D
2.在package.json中进行相关配置,具体配置如下
备注:json文件不支持注释,使用时请去掉所有注释。
javascript
{
"name": "my-electron-app",//应用程序的名称
"version": "1.0.0",//应用程序的版本
"description": "my first electron app",//应用程序的描述
"main": "main.js",//应用程序入口文件
"scripts": {
"start": "chcp 65001 && nodemon --exec electron .",//使用`electron .`命令启动应用程序
"build":"electron-builder"//使用`electron-builder`打包应用程序,生成安装包
},
"build":{
"appId":"com.ylx.hahaha",//应用程序唯一标识符
//打包windows平台安装包的具体配置
"win":{
"icon":"./logo.ico",//应用图标
"target":[
{
"target":"nsis",//指定使用 NSIS 作为安装程序格式(.exe安装包文件)
"arch":["x64"]//生成64 位安装包
}
]
},
"nsis":{
"oneClick":false,//设置为`false`使安装程序显示安装向导界面,而不是一键安装
"perMachine":true,//允许每台机器安装一次,而不是每个用户都安装
"allowToChangeInstallationDirectory":true//允许用户在安装过程中选择安装目录
}
},
"author": "ylx",//作者信息
"license": "ISC",//许可证信息
"devDependencies": {
"electron": "^32.0.1",//开发依赖中的Electron版本
"electron-builder": "^24.13.3"//开发依赖中的'electron-builder'版本
},
"dependencies": {
"nodemon": "^3.1.4"
}
}
执行打包命令:
bash
npm run build
electron-vite
electron-vite 是一个新型构建工具,旨在为 Electron 提供更快、更精简的开发体验。它主要由五部分组成:
-
一套构建指令,它使用 Vite 打包你的代码,并且它能够处理 Electron 的独特环境,包括 Node.js 和浏览器环境。
-
集中配置主进程、渲染器和预加载脚本的 Vite 配置,并针对 Electron 的独特环境进行预配置。
-
为渲染器提供快速模块热替换(HMR)支持,为主进程和预加载脚本提供热重载支持,极大地提高了开发效率。
-
优化 Electron 主进程资源处理。
-
使用 V8 字节码保护源代码。
electron-vite 需要 Node.js 版本 18+,20+ 和 Vite 版本 4.0+
更多electron-vite信息:electron-vite官网