vite+vue3+ts+electron桌面应用web端桌面端开发=>IPC进程通信!

802 阅读14分钟

Electron 进程通信 IPC(Inter-Process Communication)

前言

首先大家需要了解一些 Electron 的前置知识。众所周知 Electron 是将 chrome Chromium 内核Node.js 一起嵌入到一个二进制文件中来实现构建跨平台桌面应用的框架。其中 Chromium 提供 WebAPI(DOM、BOM) 的能力,Node.js 提供了调用 系统 API 的能力。

什么是进程?

进程(Process) 是操作系统中一个核心概念,用于描述程序的动态执行过程。它代表了程序在计算机内存中的一次运行实例,是操作系统进行资源分配和调度的基本单位。

上面的解释取自文心一言,我理解的进程便是 “微信” 与 “QQ” 的关系,两个应用便是两个进程。而操作系统便是他们的管理员,对它们进行资源分配和调度。

chromium 为什么是多进程架构?

Chromium 的多进程架构通过隔离故障、限制权限、并行计算和动态资源管理,实现了浏览器在稳定性、安全性、性能和资源利崩溃的平衡,尤其适应现代 Web 的复杂需求。

1. 稳定性保障

  • 崩溃隔离:每个标签页或插件运行在独立进程,单进程崩溃不影响其他页面或浏览器整体。
  • 内存泄漏可控:渲染进程内存泄漏仅影响自身,关闭进程即可释放资源,避免全局卡顿。

2. 安全性强化

  • 沙盒机制:渲染和插件进程在沙盒中运行,权限受限,恶意代码无法直接攻击系统。
  • 站点隔离:不同域名网页分配独立进程,防止跨站攻击(如 Spectre 等漏洞利用)。

3. 性能优化

  • 并行计算:多进程利用多核 CPU,GPU 进程独立加速渲染,提升复杂页面(如视频、游戏)性能。
  • 资源动态分配:根据进程优先级(如最近使用标签页)动态调整内存,减少磁盘交换,提升响应速度。

4. 资源灵活管理

  • 进程模型可配置:支持单进程(低资源设备)到多进程(高性能设备)的灵活切换。
  • 进程复用策略:相同域名页面共享进程,避免进程数量激增;进程超载时合并资源,平衡负载。

Electron 的进程模型

Electron 继承了 Chromium 的多进程架构,这使得该框架在架构上与现代 Web 浏览器非常相似。在 Electron 应用中主要分为 主进程(main)渲染进程(renderer),他们分别为:

  • 主进程:作为 Electron 应用的入口进程,它运行在 Node 环境中,因此它可以作为一个服务器给渲染进程提供调用系统级 API 的支持。主进程的主要目的是使用 BrowserWindow 模块创建和管理应用窗口。
  • 渲染进程:每个 Electron 应用都会为每个打开的 BrowserWindow(以及每个 Web 嵌入)生成一个单独的渲染进程。顾名思义,渲染器负责渲染 Web 内容。可以调用 chromium 提供的 WebAPI,它跟一个 chrome 浏览器没有任何区别,所以渲染进程中运行的代码的行为应符合 Web 标准。

可以理解为每个渲染进程都是一个独立的浏览器窗口,它们具备 chromium 浏览器所有的功能。

安全问题

是否开启 Node 集成(nodeIntegration)

BrowserWindow.webPreferences.nodeIntegration: true 时,为渲染进程集成 Node API。默认:false

eIMScreenShot20250617104814513.jpg

这是极其不推荐且非常不安全的做法,虽然它为我们在渲染进程操作系统级 API 带来了便利,但是相对应的也为恶意代码留下了可乘之机。例如:

// 假设存在 XSS 漏洞,攻击者注入以下代码:
const fs = require('fs');
fs.writeFileSync('/tmp/malicious.sh', '恶意脚本内容');
const { exec } = require('child_process');
exec('chmod +x /tmp/malicious.sh && /tmp/malicious.sh');

上下文隔离(contextIsolation)

上下文隔离是一项功能,可确保你的 preload 脚本和 Electron 的内部逻辑在与你在 webContents 中加载的网站不同的上下文中运行。这对于安全目的很重要,因为它有助于防止网站访问 Electron 内部结构或预加载脚本有权访问的强大 API。

