难点:之前写了一个关于运行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的vite***pression,代理我没去掉,估计不会有跨域问题,可以自行试试)
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 vite***pression from 'vite-plugin-***pression';
export default ({ ***mand, 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"],
}),
// vite***pression({
// //生成压缩包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", // 链接色
"su***ess-color": "#52c41a", // 成功色
"warning-color": "#faad14", // 警告色
"error-color": "#f5222d", // 错误色
"font-size-base": "14px", // 主字号
},
// modifyVars: getThemeVariables({
// // dark: true // 开启暗黑模式
// // ***pact: 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: "***.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.i***s',
},
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.i***s',
}
},
}).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 "***";
import { spawn } from "child_process";
import fs from "fs";
import os from "os";
function getLocalIpAddress() {
const interfaces = os.***workInterfaces();
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.***/"); // 唤起浏览器打开百度
// web层借助渲染层和主进程交互通信
win['electronAPI'].send('open-dialog'); // 打开资源管理器
win['electronAPI'].on('open-dialog-reply', (result: any) => {
if (!result.canceled) {
// 选择了什么地址
// ...
}
});
3.暂时就这些了,估计会有一定的问题,如果有问题可以评论和私信,我会经常看的,可以互相交流学习,其中node可以在electron中随便使用,后续我会接着更新一些关于electron的方法,希望大家共同进步,共同成长,加油!