vue vite ts electron ipc addon-napi c arm64
初始化
因网络问题建议使用 cnpm 代替 npm
npm init vue # 全选 yes
npm i # 进入项目目录后使用
npm i electron electron-builder -D
npm i commander -D # 额外组件
electron
新建 plugins、src/electron 文件夹
添加 src/electron/background.ts
属于主进程
ipcMain.on、ipcMain.handle 都用于主进程监听 ipc,ipcMain.on 用于监听 ipcRenderer.send,ipcMain.handle 用于监听 ipcRenderer.invoke 并 return xxx
ipc 单向:
从渲染进程发向主进程:ipcRenderer.send
从主进程发向渲染进程:window.webContents.send
ipc 双向:
从渲染进程发向主进程,主进程还会返回发向渲染进程:ipcRenderer.invoke
从主进程发向渲染进程,渲染进程还会返回发向主进程:没有类似于 ipcRenderer.invoke 的,需要间接实现。主进程使用 window.webContents.send,渲染进程使用 ipcRenderer.send
渲染进程之间的 ipc 通信:
让主进程中转,也就是“渲染进程1”使用 ipcRenderer.send 到主进程的 ipcMain.on,然后主进程在这个 ipcMain.on 里用相应的 window2.webContents.send 发送到“渲染进程2”里。每个 “const windowxxx = new BrowserWindow“ 就是一个新的渲染进程
import { app, BrowserWindow, screen, ipcMain } from 'electron'
import path from 'path'
import { Command } from 'commander'// 当 electron 准备好时触发
app.whenReady().then(() => {// 使用 c 编译出的 hello.nodetry {var addon = require('./addon/hello')console.log(addon.hello());} catch (error) {console.log(error)}// 命令行参数解析const command = new Commandcommand.option('-m, --maximize', 'maximize window').option('-l, --location <>', 'location of load index page', 'index.html').option('-d, --dev', 'openDevTools').option('--no-sandbox', 'other').parse()const options = command.opts()// 创建窗口const window = new BrowserWindow({// 命令行加 -m 则全屏width: options.maximize ? screen.getPrimaryDisplay().workAreaSize.width : 800,height: options.maximize ? screen.getPrimaryDisplay().workAreaSize.height : 600,autoHideMenuBar: true,webPreferences: {preload: path.join(__dirname, 'preload.js') // 加载 预加载脚本,用于向渲染进程提供使用 ipc 通信的方法}})// 命令行加 -l http://xxx:xxx 则加载该 url 的 index.html,可实时刷新页面的更改,用于调试if (options.location.indexOf(':') >= 0)window.loadURL(options.location)elsewindow.loadFile(options.location)// 命令行加 -d 则打开开发者工具if (options.dev)window.webContents.openDevTools()// ipcipcMain.on('rtm', () => {console.log('rtm')window.webContents.send('mtr')})ipcMain.on('rtm_p', (e, p) => {console.log(p)window.webContents.send('mtr_p', `mtr_p ${p}`)})ipcMain.handle('rtmmtr_p', (e, p) => {console.log(p)return `rtmmtr_p ${p}`})
})
添加 src/electron/preload.ts
预加载脚本,用来给渲染进程提供使用 ipc 的方法
rtm 是渲染进程发向主进程;rtmmtr 是从渲染进程发向主进程,主进程还会返回发向渲染进程;mtr 是主进程发向渲染进程
import { contextBridge, ipcRenderer } from 'electron'contextBridge.exposeInMainWorld('electronAPI', {rtm: () => ipcRenderer.send('rtm'),rtm_p: (p: any) => ipcRenderer.send('rtm_p', p),rtmmtr_p: (p: any) => ipcRenderer.invoke('rtmmtr_p', p),mtr: (p: any) => ipcRenderer.on('mtr', p),mtr_p: (p: any) => ipcRenderer.on('mtr_p', p),
})
添加 src/electron/renderer.d.ts
给渲染进程用的 preload.ts 里的方法的类型声明
export interface IElectronAPI {rtm: () => Promise<any>,rtm_p: (p: any) => Promise<any>,rtmmtr_p: (p: any) => Promise<any>,mtr: (p: any) => Promise<any>,mtr_p: (p: any) => Promise<any>,
}declare global {interface Window {electronAPI: IElectronAPI}
}
添加 plugins/vite.electron.dev.ts
自定义 dev 方法,用于启动 vite 后带起 electron
import type { Plugin } from 'vite'
import type { AddressInfo } from 'net'
import { spawn } from 'child_process'
import fs from 'fs'// 导出Vite插件函数
export const viteElectronDev = (): Plugin => {return {name: 'vite-electron-dev',// 在configureServer中实现插件的逻辑configureServer(server) {// 定义初始化Electron的函数const initElectron = () => {// 使用esbuild编译TypeScript代码为JavaScriptrequire('esbuild').buildSync({entryPoints: ['src/electron/background.ts', 'src/electron/preload.ts'],bundle: true,outdir: 'dist',platform: 'node',external: ['electron']})// 复制 .nodetry {fs.mkdirSync('dist/addon')} catch (error) {}fs.copyFileSync('addon/hello/build/Release/hello.node', 'dist/addon/hello.node')}// electron 运行let electron_run = (ip: string) => {initElectron()// 启动Electron进程并添加相应的命令行参数let electronProcess = spawn(require('electron'), ['dist/background.js', '-l', ip, '-d'])// 监听Electron进程的stdout输出electronProcess.stdout?.on('data', (data) => {console.log(`${data}`);});return electronProcess}// 监听Vite的HTTP服务器的listening事件server?.httpServer?.once('listening', () => {// 获取HTTP服务器的监听地址和端口号const address = server?.httpServer?.address() as AddressInfoconst ip = `http://localhost:${address.port}`let electronProcess = electron_run(ip)// 监听主进程代码的更改以自动编译这些 .ts 并重启 electronfs.watch('src/electron', () => {// 杀死当前的Electron进程electronProcess.kill()electronProcess = electron_run(ip)})})}}
}
添加 plugins/vite.electron.build.ts
自定义 build 方法,这里打包了 linux 的 x64、arm64 的包
import type { Plugin } from 'vite'
import * as electronBuilder from 'electron-builder'
import path from 'path'
import fs from 'fs'// 导出Vite插件函数
export const viteElectronBuild = (): Plugin => {return {name: 'vite-electron-build',// closeBundle是Vite的一个插件钩子函数,用于在Vite构建完成后执行一些自定义逻辑。closeBundle() {// 定义初始化Electron的函数const initElectron = () => {// 使用esbuild编译TypeScript代码为JavaScriptrequire('esbuild').buildSync({entryPoints: ['src/electron/background.ts', 'src/electron/preload.ts'],bundle: true,outdir: 'dist',platform: 'node',external: ['electron']})// 复制 .nodefs.mkdirSync('dist/addon')fs.copyFileSync('addon/hello/build/Release/hello.node', 'dist/addon/hello.node')}// 调用初始化Electron函数initElectron()// 修改package.json文件的main字段,不然会打包失败const json = JSON.parse(fs.readFileSync('package.json', 'utf-8'))json.main = 'background.js'fs.writeSync(fs.openSync('dist/package.json', 'w'), JSON.stringify(json, null, 2))// 创建一个空的node_modules目录 不然会打包失败fs.mkdirSync('dist/node_modules')// 使用electron-builder打包Electron应用程序electronBuilder.build({config: {appId: 'com.example.app',productName: 'vite-electron',directories: {app: path.join(process.cwd(), "dist"), // 被打包的散文件目录output: path.join(process.cwd(), "release"), // 生成的包的目录},linux: {"target": [{"target": "AppImage", // 生成的包的类型 .AppImage"arch": ["x64", "arm64"] // 会对每个架构都会生成对应的包。会下载对应架构的 electron,可能会失败,多试}]}}})}}
}
修改 src/App.vue
添加按钮和 ipc
属于渲染进程
window.electronAPI.xxx() 就是预加载脚本(preload.ts)给渲染进程提供的使用 ipc 的方法
window.electronAPI.mtr 和 …mtr_p (mtr:main to renderer)用于监听主进程发过来的消息
由于 window.electronAPI.rtmmtr_p 使用 ipcRenderer.invoke,这是异步方法,如果不在其前面加 await 而直接获取会得到一个用于异步执行的对象(Promise),其内容包含了需要异步执行的东西,await 就是等待该对象运行结束从而获取正确值,而 await 需要其调用者是异步的,所以 increment() 也加上了 async(异步标志)
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
import { ref } from 'vue'// 响应式状态
const count = ref(0)// 用来修改状态、触发更新的函数
async function increment() {count.value++window.electronAPI.rtm()window.electronAPI.rtm_p(`rtm_p ${count.value}`)const rtmmtr_p = await window.electronAPI.rtmmtr_p(`rtmmtr_p ${count.value}`)console.log(rtmmtr_p)
}window.electronAPI.mtr(() => {console.log('mtr')
})window.electronAPI.mtr_p((e: any, p: any) => {console.log(p)
})
</script><template><button @click="increment">hhh: {{ count }}</button><header><img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" /><div class="wrapper"><HelloWorld msg="You did it!" /><nav><RouterLink to="/">Home</RouterLink><RouterLink to="/about">About</RouterLink></nav></div></header><RouterView />
</template><style scoped>
header {line-height: 1.5;max-height: 100vh;
}.logo {display: block;margin: 0 auto 2rem;
}nav {width: 100%;font-size: 12px;text-align: center;margin-top: 2rem;
}nav a.router-link-exact-active {color: var(--color-text);
}nav a.router-link-exact-active:hover {background-color: transparent;
}nav a {display: inline-block;padding: 0 1rem;border-left: 1px solid var(--color-border);
}nav a:first-of-type {border: 0;
}@media (min-width: 1024px) {header {display: flex;place-items: center;padding-right: calc(var(--section-gap) / 2);}.logo {margin: 0 2rem 0 0;}header .wrapper {display: flex;place-items: flex-start;flex-wrap: wrap;}nav {text-align: left;margin-left: -1rem;font-size: 1rem;padding: 1rem 0;margin-top: 1rem;}
}
</style>
修改 tsconfig.node.json
添加 “plugins/**/*.ts”
{"extends": "@tsconfig/node18/tsconfig.json","include": ["vite.config.*","vitest.config.*","cypress.config.*","nightwatch.conf.*","playwright.config.*","plugins/**/*.ts"],"compilerOptions": {"composite": true,"module": "ESNext","types": ["node"]}
}
修改 vite.config.ts
添加 plugins
import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'// plugins
import { viteElectronDev } from './plugins/vite.electron.dev'
import { viteElectronBuild } from './plugins/vite.electron.build'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),vueJsx(),viteElectronDev(),viteElectronBuild()],base: './', //默认绝对路径改为相对路径 否则打包白屏resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}}
})
addon-napi
为了让 electron 调用 c
再新建 addon/hello 目录
添加 addon/hello/hello.c
#include <assert.h>
#include <node_api.h>static napi_value Method(napi_env env, napi_callback_info info) {napi_status status;napi_value world;status = napi_create_string_utf8(env, "hello world", 11, &world);assert(status == napi_ok);return world;
}#define DECLARE_NAPI_METHOD(name, func) \{ name, 0, func, 0, 0, 0, napi_default, 0 }static napi_value Init(napi_env env, napi_value exports) {napi_status status;napi_property_descriptor desc = DECLARE_NAPI_METHOD("hello", Method);status = napi_define_properties(env, exports, 1, &desc);assert(status == napi_ok);return exports;
}NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
添加 addon/hello/hello.js
var addon = require('./build/Release/hello')console.log(addon.hello())
添加 addon/hello/binding.gyp
{"targets": [{"target_name": "hello","sources": [ "hello.c" ]}]
}
添加 addon/hello/package.json
{"name": "hello_world","version": "0.0.0","description": "Node.js Addons Example #1","main": "hello.js","private": true,"scripts": {"test": "node hello.js"},"gypfile": true
}
.node 的编译与测试
cd 到 addon/hello
npm i # 安装依赖和编译
node ./ # 测试,会输出 hello world
修改 hello.c 后使用npm i
重新编译
使用
启动:npm run dev
打包:npm run build
npm run dev
后会在桌面出现应用界面,并自动打开开发者工具,命令行会输出 hello world
修改 src/electron 下的任何文件都会自动编译这些 .ts 并重启 electron
npm run build
后会在 release 下生成 vite-electron-0.0.0.AppImage 和 vite-electron-0.0.0-arm64.AppImage
其他
- https://xiaoman.blog.csdn.net/article/details/131713875?spm=1001.2014.3001.5502
- https://www.electronjs.org/zh/docs/latest/tutorial/context-isolation
- https://www.electronjs.org/zh/docs/latest/tutorial/ipc
相关文章:

vue vite ts electron ipc addon-napi c arm64
初始化 因网络问题建议使用 cnpm 代替 npm npm init vue # 全选 yes npm i # 进入项目目录后使用 npm i electron electron-builder -D npm i commander -D # 额外组件electron 新建 plugins、src/electron 文件夹 添加 src/electron/background.ts 属于主进程 ipcMain.o…...

机器人科普--AGILOX 叉车
机器人科普--AGILOX 叉车 1 概述2 导航3 驱动轮组4 叉举参考 1 概述 AGILOX 叉车,不需要画地图路径,很厉害。 2 导航 中间路径自由导航,末端规划出轨迹路线,并使用优良的控制器做轨迹追踪。 AGILOX | 10 Min setu…...

Django的生命周期流程图(补充)、路由层urls.py文件、无名分组和有名分组、反向解析(无名反向解析、有名反向解析)、路由分发、伪静态
一、orm的增删改查方法(补充) 1. 查询resmodels.表名(类名).objects.all()[0]resmodels.表名(类名).objects.filter(usernameusername, passwordpassword).all()res models.表名(类名).objects.first() # 判断,判断数据是否有# res如果查询…...

selenium交互代码
一:selenium交互 用selenium打开网页后,也可以做一系列真人的操作,也就是利用selenium和浏览器进行交互,可利用以下几个函数进行操作: input.send_keys() 传递输入内容给某输入框button.click() 点击某按钮browser.e…...