这意味着你的预加载脚本有权访问的 window 对象实际上与网站有权访问的对象不同。例如,如果你在预加载脚本中设置了 window.hello = 'wave' 并启用了上下文隔离,则当网站尝试访问 window.hello 时,window.hello 将是未定义的。

自 Electron 12 起,上下文隔离已默认启用,并且它是所有应用的推荐安全设置。

上文我是直接粘贴官网的解释。简单来说就是 renderer.js 中的 windowpreload.js 中的 window 是否共享。

示例:

  1. 创建 src/typings/global.d.ts, 为 global 对象声明全局变量 username
  2. 创建 electron/preload.ts, 设置 window.username = 'adimn'
  3. main.ts 中配置 BrowserWindow.webPreferences.preload: path.join(__dirname, "preload.js")
  4. App.vue 中打印 console.log(window.username)
  5. vite.electron.config.ts 修改 electron 插件配置 为 electron([{ entry: "electron/main.ts" }, { entry: "electron/preload.ts" }])

eIMScreenShot20250617113450781.jpg

eIMScreenShot20250617113612859.jpg

打印结果为 admin,证明 preloadrenderer 使用的是同一个 window 对象。

  • 上述代码不变,仅修改 contextIsolation: true 时:

eIMScreenShot20250617113911619.jpg

可以看到打印是 undefined 也就证明了此时 window 是各自独立的。也就是文中所说的 是否共享 window

预加载脚本(preload)

出于安全原因,渲染进程默认只运行网页不运行 Node.js。所以为了将 Electron 的不同进程桥接在一起,我们需要使用一个称为预加载的特殊脚本。总而言之 preloadElectron 提供的一个可以安全访问 系统 API 的沙盒环境,是渲染进程安全访问 系统API 的桥梁。

  • preload 脚本中可以访问 WebAPI 以及有限的 Node.jsElectron API

从 Electron 20 开始,预加载脚本默认被沙箱化,并且不再能够访问完整的 Node.js 环境。

可用的 API细节
Electron 模块渲染进程模块
Node.js APIeventsurltimers
Polyfill 全局变量BufferprocessclearImmediatesetImmediate
contextBridge

preload 脚本中,使用 contextBridge.exposeInMainWorld(apiKey, api)contextBridge.exposeInIsolatedWorld(worldId,apiKey, api) 方法将 api 暴露给渲染进程。

注意:即便是开启了上下文隔离跟关闭Node 集成也需要规范小心的使用 contextBridge Api 才能保证安全的将接口提供给渲染进程使用。例如:

// ❌ 错误做法
contextBridge.exposeInMainWorld('myAPI', {  
  send: ipcRenderer.send  
})

// ✅ 正确做法  
contextBridge.exposeInMainWorld('myAPI', {  
 loadPreferences: () => ipcRenderer.invoke('load-prefs')  
})

第一种方式直接公开强大的 API,无需任何类型的参数过滤。这将允许任何网站发送任意 IPC 消息,而你不希望这种情况发生。公开基于 IPC 的 API 的正确方法是为每个 IPC 消息提供一种方法。

进程通信

进程间通信(IPC)是在 Electron 中构建功能丰富的桌面应用的关键部分。由于主进程和渲染进程在 Electron 的进程模型中具有不同的职责,因此 IPC 是执行许多常见任务的唯一方法,例如从 UI 调用原生 API 或从原生菜单触发 Web 内容的更改等。

  • ipcMainipcMain 模块是 Event Emitter。 当在主进程中使用时,它处理从渲染器进程(网页)发送的异步和同步消息。从渲染器发送的消息将被发送到该模块

  • ipcRendereripcRenderer 模块是 EventEmitter。它提供了一些方法,以便你可以从渲染进程(网页)向主进程发送同步和异步消息。你还可以接收来自主进程的响应

进程通信的四种模式

根据 ipcRenderer.sendipcMain.onipcRenderer.invokeipcMain.handle 以及 MessageChannel API 可以组合为 4 种基本的通信模式。

一、 渲染进程 ==> 主进程(单向)

这种模式通常是渲染进程需要使用主进程系统 api 的能力来完成一些需求,且无需等待主进程的执行结果时使用。例如下面一个动态修改窗口 title 的示例。

  1. 在主进程中添加 ipcMain.on(channel, callback) 方法监听渲染进程发送的消息动态修改标题。
  2. 使用 ipcRenderer.send(channel, ...args) 方法发送消息通知主进程修改标题。

