Electron学习+打包

**1.**什么是 Electron?

Electron 是⼀个 跨平台桌⾯应⽤ 开发框架,开发者可以使⽤:HTML、CSS、JavaScript 等
Web 技术来构建桌⾯应⽤程序,它的本质是结合了 Chromium 和 Node.js ,现在⼴泛⽤于桌⾯应
⽤程序开发,例如这写桌⾯应⽤都⽤到了 Electron 技术:
1、VisualStudioCode
2、GitHubDesktop
3、1Password
4、新版 QQ

**2.**Electron 的优势

  1. 可跨平台:同⼀套代码可以构建出能在:Windows、macOS、Linux 上运⾏的应⽤程序。
  2. 上⼿容易:使⽤ Web 技术就可以轻松完成开发桌⾯应⽤程序。
  3. 底层权限:允许应⽤程序访问⽂件系统、操作系统等底层功能,从⽽实现复杂的系统交互。
  4. 社区⽀持:拥有⼀个庞⼤且活跃的社区,开发者可以轻松找到⽂档、教程和开源库

**3.**Electron 技术架构

**3.1.**技术架构

**3.2.**进程模型

此处我们只是先了解⼀下进程模型,后⾯会详细讲解。

**4.**搭建⼀个⼯程

初始化⼀个包,并提填写好 package.json 中的必要信息及启动命令。

javascript 复制代码
{
 "name": "test",
 "version": "1.0.0",
 "main": "main.js",
 "scripts": {
 "start": "electron ." //start命令⽤于启动整个应⽤
 },
 "author": "tianyu", //为后续能顺利打包,此处要写明作者。
 "license": "ISC",
 "description": "this is a electron demo", //为后续能顺利打包,此处要编写描述。
}

安装 electron 作为开发依赖。

javascript 复制代码
npm i electron -D

在 main.js 中编写代码,创建⼀个基本窗⼝

javascript 复制代码
/*
 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实例属性
启动应⽤查看效果

javascript 复制代码
npm start

效果如下:

额外解释:

Electron

客户端,桌面应用,安装在电脑里面用的,而不是手机里面用的,比如视频编辑和头像处理软件

这两个需要补全,否则无法打包

  • 入口点 应当是 main.js (您很快就会创建它)
  • authorlicensedescription 可以是任何值,但在稍后的packaging中是必需的。

初次打开项目,会有默认的文件

autoHideMenuBar: true //自动隐藏菜单栏

alwaysOnTop: true

一打开页面从左面的x:0,y:0开始

ctrl+shift+i 调出控制台

**5.**加载本地⻚⾯

创建 pages/index.html 编写内容:

javascript 复制代码
<!DOCTYPE html>
<html>
 <head>
 <meta charset="UTF-8" />
 <title>index</title>
 </head>
 <body>
 <h1>你好啊!</h1>
 </body>
</html>

修改 mian.js 加载本地⻚⾯

javascript 复制代码
// 加载⼀个本地⻚⾯
win.loadFile('./pages/index.html')

此时开发者⼯具会报出⼀个安全警告,需要修改 index.html ,配置 CSP(Content
Security-Policy)

javascript 复制代码
<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 平台窗⼝特点是:关闭所有窗⼝时退出应⽤。
javascript 复制代码
// 当所有窗⼝都关闭时
app.on('window-all-closed', () => {
 // 如果所处平台不是mac(darwin),则退出应⽤。
 if (process.platform !== 'darwin') app.quit()
})
  1. mac 应⽤即使在没有打开任何窗⼝的情况下也继续运⾏,并且在没有窗⼝可⽤的情况下激活
    应⽤时会打开新的窗⼝。
javascript 复制代码
// 当app准备好后,执⾏createWindow创建窗⼝
app.on('ready',()=>{
 createWindow()
 // 当应⽤被激活时
 app.on('activate', () => {
 //如果当前应⽤没有窗⼝,则创建⼀个新的窗⼝
 if (BrowserWindow.getAllWindows().length === 0) createWindow()
 })
})

**7.**配置⾃动重启

  1. 安装 Nodemon
javascript 复制代码
npm i nodemon -D
  1. 修改 package.json 命令
javascript 复制代码
scripts": {
 "start": "nodemon --exec electron ."
},
  1. 配置 nodemon.json 规则
javascript 复制代码
{
 "ignore": [
 "node_modules",
 "dist"
 ],
 "restartable": "r",
 "watch": ["*.*"],
 "ext": "html,js,css"
}

配置好以后,当代码修改后,应⽤就会⾃动重启了。

**8.**主进程与渲染进程

下图是 Chrome 浏览器的程序架构,图来⾃于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 版本。
具体⽂件结构与编码如下:

  1. 创建预加载脚本 preload.js ,内容如下:
javascript 复制代码
const {contextBridge} = require('electron')
// 暴露数据给渲染进程
contextBridge.exposeInMainWorld('myAPI',{
 n:666,
 version:process.version
})

2.在主线程中引⼊ preload.js

javascript 复制代码
const win = new BrowserWindow({
 /*******/
 webPreferences:{
 preload:path.resolve(__dirname,'./preload.js')
 }
 /*******/
})
  1. 在 html ⻚⾯中编写对应按钮,并创建专⻔编写⽹⻚脚本的 render.js ,随后引⼊。
