环境
系统:debian 12
初始化
vscode 安装 TypeScript Vue Plugin (Volar)、Vue Language Features (Volar)、C/C++ 扩展
sudo apt install nodejs npm
sudo npm install -g cnpm --registry=https://registry.npm.taobao.org # 从淘宝镜像安装 cnpm,建议使用 cnpm 代替 npm,虽然下面是用 npm 做试例,除非 cnpm 会让程序出错
npm init vue # 全选 yes。End-to-End: Cypress
# 进入项目目录后使用
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'
import os from 'os'
// 当 electron 准备好时触发
app.whenReady().then(() => {
// 使用 c 编译出的 .node
try {
var addon = require(`./addon/hello_${os.arch()}.node`)
console.log(addon.hello())
} catch (error) {
console.log(error)
}
// 命令行参数解析
// -m 则全屏
// -l http://xxx:xxx 则加载该 url 的 index.html,可实时刷新页面的更改,用于调试
// -d 则打开开发者工具
// 以下参数在某些情况下会使用,对用户无实际功能,但不添加到 Command 会因参数错误而被强制退出
// --no-sandbox root 用户执行 electron 应用需要传入此参数
const command = new Command
command
.option('-m, --maximize', 'maximize window')
.option('-l, --location <>', 'location of load index.html', './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
if (options.location.indexOf(':') >= 0)
window.loadURL(options.location)
else
window.loadFile(options.location)
// -d
if (options.dev)
window.webContents.openDevTools()
// ipc 通信
ipcMain.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}`
})
ipcMain.handle('rtmmtr_hello', () => {
return `${addon.hello()}`
})
})
添加 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),
rtmmtr_hello: () => ipcRenderer.invoke('rtmmtr_hello'),
})
添加 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>,
rtmmtr_hello: () => 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 代码为 JavaScript
require('esbuild').buildSync({
entryPoints: ['src/electron/background.ts', 'src/electron/preload.ts'],
bundle: true,
outdir: 'dist',
platform: 'node',
external: ['electron']
})
// 复制 .node 文件
try {
fs.mkdirSync('dist/addon')
} catch (error) { }
const node_file_table: string[] = ['hello'] // 需要使用的 .node 文件
node_file_table.every((v, i, a) => {
try {
fs.copyFileSync(`addon/${v}_x64/build/Release/${v}.node`, `dist/addon/${v}_x64.node`)
} catch (error) { }
try {
fs.copyFileSync(`addon/${v}_arm64/build/Release/${v}.node`, `dist/addon/${v}_arm64.node`)
} catch (error) { }
})
// 定义运行 electron 的方法
const 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 AddressInfo
const ip = `http://localhost:${address.port}`
let electronProcess = electron_run(ip)
// 监听主进程代码的更改以自动编译这些 .ts 并重启 electron
fs.watch('src/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 fs from 'fs'
// 导出Vite插件函数
export const viteElectronBuild = (): Plugin => {
return {
name: 'vite-electron-build',
// closeBundle 是 Vite 的一个插件钩子函数,用于在 Vite 构建完成后执行一些自定义逻辑。
closeBundle() {
// 定义初始化 Electron 的函数
const initElectron = () => {
// 使用 esbuild 编译 TypeScript 代码为 JavaScript
require('esbuild').buildSync({
entryPoints: ['src/electron/background.ts', 'src/electron/preload.ts'],
bundle: true,
outdir: 'dist',
platform: 'node',
external: ['electron']
})
// 复制 .node 文件
try {
fs.mkdirSync('dist/addon')
} catch (error) { }
const node_file_table: string[] = [] // 需要使用的 .node 文件
node_file_table.every((v, i, a) => {
try {
fs.copyFileSync(`addon/${v}_x64/build/Release/${v}.node`, `dist/addon/${v}_x64.node`)
} catch (error) { }
try {
fs.copyFileSync(`addon/${v}_arm64/build/Release/${v}.node`, `dist/addon/${v}_arm64.node`)
} catch (error) { }
})
}
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,避免自动复制一堆没用的 node_modules,降低空间占用
try {
fs.mkdirSync('dist/node_modules')
} catch (error) { }
// 使用 electron-builder 打包
electronBuilder.build({
config: {
appId: 'com.example.app',
productName: 'vite-electron',
directories: {
app: "dist", // 被打包的散文件目录
output: "release", // 生成的包的目录
},
// 无法跨操作系统打包
// linux
linux: {
"target": [
{
"target": "AppImage", // 免安装 .AppImage
"arch": ["x64", "arm64"] // 会对每个架构都会生成对应的包。会下载对应架构的 electron,可能会下载失败,多试几次
}
]
},
// windows
win: {
"target": [
{
"target": "portable", // 免安装 .exe
"arch": ["x64"]
}
]
}
}
})
}
}
}
修改页面 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)
})
async function increment_2() {
hello.value = await window.electronAPI.rtmmtr_hello()
}
</script>
<template>
<button @click="increment">hhh: {{ count }}</button>
<button @click="increment_2">hello {{ hello }}</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,将 c/c++ 编译成 .node 文件给 nodejs 用
新建 addon/hello_x64 目录
添加 .vscode/c_cpp_properties.json
此文件可以使用 vscode 的命令面板进行添加
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/usr/include/**"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-x64"
}
],
"version": 4
}
添加 addon/hello_x64/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, "world !", 7, &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_x64/hello.js
var addon = require('./build/Release/hello.node')
console.log(addon.hello())
添加 addon/hello_x64/binding.gyp
{
"targets": [
{
"target_name": "hello",
"sources": [ "hello.c" ]
}
]
}
.node 的编译与测试
cd 到 addon/hello_x64
sudo npm i -g node-gyp # 全局安装 node-gyp
node-gyp configure # 生成 Makefile 文件
node-gyp build # 编译
node ./hello.js # 测试,会输出 world !
arm64 .node
.node 模块必须要是对应的 cpu 架构
复制 addon/hello_x64 目录为 addon/hello_arm64
cd 到 addon/hello_arm64
node-gyp configure --arch=arm64
将这些复制到 addon/hello/build/Makefile 的开头:
cross_compiler = aarch64-linux-gnu-
CC = $(cross_compiler)gcc
CXX = $(cross_compiler)g++
LINK = $(CXX)
AR = $(cross_compiler)ar
编译:
node-gyp build
使用
启动:npm run dev
打包:npm run build
npm run dev
后会在桌面出现应用界面,并自动打开开发者工具,命令行会输出 world。
修改 src/electron 下的任何文件都会自动编译这些 .ts 并重启 electron文章来源:https://www.toymoban.com/news/detail-615115.html
npm run build
后会在 release 下生成 vite-electron-0.0.0.AppImage 和 vite-electron-0.0.0-arm64.AppImage。首次打包会下载 https://github.com/electron/electron/releases/download/v25.5.0/electron-v25.5.0-linux-x64.zip 和 electron-v25.5.0-linux-arm64.zip,如果网络不好就多试几次,或者下载好放到 ~/.cache/electron/ 下文章来源地址https://www.toymoban.com/news/detail-615115.html
其他
- 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 arm64的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!