注意:所有在 renderer 中调用的 API 都是在 contextIsolation: true 以及 未开启 nodeIntegration: false 的沙箱环境下在 preload 中使用 contextBridge API 提供的。

类型声明别漏了🫡

# global.d.ts

export interface ElectronAPI {
  setTitle(title: string): void;
}

declare global {
  var electronAPI: ElectronAPI;
}

export {};


# main.ts

import { ipcMain } from 'electron'

ipcMain.on("set-title", (event, title) => {
  const win = BrowserWindow.fromWebContents(event.sender);
  win?.setTitle(title);
});
# preload.ts

import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('electronAPI', {
    setTitle: (title: string) => {
        ipcRenderer.send('set-title', title)
    }
})
# App.vue

<script setup lang="ts">
import { ref } from "vue";
const title = ref("App Title");

function setTitle() {
  window.electronAPI.setTitle(title.value);
}
</script>

<template>
  <input type="text" v-model="title" />
  <button @click="setTitle">确认</button>
</template>

eIMScreenShot20250617160747335.jpg

二、 主进程 <==> 渲染进程

双向 IPC 的常见应用场景通常是渲染进程需要使用主进程系统 API 的某些能力并等待返回结果时。通过使用 ipcRenderer.invokeipcMain上述andle 配对来完成。添加初始化时获取当前窗口 title 的示例:

  1. 在主进程使用 ipcMain.handle('get-title') 添加一个处理器等待 ipcRenderer.invoke('get-title') 触发。
  2. preload 脚本中 调用 ipcRenderer.invoke('get-title')getTitle API 提供给 renderer
  3. renderer 中调用 window.electronAPI.getTitle() 获取当前窗口的默认标题。
export interface ElectronAPI {
  getTitle(): Promise<string>;
}

declare global {
  var electronAPI: ElectronAPI;
}

export {};
# main.ts

import { ipcMain } from "electron";

ipcMain.handle("get-title", (event) => {
  const webContents = event.sender;
  const win = BrowserWindow.fromWebContents(webContents);
  return win?.getTitle();
});
# preload.ts

import { contextBridge, ipcRenderer } from "electron";

contextBridge.exposeInMainWorld("electronAPI", {
  getTitle: () => {
    return ipcRenderer.invoke("get-title");
  }
});
# App.vue

<script setup lang="ts">
import { ref } from "vue";
const title = ref("App Title");

window.electronAPI.getTitle().then((value) => {
  title.value = value;
});
</script>

<template>
  <h1>{{ title }}</h1>
</template>

可以看到 h1 标签中显示的是 Vite + Vue + TS,而不是默认的 App Title,就证明我们已经通过主进程的 api 获取了窗口默认的标题。

eIMScreenShot20250617162631709.jpg

ipcRenderer.invokeelectron 7 中添加的新 api,也可以使用 ipcRenderer.sendipcRenderer.sendSync 来实现双向通信,但并不推荐。其原因便是开发代码的冗余跟同步代码带来的性能阻塞问题。参考

三、 主进程 ==> 渲染进程

当从主进程向渲染器进程发送消息时,需要指定哪个渲染器正在接收该消息。消息需要通过渲染进程的 WebContents 实例发送到渲染进程。此 WebContents 实例包含一个 send 方法,其使用方式与 ipcRenderer.send 相同。添加一个点击系统菜单提醒用户的功能示例:

  1. 在主进程使用 Menu.buildFromTemplate 添加一个 "提醒用户" 的菜单按钮,并添加 send 事件。 2.preload 中使用 ipcRenderer.on('message') 添加 onMessage 函数暴露给渲染进程。
  2. renderer 中调用 window.electron.onMessage() 监听主进程发送的消息
  3. 还可以在 preload 中额外添加一个 sendMessage 函数来处理渲染进程需要回复消息的情况。当然,这不是必须的。
# global.d.ts

export interface ElectronAPI {
  onMessage: (callback: (...args: any[]) => void) => void;
  replyMessage: (...args: any[]) => void;
}

declare global {
  var electronAPI: ElectronAPI;
}

export {};
# main.ts

import { app, BrowserWindow, ipcMain, Menu } from "electron";
import path from "node:path";