javascript 复制代码
<body>
 <h1>你好啊!</h1>
 <button id="btn">在⽤户的D盘创建⼀个hello.txt</button>
 <script type="text/javascript" src="./render.js"></script>
</body>
  1. 在渲染进程中使⽤ version
javascript 复制代码
btn.addEventListener('click',()=>{
 console.log(myAPI.version)
 document.body.innerHTML += `<h2>${myAPI.version}</h2>`
})
  1. 整体⽂件结构如下:

渲染进程(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)

值得注意的是:
上⽂中的 preload.js ,⽆法使⽤全部 Node 的 API ,⽐如:不能使⽤ Node 中的 fs 模
块,但主进程( main.js )是可以的,这时就需要
了。简单说:要
让 preload.js 通知 main.js 去调⽤ fs 模块去⼲活。
关于 Electron 进程通信,我们要知道:

  • IPC 全称为: InterProcess Communication ,即:进程通信。
  • IPC 是 Electron 中最为核⼼的内容,它是从 UI 调⽤原⽣ API 的唯⼀⽅法!
  • Electron 中,主要使⽤ ipcMainipcRenderer来定义"通道",进⾏进程通信。

**10.1.**渲染进程➡️主进程(单向)

概述: 在 渲染器进程 中 ipcRenderer.send 发送消息,在 主进程 中使⽤ ipcMain.on 接收消息。
常⽤于: 在Web中调用主进程的API ,例如下⾯的这个需求:
需求:点击按钮后,在⽤户的 D 盘创建⼀个 hello.txt ⽂件,⽂件内容来⾃于⽤户输⼊。

渲染进程向主进程单向通信

  1. ⻚⾯中添加相关元素, render.js 中添加对应脚本

index.html

javascript 复制代码
<input id="content" type="text"><br><br>
<button id="btn">在⽤户的D盘创建⼀个hello.txt</button>

render.js

javascript 复制代码
const btn = document.getElementById('btn')
const content = document.getElementById('content')
btn.addEventListener('click',()=>{
 console.log(content.value)
 myAPI.saveFile(content.value)
})
  1. preload.js 中使⽤ **ipcRenderer.send('信道',参数)**发送消息,与主进程通信。

preload.js

javascript 复制代码
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
 /*******/
 saveFile(str){
 // 渲染进程给主进程发送⼀个消息
 ipcRenderer.send('create-file',str)
 }
})
  1. 主进程中,在加载⻚⾯之前,使⽤ ipcMain.on(' 信道 ', 回调 ) 配置对应回调函数,接收
    消息。
    main.js
javascript 复制代码
// ⽤于创建窗⼝
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.**渲染进程↔主进程(双向)

概述: 渲染进程 通过 ipcRenderer.invoke 发送消息, 主进程 使⽤ ipcMain.handle 接收并 处理消
息。
备注: ipcRender.invoke 的返回值是 Promise 实例。
常⽤于: 从渲染器进程调用主进程方法并等待 ,例如下⾯的这个需求:
需求:点击按钮从 D 盘读取 hello.txt 中的内容,并将结果呈现在⻚⾯上。

  1. ⻚⾯中添加相关元素, render.js 中添加对应脚本
    index.html
javascript 复制代码
<button id="btn">读取⽤户D盘的hello.txt</button>

render.js

javascript 复制代码
const btn = document.getElementById('btn')
btn.addEventListener('click',async()=>{
 let data =
 document.body.innerHTML += `<h2>${data}</h2>`
})
  1. preload.js 中使⽤ ipcRenderer.invoke(' 信道 ', 参数 ) 发送消息,与主进程通信。
javascript 复制代码
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
 /*******/
 readFile (path){
 return ipcRenderer.invoke('read-file')
 }
})
  1. 主进程中,在加载⻚⾯之前,使⽤ ipcMain.handle(' 信道 ', 回调 ) 接收消息,并配置回
    调函数。
