react+electron/现成的react项目直接转electron(2)能本地运行,打包,【web层,渲染层,主进程】之间的互相通信交互

难点:之前写了一个关于运行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的方法,希望大家共同进步,共同成长,加油!

  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咸喵小宇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值