const createWindow = () => {
  const win = new BrowserWindow({
    width: 960,
    height: 600,
    webPreferences: {
      nodeIntegration: false, // 设置是否在页面中启用 Node.js 集成模式
      contextIsolation: true, // 设置是否启用上下文隔离模式。
      preload: path.join(__dirname, "preload.js"),
    },
  });

  ipcMain.on("reply-message", (_, message) => {
    console.log(message); // 打印:"收到消息了😉"
  });

  const menu = Menu.buildFromTemplate([
    {
      label: "提醒用户",
      click: () => {
        win.webContents.send("message", "测试消息");
      },
    },
  ]);

  win.setMenu(menu);

  if (process.env.VITE_DEV_SERVER_URL) {
    win.loadURL(process.env.VITE_DEV_SERVER_URL);
  } else {
    win.loadFile(path.join(__dirname, "../dist/index.html"));
    // win.loadFile(path.join(__dirname, '../index.html'));
    // win.loadFile(path.join(__dirname, '../web-dist/index.html'));
  }
};

app.whenReady().then(createWindow);
# preload.ts

import { contextBridge, ipcRenderer } from "electron";

contextBridge.exposeInMainWorld("electronAPI", {
  onMessage: (callback: (...args: any[]) => void) => {
    ipcRenderer.on("message", (_, ...args) => callback(...args));
  },
  replyMessage: (message: string) => {
    ipcRenderer.send("reply-message", message);
  },
});
# App.vue

<script setup lang="ts">
import { ref } from "vue";
const title = ref("App Title");

window.electronAPI.onMessage((value) => {
  window.alert(value);
  window.electronAPI.replyMessage("收到消息了😉");
});
</script>

<template>
  <h1>{{ title }}</h1>
</template>

eIMScreenShot20250617164915110.jpg

四、 渲染进程 1 <==> 渲染进程 2

没有直接的方法可以使用 ipcMainipcRenderer 模块在 Electron 中的渲染进程之间发送消息。想要实现这种模式,有两种选择:

  • 使用主进程作为渲染进程之间的消息代理,由渲染进程向主进程发送消息,在由主进程将消息转发到另一个渲染进程。
  • 使用 MessagePort 从主进程传递到两个渲染器。这将允许在初始化后渲染器之间进行直接通信。

开始之前先做一点前置准备

  • 新建 public/login.html 文件作为第二个渲染进程。
  • electron/main.ts 新增 createLoginWindow 函数,用于创建 login 渲染进程。
  • 新建 electron/loginPreload.ts 文件作为 login 渲染进程的 preload 脚本。
  • 修改 App.vue 的代码,添加一个 登录按钮,用于打开 LoginWindow
  • electron/main.tselectron/preload.tsApp.vue 分别添加跟调用 open-login-view 这个事件的函数。
  • vite.electron.config.ts中 添加 electron([...,{ entry: "electron/loginPreload.ts" }])
# global.d.ts

export interface ElectronAPI {
  openLoginView: () => void;
}

declare global {
  var electronAPI: ElectronAPI;
}

export {};
# login.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Login</h1>
    <input type="button" value="Login" onclick="login()" />
    <script>
      function login() {
        // 登录成功
        console.log("登录成功");
      }
    </script>
  </body>
</html>
# main.ts

import { app, BrowserWindow, ipcMain } from "electron";
import path from "node:path";

const createWindow = () => {
  const win = new BrowserWindow({
    width: 960,
    height: 600,
    webPreferences: {
      nodeIntegration: false, // 设置是否在页面中启用 Node.js 集成模式
      contextIsolation: true, // 设置是否启用上下文隔离模式。
      preload: path.join(__dirname, "preload.js"),
    },
  });

  if (process.env.VITE_DEV_SERVER_URL) {
    win.loadURL(process.env.VITE_DEV_SERVER_URL);
  } else {
    win.loadFile(path.join(__dirname, "../dist/index.html"));
    // win.loadFile(path.join(__dirname, '../index.html'));
    // win.loadFile(path.join(__dirname, '../web-dist/index.html'));
  }
};