javascript 复制代码
// ⽤于创建窗⼝
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 处理消息,
常⽤于: 从主进程主动发送消息给渲染进程 ,例如下⾯的这个需求:
需求:应⽤加载 6 秒钟后,主动给渲染进程发送⼀个消息,内容是:你好啊!

  1. ⻚⾯中添加相关元素, render.js 中添加对应脚本
    render.js
javascript 复制代码
window.onload = ()=>{
 myAPI.getMessage(logMessage)
}
function logMessage(event,str){
 console.log(event,str)
}
  1. preload.js 中使⽤ ipcRenderer. on (' 信道 ', 回调 ) 接收消息,并配置回调函数。
    prelosd.js
javascript 复制代码
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
 /*******/
 getMessage: (callback) => {
 return ipcRenderer.on('message', callback);
 }
})
  1. 主进程中,在合适的时候,使⽤ win.webContents.send(' 信道 ', 数据 ) 发送消息。
    main.js
javascript 复制代码
// ⽤于创建窗⼝
function createWindow() {
 /**********/
 // 加载⼀个本地⻚⾯
 win.loadFile(path.resolve(__dirname,'./pages/index.html'))
 // 创建⼀个定时器
 setTimeout(() => {
 win.webContents.send('message','你好啊!')
 }, 6000);
}

**11.**打包应⽤

使⽤ electron-builder 打包应⽤

  1. 安装 electron-builder :
javascript 复制代码
npm install electron-builder -D
  1. 在 package.json 中进⾏相关配置,具体配置如下:

备注:json ⽂件不⽀持注释,使⽤时请去掉所有注释

javascript 复制代码
{
 "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" // 应⽤程
序的描述
}
  1. 执⾏打包命令
javascript 复制代码
npm run build

**12.**electron-vite

electron-vite 是⼀个新型构建⼯具,旨在为 Electron 提供更快、更精简的体验。主要由五部分
组成:
⼀套构建指令,它使⽤ Vite 打包你的代码,并且它能够处理 Electron 的独特环境,包括
Node.js 和浏览器环境。
集中配置主进程、渲染器和预加载脚本的 Vite 配置,并针对 Electron 的独特环境进⾏预配
置。
为渲染器提供快速模块热替换(HMR)⽀持,为主进程和预加载脚本提供热重载⽀持,极⼤
地提⾼了开发效率。
优化 Electron 主进程资源处理。
使⽤ V8 字节码保护源代码。
electron-vite 快速、简单且功能强⼤,旨在开箱即⽤。
官⽹地址: https://cn-evite.netlify.app/

扩展

渲染进程与渲染进程通信借助于主进程

总体代码:

main.js

javascript 复制代码
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

javascript 复制代码
// 预加载教程,作为主进程与渲染进程的桥梁
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

javascript 复制代码
{
  "ignore": [
  "node_modules",
  "dist"
  ],
  "restartable": "r",
  "watch": ["*.*"],
  "ext": "html,js,css"
 }

package.json

javascript 复制代码
{
  "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

javascript 复制代码
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

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

html 复制代码
h1{
  background-color: gray;
  color: orange;
}

打包之后文件

双击.exe文件可以安装

安装之后的文件

相关推荐
七灵微1 小时前
ES6入门---第二单元 模块五:模块化
前端·ecmascript·es6
m0_616188492 小时前
vue3 - keepAlive缓存组件
前端·vue.js·缓存
lh_12543 小时前
Uni-app 组件使用
前端·javascript·uni-app
Kx…………3 小时前
Day3:设置页面全局渐变线性渐变背景色uniapp壁纸实战
前端·学习·uni-app·实战·项目
Q_Boom3 小时前
前端跨域问题怎么在后端解决
java·前端·后端·spring
搬砖工程师Cola3 小时前
<Revit二次开发> 通过一组模型线构成墙面,并生成墙。Create(Document, IList.Curve., Boolean)
java·前端·javascript
林十一npc4 小时前
Fiddler抓取APP端,HTTPS报错全解析及解决方案(一篇解决常见问题)
android·前端·网络协议·https·fiddler·接口测试
小妖6664 小时前
4个纯CSS自定义的简单而优雅的滚动条样式
前端·javascript·css
Yensean4 小时前
Learning vtkjs之MultiSliceImageMapper
javascript·webgl
江沉晚呤时4 小时前
深入解析 .NET Kestrel:高性能 Web 服务器的架构与最佳实践
服务器·前端·.net