难点:之前写了一个关于运行electron的文章 《现成的react项目直接转electron(1)能本地运行》后,又接着找打包的,找的是坑真多,全部失败,后来无意中看到 小满zs的B站视频 后,发现这个非常好,然后跟着重构一下,也可以直接看人家写的文章Vue3 Vite electron 开发桌面程序和小满Vue3第三十九章(Vue开发桌面程序Electron)
知识点:人家那是vue的,我这是react的,加减了一些东西,增加了【web层,渲染层,主进程】之间的互相通信交互,话不多说?,接着填坑吧,填了的都是知识点
1.还是先下载依赖
pnpm install -D electron electron-builder
2.先在根目录下创建两个文件夹,每个里面包含两个ts文件
electron-app
plugins
vite.electron.build.ts
vite.electron.dev.ts
electron
main.ts
preload.ts
接下来就一个个讲,干嘛的
2.1 先看看plugins
需要在vite.config.ts中调用这俩文件,以防大家不知道结构,所以索性就都贴上来了,实际上找electron相关就行,其他配置还是照旧(注释掉的是需要去掉的web相关的配置,比如打包的zip,生成压缩包gz的viteCompression,代理我没去掉,估计不会有跨域问题,可以自行试试)
import { fileURLToPath, URL } from "node:url";import { viteElectronDev } from "./plugins/vite.electron.dev";import { viteElectronBuild } from "./plugins/vite.electron.build";import { defineConfig, loadEnv, UserConfigExport, ConfigEnv } from "vite";// import zip from 'vite-plugin-zip';import react from "@vitejs/plugin-react";import ViteRestart from "vite-plugin-restart";import reactRefresh from "@vitejs/plugin-react-refresh";import path from "path";import vitePluginImp from "vite-plugin-imp";// import viteCompression from 'vite-plugin-compression';export default ({ command, mode }: ConfigEnv): UserConfigExport => { const env = loadEnv(mode, process.cwd()); // 获取.env文件里定义的环境变量 // console.log(env); //变量在命令行里打印出来 return defineConfig({ mode: env.VITE_APP_MODE, base: "./", // 设置打包路径 server: { host: "0.0.0.0", port: 9000, // 设置服务启动端口号 open: false, // 设置服务启动时是否自动打开浏览器 https: false, cors: true, // 允许跨域 // 设置代理,根据我们项目实际情况配置 proxy: { }, }, resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)), // '@': path.resolve(__dirname, './src'), "~": path.resolve(__dirname, ""), "#": path.join(__dirname, "types"), }, }, plugins: [ react(), reactRefresh(), viteElectronDev(), viteElectronBuild(), vitePluginImp({ // 按需引入 libList: [ { libName: "antd", style: (name) => `antd/es/${name}/style`, libDirectory: "es", }, ], }), // zip({ // // 打包zip // dir: 'photoSelect', // outputName: 'photoSelect' // }), ViteRestart({ // vite.config.js 和 .env文件后不用重启 restart: ["my.config.[jt]s"], }), // viteCompression({ // //生成压缩包gz // verbose: true, // disable: false, // threshold: 512, // 10240 // algorithm: 'gzip', // ext: '.gz' // }) ], css: { //* css模块化 modules: { // css模块化 文件以.module.[css|less|scss]结尾 generateScopedName: "[name]__[local]___[hash:base64:5]", hashPrefix: "prefix", }, preprocessorOptions: { less: { modifyVars: { // 更改主题在这里 // 'primary-color': '#87ceeb', // 全局主色 "primary-color": "#FF7E00", // 全局主色 "link-color": "#1890ff", // 链接色 "success-color": "#52c41a", // 成功色 "warning-color": "#faad14", // 警告色 "error-color": "#f5222d", // 错误色 "font-size-base": "14px", // 主字号 }, // modifyVars: getThemeVariables({ // // dark: true // 开启暗黑模式 // // compact: true, // 开启紧凑模式 // }), javascriptEnabled: true, }, }, }, });};
package.json中命令如下,执行 pnpm run dev 和 pnpm run build 即可
单纯的web项目还是执行pnpm start
{ "scripts": { "dev": "vite --mode dev", "build": "tsc && vite build", "start": "vite --host 0.0.0.0 --mode dev" }}
要知道,node其实没办法直接执行ts文件,所以执行命令后会在将根目录下electron里面的ts打包一份js在根目录下,生成新的文件,然后electron找的是这些js文件,这个不用管就行
electron-app
dist
main.js
preload.js
2.1.1 vite.electron.build.ts:打包时候走的程序
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代码为JavaScript // 主进程 require("esbuild").buildSync({ entryPoints: ["electron/main.ts"], bundle: true, outfile: "dist/main.js", platform: "node", target: "node12", external: ["electron"], }); // 渲染层 require("esbuild").buildSync({ entryPoints: ["electron/preload.ts"], bundle: true, outfile: "dist/preload.js", platform: "node", target: "node12", external: ["electron"], }); }; // 调用初始化Electron函数 initElectron(); // 修改package.json文件的main字段 不然会打包失败 const json = JSON.parse(fs.readFileSync("package.json", "utf-8")); json.main = "main.js"; fs.writeSync( fs.openSync("dist/package.json", "w"), JSON.stringify(json, null, 2) ); // 创建一个空的node_modules目录 不然会打包失败 fs.mkdirSync(path.join(process.cwd(), "dist/node_modules")); // 使用electron-builder打包Electron应用程序 electronBuilder.build({ config: { appId: "com.example.app", productName: "打包项目名字", directories: { output: path.join(process.cwd(), "release"), //输出目录 app: path.join(process.cwd(), "dist"), //app目录 }, asar: true, nsis: { installerIcon: 'assets/installer-icon.ico', // 安装程序图标文件的路径 oneClick: false, //取消一键安装 }, mac: { icon: 'assets/app.icns', }, dmg: { //backgroundColor: '#f1f1f6', background: 'assets/app.png', icon: 'assets/installer-icon.ico', iconSize: 160, window: { width: 600, height: 420, }, contents: [ { x: 150, y: 200, }, { x: 450, y: 200, type: 'link', path: '/Applications', }, ], sign: false, artifactName: '${productName}_mac_${arch}_${version}(${buildVersion}).${ext}', }, win: { icon: 'assets/icon.ico', target: [ { target: "nsis", arch: ["x64", "ia32"], }, ], }, linux: { icon: 'assets/app.icns', } }, }).then(() => { // 在这里执行构建成功后的逻辑 console.log('Electron应用程序构建成功'); fs.rmdirSync('./dist', { recursive: true }); }); }, };};
2.1.2 vite.electron.dev.ts:开发时候走的程序
import type { Plugin } from "vite";import type { AddressInfo } from "net";import { spawn } from "child_process";import fs from "fs";import os from "os";function getLocalIpAddress() { const interfaces = os.networkInterfaces(); for (const interfaceName in interfaces) { const addresses = interfaces[interfaceName]; for (const address of addresses) { if (address.family === "IPv4" && !address.internal) { return address.address; } } } return null;}const ipAddress = getLocalIpAddress();// 导出Vite插件函数export const viteElectronDev = (): Plugin => { return { name: "vite-electron-dev", // 在configureServer中实现插件的逻辑 configureServer(server) { // 定义初始化Electron的函数 const initElectron = () => { // 使用esbuild编译TypeScript代码为JavaScript require("esbuild").buildSync({ entryPoints: ["electron/main.ts"], bundle: true, outfile: "dist/main.js", platform: "node", target: "node12", external: ["electron"], }); require("esbuild").buildSync({ entryPoints: ["electron/preload.ts"], bundle: true, outfile: "dist/preload.js", platform: "node", target: "node12", external: ["electron"], }); }; // 调用初始化Electron函数 initElectron(); // 监听Vite的HTTP服务器的listening事件 server?.httpServer?.once("listening", () => { // 获取HTTP服务器的监听地址和端口号 const addressInfo = server?.httpServer?.address() as AddressInfo; // console.log(addressInfo); const IP = `http://${ipAddress}:${addressInfo.port}`; console.log(`本地IP地址:${IP}`); // 启动Electron进程 let electronProcess = spawn("electron", ["dist/main.js", IP]); const fsWatchFile = (fileName) => { fs.watchFile(`electron/${fileName}.ts`, () => { // 杀死当前的Electron进程 electronProcess.kill(); // 重新编译主进程代码并重新启动Electron进程 initElectron(); electronProcess = spawn("electron", [`dist/main.js`, IP]); }); } // 监听主进程代码的更改 fsWatchFile('main'); fsWatchFile('preload'); // console.log(electronProcess); // 监听Electron进程的stdout输出 electronProcess.stdout?.on("data", (data) => { console.log(`日志: ${data}`); }); }); }, };};
2.2 electron 主进程和渲染层还有web层的互相通信交互
2.2.1 main.ts 主进程
import { app, BrowserWindow, ipcMain, dialog } from "electron";import path from "path";app.whenReady().then(async () => { const win = await new BrowserWindow({ // 窗口的宽高 width: 1800, height: 900, // 配置窗口的WebPreferences选项,用于控制渲染进程的行为 webPreferences: { nodeIntegration: true, contextIsolation: true, preload: path.join(__dirname, "preload.js"), // 渲染进程,能满足交互作用 }, }); // 根据命令行参数加载URL或本地文件 配置好的开发环境,看情况而定 if (process.argv[2]) { win.loadURL(process.argv[2]); win.webContents.openDevTools(); // 开发时候使用 打开web的ip或者本地 可以热更新 } else { win.loadFile("index.html"); // 打包时候使用 打开的是本地的html } // web层借助渲染层和主进程的交互 ipcMain.on("open-dialog", (event) => { dialog .showOpenDialog({ properties: ["openDirectory"], }) .then((result) => { event.reply("open-dialog-reply", result); }); }); ipcMain.on('openFlyCar',()=>{ console.log('收到') })});app.on("window-all-closed", () => { if (process.platform !== "darwin") app.quit();});
2.2.2 preload.ts 渲染层
import { contextBridge, ipcRenderer, shell } from "electron";import path from 'path';// 和web端的交互contextBridge.exposeInMainWorld('electronAPI', { // 打开浏览器窗口 openExternal(url: string){ shell.openExternal(url); }, // 打开文件夹 async openPath() { const folderPath: string = "本地的地址"; shell.openPath(folderPath); }, // web层借助渲染层和主进程的交互 send: (channel, data) => ipcRenderer.send(channel, data), // web层借助渲染层和主进程的交互,切有回调 on: (channel, callback) => ipcRenderer.on(channel, (event, ...args) => callback(...args))});渲染层和主进程的交互const open = () => { ipcRenderer.send('openFlyCar')}open();
2.2.3 react中tsx web层
// 最外部写入let win: any = window;// 随便在按钮的点击事件中写即可// web层和渲染层交互通信win['electronAPI'].openExternal("http://www.baidu.com/"); // 唤起浏览器打开百度// web层借助渲染层和主进程交互通信win['electronAPI'].send('open-dialog'); // 打开资源管理器win['electronAPI'].on('open-dialog-reply', (result: any) => {if (!result.canceled) { // 选择了什么地址 // ...}});
3.暂时就这些了,估计会有一定的问题,如果有问题可以评论和私信,我会经常看的,可以互相交流学习,其中node可以在electron中随便使用,后续我会接着更新一些关于electron的方法,希望大家共同进步,共同成长,加油!