昆明做网站优化,中国外协机械加工订单网,中医网站建设素材,企业it外包服务初始化
因网络问题建议使用 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…初始化
因网络问题建议使用 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 都用于主进程监听 ipcipcMain.on 用于监听 ipcRenderer.sendipcMain.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 electroncontextBridge.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: () Promiseany,rtm_p: (p: any) Promiseany,rtmmtr_p: (p: any) Promiseany,mtr: (p: any) Promiseany,mtr_p: (p: any) Promiseany,
}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.jsfs.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, // 生成的包的类型 .AppImagearch: [x64, arm64] // 会对每个架构都会生成对应的包。会下载对应架构的 electron可能会失败多试}]}}})}}
}修改 src/App.vue
添加按钮和 ipc 属于渲染进程
window.electronAPI.xxx() 就是预加载脚本preload.ts给渲染进程提供的使用 ipc 的方法 window.electronAPI.mtr 和 …mtr_p mtrmain to renderer用于监听主进程发过来的消息 由于 window.electronAPI.rtmmtr_p 使用 ipcRenderer.invoke这是异步方法如果不在其前面加 await 而直接获取会得到一个用于异步执行的对象Promise其内容包含了需要异步执行的东西await 就是等待该对象运行结束从而获取正确值而 await 需要其调用者是异步的所以 increment() 也加上了 async异步标志
script setup langts
import { RouterLink, RouterView } from vue-router
import HelloWorld from ./components/HelloWorld.vue
import { ref } from vue// 响应式状态
const count ref(0)// 用来修改状态、触发更新的函数
async function increment() {count.valuewindow.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)
})
/scripttemplatebutton clickincrementhhh: {{ count }}/buttonheaderimg altVue logo classlogo src/assets/logo.svg width125 height125 /div classwrapperHelloWorld msgYou did it! /navRouterLink to/Home/RouterLinkRouterLink to/aboutAbout/RouterLink/nav/div/headerRouterView /
/templatestyle 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:urlimport { 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.hstatic 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?spm1001.2014.3001.5502https://www.electronjs.org/zh/docs/latest/tutorial/context-isolationhttps://www.electronjs.org/zh/docs/latest/tutorial/ipc