下载远程服务器文件
业务需求:下载某云盘的视频文件存储到本地 测试代码 RequestMapping("testVideo")public String test() {try {SimpleDateFormat DATE_FORMAT new SimpleDateFormat("yyyy/MM/dd/");//组装本地保存地址StringBuilder filePath new StringBuilder(StoreP…...

[SQL挖掘机] - 索引
介绍: 当你在数据库中进行查询时,索引是一种用于提高查询性能的重要工具。索引是对表中的一列或多列进行排序的数据结构,它可以快速定位到满足特定条件的记录,从而减少了查询所需的时间和资源。 在数据库中使用索引的主要好处包括ÿ…...

C++STL库中的list
文章目录 list的介绍及使用 list的常用接口 list的模拟实现 list与vector的对比 一、list的介绍及使用 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 2. list的底层是双向带头循环链表结构,双向带头循…...

【LeetCode 75】第十七题(1493)删掉一个元素以后全为1的最长子数组
目录 题目: 示例: 分析: 代码运行结果: 题目: 示例: 分析: 给一个数组,求删除一个元素以后能得到的连续的最长的全是1的子数组。 我们可以先单独统计出连续为1的子数组分别长度…...

配置IPv6 over IPv4 GRE隧道示例
组网需求 如图1,两个IPv6网络分别通过SwitchA和SwitchC与IPv4公网中的SwitchB连接,客户希望两个IPv6网络中的PC1和PC2实现互通。 其中PC1和PC2上分别指定SwitchA和SwitchC为自己的缺省网关。 图1 配置IPv6 over IPv4 GRE隧道组网图 配置思路 要实现I…...

Google Earth Engine谷歌地球引擎提取多波段长期反射率数据后绘制折线图并导出为Excel
本文介绍在谷歌地球引擎GEE中,提取多年遥感影像多个不同波段的反射率数据,在GEE内绘制各波段的长时间序列走势曲线图,并将各波段的反射率数据与其对应的成像日期一起导出为.csv文件的方法。 本文是谷歌地球引擎(Google Earth Engi…...

第三大的数
414、第三大的数 class Solution {public int thirdMax(int[] nums) {Arrays.sort(nums);int tempnums[0];int ansnums[0];int count 0;// if(nums.length<3){// return nums[nums.length-1];// }// else {for(int inums.length-1;i>0;i--){if (nums[i]>nums[i…...

正则表达式中的方括号[]有什么用?
在正则表达式中,方括号 [] 是用于定义字符集合的元字符。它在正则表达式中有以下作用: 匹配字符集合中的任意一个字符:方括号中列出的字符,表示在这个位置可以匹配这些字符中的任意一个。例如,[abc] 将匹配任意一个字符…...

SQL编写规范
文章目录 1.命名规范:2.库表设计:3.查询数据:4.修改数据:5.索引创建: 1.命名规范: 1.库名、表名、字段名,必须使用小写字母或数字,不得超过30个字符。 2.库名、表名、字段名&#…...

Azure pipeline自动化打包发布
pipeline自动化,提交代码后,就自动打包,打包成功后自动发布 第一步 pipeline提交代码后,自动打包。 1 在Repos,分支里选择要触发的分支,这里选择cn_china,对该分支设置分支策略 2 在生产验证中增加新的策略 3 在分支安…...

【算法提高:动态规划】1.4 状态机模型 TODO
文章目录 例题列表1049. 大盗阿福(其实就是打家劫舍)1057. 股票买卖 IV(k笔交易)1058. 股票买卖 V(冷冻期)1052. 设计密码⭐⭐⭐🚹🚹🚹(TODO)1053…...

ip link add 命令
ip link add veth0 type veth peer name veth1 这条命令主要用于在 Linux 操作系统中创建一个新的 veth(虚拟以太网) 对,这是一种虚拟网络设备,用于在 Linux 命名空间(namespaces)之间创建网络连接。此命令将创建两个设备…...

unity事件处理
方法调用 //发送事件 【发送事件码,发送消息内容】 EventCenterUtil.Broadcast(EventCenterUtil.EventType.Joystick, ui);//监听无参事件 EventCenterUtil.AddListener(EventCenterUtil.EventType.Joystick, show); public void show(){}//发送事件 有参事件 Eve…...

《ChatGPT原理最佳解释,从根上理解ChatGPT》
【热点】 2022年11月30日,OpenAI发布ChatGPT(全名:Chat Generative Pre-trained Transformer), 即聊天机器人程序 ,开启AIGC的研究热潮。 ChatGPT是人工智能技术驱动的自然语言处理工具,它能够…...

大数据Flink(五十):流式计算简介
文章目录 流式计算简介 一、数据的时效性 二、流式计算和批量计算...

13-4_Qt 5.9 C++开发指南_基于QWaitCondition 的线程同步_Wait
在多线程的程序中,多个线程之间的同步实际上就是它们之间的协调问题。例如上一小节讲到的3个线程的例子中,假设 threadDAQ 写满一个缓冲区之后,threadShow 和 threadSaveFile 才能对缓冲区进行读操作。前面采用的互斥量和基于 OReadWriteLock…...

STM32(HAL)多串口进行重定向(printf函数发送数据)
目录 1、简介 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 串口外设配置 2.3 项目生成 3、KEIL端程序整合 4、效果测试 1、简介 在HAL库中,常用的printf函数是无法使用的。本文通过重映射实现在HAL库多个串口可进行类似printf函数的操作。 2.1 基础配置 2.…...

29_互联网(The Internet)(IP数据包;UDP;TCP;DNS;OSI)
上篇介绍了计算机网络的基础知识,也提到互联网(The Internet),本篇将会详细介绍互联网(The Internet)。 文章目录 1. 互联网(The Internet)组成及数据包传输过程2. IP 数据包的不足3…...

xShell常用命令
xShell常用命令 一、文件夹目录1、cd-更改目录2、mkdir-建立目录3、rm-删除目录4、pwd-查看当前路径5、rmdir-删除空目录 二、文件操作1、cat-显示文件内容2、diff-比较文件内容3、查看文件的名字和后缀4、ls-列出文件5、cp-复制文件6、mv-移动和重命名文件找不同:选…...

React性能优化之Memo、useMemo
文章目录 React.memo两种方式参数应用场景 拓展useMemouseMemo(calculateValue, dependencies) 参考资料 React.memo React 的渲染机制,组件内部的 state 或者 props 一旦发生修改,整个组件树都会被重新渲染一次,即时子组件的参数没有被修改&…...

IDEA开启并配置services窗口
前言: 一般一个spring cloud项目中大大小小存在几个十几个module编写具体的微服务项目。此时,如果要调试测需要依次启动各个项目比较麻烦。 方法一: 默认第一次打开项目的时候,idea会提示是否增加这个选项卡,如果你没…...

vue2企业级项目(三)
vue2企业级项目(三) 引入mockjs,i18n 1、mockjs 项目下载依赖 npm install --save-dev mock根目录创建mock文件夹,并创建mock/index.js import Mock from "mockjs";// 设置全局延时 没有延时的话有时候会检测不到数据…...

QT 在label上透明绘图
一、新建TransparentDemo工程 二、在界面上添加label,修改样式表,将底色置为红色,作为北京 三、新建一个TransparentLabel类,继承自QLabel 此时,工程包括文件 五、在transparentlabel.h中添加 头文件 #include …...

SAM(Segment Anything)大模型论文汇总
A Comprehensive Survey on Segment Anything Model for Vision and Beyond 论文:https://arxiv.org/abs/2305.08196 25页综述,198篇参考文献!52个开源项目!本文第一个全面回顾了分割一切模型(SAM)的研究和应用进展,…...

金融翻译难吗,如何做好金融翻译?
我们知道,金融翻译涉及企业经济这块的,是影响各公司发展很重要的一方面,翻译做得好,可以促进公司内外的交流,及时掌握各种信息,做好应对。那么,金融翻译难吗,如何做好金融翻译&#…...

Java面试题(Tomcat与Nginx)
Tomcat 什么是Tomcat? 简单来说是一个运行Java的网络服务器,也是jsp和serlvet的一个容器 Tomcat的缺省端口是多少,怎么修改? conf文件夹下修改server.xml文件 <Connector connectionTimeout"20000" port"8080" p…...