// 🚀
const createLoginWindow = () => {
  const win = new BrowserWindow({
    webPreferences: {
      nodeIntegration: false, // 设置是否在页面中启用 Node.js 集成模式
      contextIsolation: true, // 设置是否启用上下文隔离模式。
      preload: path.join(__dirname, "loginPreload.js"),
    },
  });

  if (process.env.VITE_DEV_SERVER_URL) {
    // path.join(process.env.VITE_DEV_SERVER_URL, "login.html") => http://localhost:3000/login.html
    win.loadURL(path.join(process.env.VITE_DEV_SERVER_URL, "login.html"));
  } else {
    win.loadFile(path.join(__dirname, "../dist/index.html"));
  }
};

// 🚀
ipcMain.handle("open-login-view", () => {
  createLoginWindow();
});

app.whenReady().then(() => {
  createWindow();
});
# preload.ts

import { contextBridge, ipcRenderer } from "electron";

contextBridge.exposeInMainWorld("electronAPI", {
  openLoginView: () => ipcRenderer.invoke("open-login-view"),
});
# App.vue

<script setup lang="ts">
function openLoginView() {
  window.electronAPI.openLoginView();
}
</script>

<template>
  <h1>首页</h1>
  <button @click="openLoginView">登录</button>
</template>
# vite.electron.config.ts

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import electron from "vite-plugin-electron";
import { rmSync } from "node:fs";

// 先将dist、dist-electron、release文件夹强制删除在进行后续的打包流程
rmSync("dist", { recursive: true, force: true });
rmSync("dist-electron", { recursive: true, force: true });
rmSync("release", { recursive: true, force: true });

export default defineConfig({
  plugins: [
    vue(),
    electron([
      { entry: "electron/main.ts" },
      { entry: "electron/preload.ts" },
      // 🚀
      { entry: "electron/loginPreload.ts" },
    ]),
  ],
});

eIMScreenShot20250617172658778.jpg

OK!准备就绪!下面实现一个模拟登录成功后通知主渲染进程来调用系统通知的 API 通知用户登录成功的操作。

1. 代理中转模式

这个模式我就不演示啦,流程就是 渲染进程1 <=> 主进程 <=> 渲染进程2

2.MessagePort 模式

这个模式是在主进程 使用 MessageChannel API 创建一个信息通道,分别给渲染进程去使用,实现两个渲染进程之间直接可以进行通信。就像一条管道一样,能够互相发送消息以及回复消息。

  1. 使用 MessageChannel API 创建一个消息通道,分别在 ready-to-show 事件中发送给 mainWindowloginWindow 两个渲染进程使用。
  2. 在主进程中新增名为 close-windowshow-notification 的处理器等待调用。
  3. loginPreload.ts、跟 preload.ts 使用 ipcRenderer.on('port') 获取 MessagePort
  4. loginPreload.ts 添加 postMessage 函数暴露给 loginWindow 渲染进程。
  5. login.html 调用 window.electronAPI.postMessage 通知 mainWindow 已经登陆成功。
  6. preload.ts 文件中根据 data.type 处理对应事件逻辑。
# main.ts

import {
  app,
  BrowserWindow,
  ipcMain,
  MessageChannelMain,
  Notification,
  NotificationConstructorOptions,
} from "electron";
import path from "node:path";

// 🚀
const { port1, port2 } = new MessageChannelMain();

const createWindow = () => {
  const win = new BrowserWindow({
    width: 960,
    height: 600,
    webPreferences: {
      nodeIntegration: false, // 设置是否在页面中启用 Node.js 集成模式
      contextIsolation: true, // 设置是否启用上下文隔离模式。
      preload: path.join(__dirname, "preload.js"),
    },
  });

  if (process.env.VITE_DEV_SERVER_URL) {
    win.loadURL(process.env.VITE_DEV_SERVER_URL);
  } else {
    win.loadFile(path.join(__dirname, "../dist/index.html"));
    // win.loadFile(path.join(__dirname, '../index.html'));
    // win.loadFile(path.join(__dirname, '../web-dist/index.html'));
  }

// 🚀
  win.once("ready-to-show", () => {
    win.webContents.postMessage("port", null, [port1]);
  });
};

const createLoginWindow = () => {
  const win = new BrowserWindow({
    webPreferences: {
      nodeIntegration: false, // 设置是否在页面中启用 Node.js 集成模式
      contextIsolation: true, // 设置是否启用上下文隔离模式。
      preload: path.join(__dirname, "loginPreload.js"),
    },
  });

  if (process.env.VITE_DEV_SERVER_URL) {
    // path.join(process.env.VITE_DEV_SERVER_URL, "login.html") => http://localhost:3000/login.html
    win.loadURL(path.join(process.env.VITE_DEV_SERVER_URL, "login.html"));
  } else {
    win.loadFile(path.join(__dirname, "../dist/index.html"));
  }

// 🚀
  win.once("ready-to-show", () => {
    win.webContents.postMessage("port", null, [port2]);
  });
};

