初识Electron 进程通信
概述
Electron = chromium + nodejs + native API,也就是将node环境和浏览器环境整合到了一起,这样就构成了桌面端(chromium负责渲染、node负责操作系统API等)
流程模型
预加载脚本:运行在浏览器环境下,但是也能访问部分node API
初始化项目
先创建一个vue3+vite的项目,然后在项目当中安装electron。具体命令就是这样
npm create vite@latest vue-electron
cd vue-electron
npm i
npm i electron -D
构建Electron项目
在package.json文件当中修改和添加type和main配置,electron脚本运行在node环境下,所以type要设置为commonjs,然后main指向electron运行的入口文件,再将启动命令加进去,这样就可以通过 npm run start
来启动electron座面应用了
"type": "commonjs","main": "electron/main/index.js"// 启动命令"start": "electron .",
编写electron/main/index.js。通过BrowserWindow创建一个浏览器窗口,然后可以通过loadURL加载一个网页,或者通过loadFile加载一个html文件进行渲染。然后还可以通过初始化监听去创建对应的窗口和关闭窗口的时候去退出electron。
const { app, BrowserWindow } = require('electron')
const createWindow = () => {const win = new BrowserWindow({width: 800,height: 600})win.loadURL('http://localhost:5173/');// win.loadFile('index.html')}
app.whenReady().then(() => {createWindow()app.on('activate', () => {if (BrowserWindow.getAllWindows().length === 0) createWindow()})
})
app.on("window-all-closed", () => {console.log("在关闭一个应用的所有窗口后让它退出");// 在关闭一个应用的所有窗口后让它退出if (process.platform === "darwin") {app.quit();}
});
github 仓库地址:https://github.com/lizuoqun/vue-electron.git
窗口常用方法
在win【BrowserWindow】对象当中会有一些常用的方法,这里简单列出来一下,完整的在这BrowserWindow Object API
- getAllWindows:所有打开的窗口的数组
- getFocusedWindow:此应用程序中当前获得焦点的窗口
- fromBrowserView:通过给定的视图返回对应的窗口
- close:关闭窗口
- maximize:最大化窗口
- unmaximize:取消窗口最大化
- 还有最小化、全屏、聚焦、背景、阴影、透明等等,用的时候可以再去文档里面找一下对应的方法调用
命令合并
在这里是vue3和electron项目都在一起,并且是直接访问5137端口加到桌面端的,这个时候可以通过 npm-run-all
这个包可以将命令进行合并。先npm install npm-run-all这个包,然后改一下下面的启动脚本,通过 npm run deva
就可以直接启动vue3和electron项目了。
{"scripts": {"dev": "vite","build": "vue-tsc -b && vite build","preview": "vite preview","start": "electron .","deva": "npm-run-all --parallel dev start"},
}
预加载脚本
预加载脚本通过进行配置,在electron入口文件中进行配置,这里配置了一个同目录下的preload作为先进行加载的js脚本。
其中对于 WebPreferences Object 对象的配置可以看这个官方文档的配置,其中有几个比较常用的
devTools
boolean 是否开启 DevToolspreload
string 预加载的脚本文件地址nodeIntegration
boolean 是否启用Node integration 是否可以使用 Node.js 功能的配置contextIsolation
boolean 是否在独立 JavaScript 环境中运行 Electron API和指定的preload
脚本. 默认为true
。- nodeIntegration设置为true后require is not define。需要设置contextIsolation为false
const win = new BrowserWindow({width: 800,height: 600,webPreferences: {preload: path.join(__dirname, 'preload.js')}});
扩展
这里来一个简单的electron当中的预加载脚本看一下并且进行扩展下
contextBridge API 上下文桥梁,其他更多详细的配置可以参考官方文档,下面只说一下使用到的
在隔离的上下文中创建一个安全的、双向的、同步的桥梁。
exposeInMainWorld方法
contextBridge.exposeInMainWorld(apiKey, api)
apiKey
string - 将 API 注入到窗口
的键。 API 将可通过window[apiKey]
访问。api
any - 你的 API可以是什么样的以及它是如何工作的相关信息如下。
const { contextBridge } = require('electron')
/*** 在预处理的过程当中,不能直接将值给绑定到window对象上再取值,* 虽然预加载脚本与其所附着的渲染器在共享着一个全局 window 对象,* 但并不能从中直接附加任何变动到 window 之上,因为 contextIsolation 是默认的。* 所以在preload当中window.myAPI = { desktop: true } 设置了myAPI,但是在render当中获取不到console.log(window.myAPI)*/
contextBridge.exposeInMainWorld('versions', {node: () => process.versions.node,chrome: () => process.versions.chrome,electron: () => process.versions.electron,
})
因为这个脚本是预加载的,在项目启动之后就会执行,并且他里面的数据都是直接绑定在window对象上,那么在这个项目里面其他的地方就可以直接使用了,比方说在vue代码当中使用:
这里不能通过模版插值直接写的,原因很简单,在vue里面只会去加载setup里面的数据,但是versions是window的,所以要在js里面用
<script setup lang="ts">
import {onMounted} from 'vue';onMounted(() => {const information = document.getElementById('info') as HTMLDivElement;information.innerText = `本应用正在使用 Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), 和 Electron (v${versions.electron()})`;
});
</script><template><div id="info"></div><div>versions: {{ versions }}</div>
</template>
进程通信
概述
Electron 继承了来自 Chromium 的多进程架构,网页浏览器的基本架构是单个浏览器进程控制不同标签页进程,以及整个应用程序的生命周期。这样可以避免单个浏览器的无响应不会影响到整个浏览器。
Electron 应用的大致工作流程是:启动APP——主进程创建window——win加载页面(渲染进程)
Electron 应用程序将控制两种类型的进程:主进程和渲染器进程。
主进程&渲染进程是啥
主进程 ipcMain
- 是应用的核心,在应用启动时运行,并在整个应用的生命周期中保持活动状态
- 主要功能
- 创建和管理应用程序窗口
- 管理窗口生命周期
- 使用原生API
渲染进程
- 是应用的用户界面部分,渲染线程运行在 Chromium 内核中,可以像 Web 浏览器一样加载和呈现网页
- 主要功能
- 一个应用可以有多个渲染进程
- Windows 中展示的界面通过渲染进程表现
- 每个窗口都是一个独立的渲染线程
- 无法直接操作原生API
渲染进程到主进程(单向通信)
在这里借用预加载脚本进行通信,这里演示一下直接以vue3页面作为渲染进程,然后通过preload预加载脚本传递到electron入口也就是主进程。
<script setup lang="ts">
const renderToMainProcess = () => {electronApi.setTitle('渲染进程');
};
</script>
然后这个方法是在预加载脚本preload.js通过exposeInMainWorld进行绑定在window对象上的,所以在上面可以直接使用
contextBridge.exposeInMainWorld('electronApi', {// 渲染器进程到主进程的通信(单向通信)setTitle: (title) => ipcRenderer.send('set-title', title)
});
在最后可以在electron入口当中拿到这个数据。这里就有问题了,这是怎么知道触发了setTitle方法?
ipcMain:API在这
ipcRenderer:是一个 EventEmitter 的实例。 当在主进程中使用时,它处理从渲染器进程(网页)发送出来的异步和同步信息。 从渲染器进程发送的消息将被发送到该模块。
在ipcMain当中可以通过on、once等方法来监听ipcRenderer发送的事件。这就类比一个bus事件总线,从ipcRenderer发的set-title事件之后,从ipcMain当中拦截监听这个事件,并且指定对应的回调来处理后续操作。
- event:他是一个 IpcMainEvent 对象。在这个可以直接通过点去获取里面的属性,其中sender WebContent - 返回发送消息的 webContents
- 然后通过 fromWebContents() 方法找到对应的窗口对象,最后通过setTitle设置标题
- 补充:直接用win.setTitle其实也是一样的效果,这是因为这个渲染进程和主进程是在同一个页面上的,所以效果其实是一样的。因为 win 对象是在创建窗口时定义的,它代表了整个窗口,而不是特定的渲染进程。而我们需要的是找到发送消息的特定渲染进程对应的窗口对象,
const createWindow = () => {const win = new BrowserWindow({width: 800,height: 600,webPreferences: {preload: path.join(__dirname, 'preload.js')}});ipcMain.on('set-title', (event, title) => {const webContents = event.sender;const ipcWin = BrowserWindow.fromWebContents(webContents);ipcWin.setTitle(title);// win.setTitle(title)});win.loadURL('http://localhost:5173/');
};
渲染进程到主进程(双向通信)
在主进程(入口)当中添加以下代码:这里模拟的效果是在vue当中点击按钮后通过electron主进程打开文件系统选择文件,选择文件之后拿到文件的路径之后再传递给渲染进程(vue)进行显示。
dialog API在这
- 显示用于打开和保存文件、警报等的本机系统对话框
- showOpenDialog打开这是一个异步的所以给他加了async和await变成同步,他对应的也有一个showOpenDialogSync同步方法。
- Promise成功包含的数据
- canceled boolean - 对话框是否被取消。
- filePaths string[] - 用户选择的文件路径的数组. 如果对话框被取消,这将是一个空的数组。因为前面调用showOpenDialog方法的时候没有给他设置multiSelections参数(文件多选),这里直接取数组下标0也就是文件的路径了。
- ipcMain.handle:为一个invokeable的IPC 添加一个handler。 每当一个渲染进程调用
ipcRenderer.invoke(channel, ...args)
时这个处理器就会被调用。
async function handleFileOpen () {const { canceled, filePaths } = await dialog.showOpenDialog({})if (!canceled) {return filePaths[0]}
}app.whenReady().then(() => {ipcMain.handle('dialog:openFile', handleFileOpen)createWindow();app.on('activate', () => {if (BrowserWindow.getAllWindows().length === 0) createWindow();});
});
在预加载脚本当中把dialog:openFile给定义下。这里用的是invoke:表示通过 channel
向主过程发送消息,并异步等待结果。拿到这一步就实现了主进程和渲染进程的双向通信机制,和单向通信进行对比的区别在于单向通信使用到的send和on是单向传递(理解成on和emit就可以了,只能单向传递)。而双向通信通过invoke和handle就像是一个监听和异步等待的效果,每一个事件都会在ipcRenderer当中异步等待响应结果,得到结果之后直接响应给渲染进程也就完成了双向通信
contextBridge.exposeInMainWorld('electronApi', {// 渲染器进程到主进程的通信(双向通信)openFile: () => ipcRenderer.invoke('dialog:openFile')
});
最后就是在vue渲染进程当中进行触发
<script setup lang="ts">
import {ref} from 'vue';const renderToMainSingleProcess = () => {electronApi.setTitle('渲染进程');
};let filePath = ref('')
const renderToMainMultiProcess = async () => {filePath.value = await window.electronApi.openFile()
};
</script><template><div>渲染进程->主进程通信</div><el-button type="primary" @click="renderToMainSingleProcess">单向</el-button><el-button type="primary" @click="renderToMainMultiProcess">双向</el-button><div>双向通信的文件路径为:{{ filePath }}</div>
</template>
主进程到渲染进程
在electron主进程当中添加一个menu菜单项,这里通过webContents.send发送消息
const menu = Menu.buildFromTemplate([{label: app.name,submenu: [{click: () => win.webContents.send('update-counter', 1),label: '加一'},{click: () => win.webContents.send('update-counter', -1),label: '减一'}]}]);Menu.setApplicationMenu(menu);
预加载脚本暴露
contextBridge.exposeInMainWorld('electronApi', {// 主进程向渲染器进程发送消息onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),counterValue: (value) => ipcRenderer.send('counter-value', value)
});
最后在渲染进程当中调用即可实现效果,在这里的onUpdateCounter相对于一个回调触发,在主进程当中调用update-counter之后会通过预加载暴露的方法到渲染进程,这个渲染进程可以理解成一个监听器,得到监听结果之后就可以拿到改变的值了。
补充:然后这里的渲染进程和主进程的通信看前面的就好了,也就是预加载脚本通过send发送,在主进程当中通过ipcMain.on得到发送的消息
<script setup lang="ts">
import {ref} from 'vue';let counter = ref(0);
const mainToRender = () => {window.electronApi.onUpdateCounter((value) => {const oldValue = Number(counter.value);const newValue = oldValue + value;counter.value = newValue.toString();window.electronApi.counterValue(newValue);});
};
</script><template><el-button type="primary" @click="mainToRender">主进程->渲染进程通信</el-button><div>Current value = {{ counter }}</div>
</template>
遇见的问题
问题一:-32601
开启了控制台报错:win.webContents.openDevTools();
{"code":-32601,"message":"'Autofill.enable' wasn't found"}
异常信息:在打开了控制台的时候会报这个错误,但是并不影响实际使用:这个持续可以看一下Electron GitHub issues
问题二:Authors is required
An unhandled rejection has occurred inside Forge:
Error: Failed with exit code: 1
Output:
���ڳ��Դӡ�web_electron.nuspec�����ɳ������
Authors is required.
Description is required.
可以看到这个的报错是Authors和Description都是必填的,这个在package.json文件当中可能在初始化项目时没有填这两项,这两项是必填的,补充一下即可
打包之后在out目录下会有改exe文件,exe文件名对应的是项目名称,out\web-electron-win32-x64
到这里也就完成了electron的打包了,这个exe文件就可以直接在电脑上打开了。
打包
electron-build打包
这里安装electron-builder,需要安装在开发环境当中
npm i electron-builder -D
而后在package.json里面添加build配置,这里简化些,下面是需要添加的打包配置。打包后在dist目录下的exe直接安装即可
{"scripts": {"build-win": "electron-builder"},"build": {"productName": "Electron","appId": "com.modify","copyright": "by modify","asar": true,"win": {"icon": "electron/logo.ico"},"nsis": {"oneClick": false,"allowElevation": true,"allowToChangeInstallationDirectory": true,"installerIcon": "electron/logo.ico","uninstallerIcon": "electron/logo.ico","installerHeaderIcon": "electron/logo.ico","createDesktopShortcut": true,"createStartMenuShortcut": true,"shortcutName": "APP","runAfterFinish": true}}
}
相关文章:

初识Electron 进程通信
概述 Electron chromium nodejs native API,也就是将node环境和浏览器环境整合到了一起,这样就构成了桌面端(chromium负责渲染、node负责操作系统API等) 流程模型 预加载脚本:运行在浏览器环境下,但是…...
go语言中的通道(channel)详解
在 Go 语言中,通道(channel) 是一种用于在 goroutine(协程)之间传递数据的管道。通道具有类型安全性,即它只能传递一种指定类型的数据。通道是 Go 并发编程的重要特性,能够让多个 goroutine 之间…...
【JS】内置类型的相关问题
我是目录 引言内置类型undefined与nullnull和undefined的区别字符串转换为字符串数字0.1+0.2不等于0.3NaNBigInt大数相加问题原生函数(封箱与解封)判断类型的方法typeofinstanceofObject.prototype.toString.callconstructor类型转换toStringtoNumbertoBoolean显式强制类型转…...

Mac上无法访问usr/local的文件
sudo chmod 755 /usr/loca 最后用百度提供的方法解决了...
http 常见状态码
1xx 信息,表示临时响应并需要请求者继续执行操作 2xx 成功,操作被成功接收并处理 3xx 表示要完成请求,需要进一步操作。通常,这些状态码用来重定向 4xx 客户端错误,请求包含语法错误或无法完成请求 5xx 服务…...
代码训练营 day59|并查集
前言 这里记录一下陈菜菜的刷题记录,主要应对25秋招、春招 个人背景 211CS本CUHK计算机相关硕,一年车企软件开发经验 代码能力:有待提高 常用语言:C 系列文章目录 第59天 :第十一章:图论part05 文章目录…...

Node.js——fs模块-路径补充说明
1、相对路径: ./座右铭.txt 当前目录下的座右铭.txt座右铭.txt 等效于上面的写法../座右铭.txt 当前目录的上一级目录中的座右铭.txt 2、绝对路径 D:/Program File Windows系统下的绝对路径/usr/bin Linux系统…...

华为ENSP--ISIS路由协议
项目背景 为了确保资源共享、办公自动化和节省人力成本,公司E申请两条专线将深圳总部和广州、北京两家分公司网络连接起来。公司原来运行OSFP路由协议,现打算迁移到IS-IS路由协议,张同学正在该公司实习,为了提高实际工作的准确性和…...
论软件可靠性设计及其应用
摘要 2023 年 3 月,我所在的公司承接了某智慧加油站平台的建设工作。该项目旨在帮助加油站提升运营效率、降低运营成本和提高销售额。我在该项目中担任系统架构设计师,负责整个项目的架构设计工作。 本文结合我在该项目中的实践,详细论述了…...

Android中桌面小部件framework层使用到的设计模式
在Android中,桌面小部件(App Widget)的Framework层采用了多种设计模式,以实现模块化、可维护性和高效的交互。 以下是Android桌面小部件Framework层中常用的设计模式及其具体应用: 1. 观察者模式(Observe…...

【JavaEE进阶】HTML
本节⽬标 认识 HTML 的基本结构, 学习常⽤的 HTML 标签. 一 HTML基础 1.什么是HTML HTML(Hyper Text Markup Language), 超⽂本标记语⾔. 超⽂本: ⽐⽂本要强⼤. 通过链接和交互式⽅式来组织和呈现信息的⽂本形式. 不仅仅有⽂本, 还可能包含图⽚, ⾳频, 或者⾃已经审阅过它…...

ElasticSearch 添加IK分词器
ElasticSearch 添加IK分词器 前言一、IK分词器的算法二、Ik分词器的下载安装(Winows 版本)三、Ik分词器的下载安装(Linux 版本)四、验证测试(postman工具)测试 ik_smart 分词算法测试 ik_max_word 分词算法…...

可视化建模与UML《顺序图实验报告》
旷野的规则是永不回头。 一、实验目的: 1、熟悉顺序图的构件事物。 2、熟悉发送者与接受者的关系 3、熟练掌握描绘顺序图 4、加深对顺序图的理解和应用能力 二、实验环境: window7 | 10 | 11 EA15 三、实验内容: 据如下描述绘制顺序图&…...

Mac的极速文件搜索工具,高效管理文件
Mac的资源管理可以说是许多转Mac的朋友用不明白的一点了,访达怎么用,文件怎么找,为什么找不到,非常的头大 All作为Mac上的极速文件搜索管理工具,有效的为文件查找困难的用户解决难题 基于极速搜索引擎,快…...
公开仓库改私有再配置公钥后Git拉取仍需要输入用户名的问题
问题描述:git拉取私有仓库需要输入用户名和密码 我之前写了一个脚本用来定时自动拉取远程仓库更新本地仓库,后来将这个远程仓库改成私有后执行脚本就会需要输入用户名和密码。 [rootLH2020 ~]# ./sync_repo.sh 正在从远程仓库拉取最新更改… Username f…...

工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置
工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置...-CSDN博客 工作流初始错误 泛微提交流程提示_泛微OA 工作流WebService接口使用说明 工作流初始错误 泛微提交流程提示_泛微OA 工作流WebService接口使用说明-CSDN博客 工作…...

window下安装rust 及 vscode配置
安装 安装mingw64 (c语言环境 选择posix-ucrt) ucrt:通用c运行时库配置mingw64/bin的路径到环境变量中在cmd窗口中输入命令 "gcc -v" 4. 下载Rust安装程序 安装 Rust - Rust 程序设计语言 5. 配置rustup和cargo目录 (cargo是包管…...
【数据结构】【线性表】单链表1—概念即创建(附C语言源码)
单链表的定义, 链表用链式存储的方式实现线性表,链表中每个结点元素中需要指向下一个结点的指针(有时候也要指向上一个结点的指针),链表中的每个结点指针只指向下一结点的被叫为单链表。 单链表的创建和初始化 先定…...

centos7的maven配置
首先进入conf配置文件夹下的setting.xml 要改两个地方 第一:设置镜像源 <mirror> <id>alimaven</id> <name>aliyun maven</name> <url>https://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>c…...
day57 图论章节刷题Part08(拓扑排序、dijkstra(朴素版))
拓扑排序-117. 软件构建 思路:拓扑排序是经典的图论问题。给出一个有向图,把有向图转成线性的排序就叫拓扑排序,拓扑排序也要检测有向图是否有环,即存在循环依赖的情况,因为这种情况是不能做线性排序的,所…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...