ipcMain.handle("open-login-view", () => {
  createLoginWindow();
});

// 🚀
ipcMain.handle("close-window", (event) => {
  const win = BrowserWindow.fromWebContents(event.sender);
  win?.close();
});

// 🚀
ipcMain.handle(
  "show-notification",
  (_, options: NotificationConstructorOptions) => {
    if (Notification.isSupported()) {
      new Notification(options).show();
    }
  }
);

app.whenReady().then(createWindow);

# loginPreload.ts

import { contextBridge, ipcRenderer } from "electron";

let port: MessagePort | null = null;

ipcRenderer.on("port", (event) => {
  port = event.ports[0];

  port.start();
});

contextBridge.exposeInMainWorld("electronAPI", {
  postMessage: (message: any, options?: StructuredSerializeOptions) => {
    port?.postMessage(message, options);
  },
});
# login.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Login</h1>
    <input type="button" value="Login" onclick="login()" />
    <script>
      function login() {
        // 登录成功...

        // 通知主渲染进程
        window.electronAPI.postMessage({
          type: "login-success",
          response: {
            code: 200,
            data: { username: "admin", password: "admin" },
            message: "登录成功",
          },
        });
      }
    </script>
  </body>
</html>
# preload.ts
import {
  contextBridge,
  ipcRenderer,
  type NotificationConstructorOptions,
} from "electron";

ipcRenderer.on("port", (event) => {
  const port = event.ports[0];
  port.onmessage = (messageEvent) => {
    const { data } = messageEvent;
    switch (data.type) {
      case "login-success":
        const options: NotificationConstructorOptions = {
          title: "登录成功",
          body: `您好,${data.response.data.username}。恭喜登录成功,很高心见到你!`,
        };
        ipcRenderer.invoke("show-notification", options);
        break;
      default:
        console.warn("没有对应的事件处理");
        break;
    }
  };

  port.start();
});

contextBridge.exposeInMainWorld("electronAPI", {
  openLoginView: () => ipcRenderer.invoke("open-login-view"),
});

eIMScreenShot20250617183906894.jpg

这就完成啦🫡!这种模式的通信流程便是 渲染进程1 <===> 渲染进程2,就不用像第一种模式那种需要主进程作为中间代理来实现了。

总结

Electron 中有 “主进程” 跟 “渲染进程” 两类进程模型。而进程的通信实现起来代码其实并不多,主要还是对进程与进程通信的概念方式的理解为主。方法就那么几个,可以灵活使用,当然最好是以官方推荐的为主。

  • 主进程: 主进程负责给渲染进程提供调用系统级 API 的支持。跟管理渲染进程的创建和管理。

  • 渲染进程: 渲染 Web 内容。提供了 WebApi。

  • preload: 出于安全原因,建议开启 “上下文隔离”,关闭渲染模式的 “node 集成模式”,并在 preload 中使用 contextBridge API 安全的暴露渲染进程需要使用的 api。

  • 四种通信模式:

    • 渲染进程 ==> 主进程: ipcRenderer.send/ipcRenderer.sendSync/ipcMain.onipcRenderer.invoke/ipcMain.handle
    • 主进程 <==> 渲染进程: ipcRenderer.send/ipcRenderer.sendSync/win.webContents.send/ipcMain.on/ipcRenderer.onipcRenderer.invoke/ipcMain.handle(推荐)。
    • 主进程 ==> 渲染进程: win.webContents.send/ipcRenderer.on
    • 渲染进程 <==> 渲染进程: MessageChannel API、主进程代理中转

这里额外补充一个最近出现的问题,就是最近刚写这篇文章的时候,重新 pnpm i 为我的项目安装依赖后,执行 pnpm electron:dev 命令会报错。这时候全局安装一个依赖 electron-fix 然后执行 electron-fix start 来修复错误,完成以后就可以了。

image.png

任务列表

  • 搭建electron项目
    • 运行 electron 桌面端开发环境
    • 运行 web 浏览器端开发环境
  • 打包
  • electron 通信