文章目录

Electron 桌面应用开发学习笔记

适合目标:8 小时内建立 Electron 桌面应用完整知识框架,覆盖 Electron 核心架构、Bridge 交互、IPC、调用 C++、自动更新、性能优化、CI/CD 自动化打包构建发布与高频面试题。
学习重点:主进程/渲染进程/Preload 的职责边界,安全通信模型,原生能力接入,桌面端发布链路,以及真实项目中的性能和工程化方案。
学习原则:先理解进程模型,再记 API;先掌握安全边界,再写功能;先会搭架构,再做复杂集成。


目录

  1. 学习总览
  2. Electron 到底在解决什么
  3. Electron 核心架构
  4. 主进程、渲染进程、Preload
  5. Electron 中的 Bridge 交互
  6. IPC 通信与工程化封装
  7. Electron 调用 C++
  8. 自动更新与增量更新
  9. 性能优化与安全优化
  10. Electron 项目 CI/CD 与自动化打包发布
  11. 实战项目架构建议
  12. 高频面试题
  13. 更好记的学习方法
  14. 8 小时学习节奏建议
  15. 一页速记总结
  16. 背诵口诀

1. 学习总览

1.1 Electron 是在学什么

很多人第一次接触 Electron,会把它理解成:

用前端技术做桌面应用。

这句话没有错,但还不够完整。

更准确地说,Electron 学的是:

如何让 Web 技术跑在桌面容器里,并安全地接入操作系统能力。

所以 Electron 的关键不只是“会写页面”,而是下面这些问题:

  1. 页面和系统能力怎么连接
  2. 主进程和渲染进程怎么分工
  3. 前端代码如何安全访问 Node 和系统 API
  4. 如何调用本地原生模块或 C++
  5. 如何构建安装包、自动更新和发布
  6. 如何处理桌面应用的性能、内存和稳定性

1.2 你可以把 Electron 看成什么

把 Electron 压缩成一句话:

Chromium 负责界面,Node.js 负责系统能力,Electron 负责把两者组织起来。

所以 Electron 本质上是一个桌面应用运行时,它把:

  1. 浏览器渲染能力
  2. Node.js 系统能力
  3. 桌面应用容器能力

组合到了一起。

1.3 学这份笔记要抓哪几条主线

主线 1:进程模型

  • 主进程做什么
  • 渲染进程做什么
  • Preload 为什么重要

主线 2:Bridge 与 IPC

  • 渲染层如何安全调用系统能力
  • 为什么不能直接全开 Node
  • 怎么做安全桥接

主线 3:原生能力接入

  • 为什么 Electron 还要调用 C++
  • 原生模块怎么接
  • ABI、重编译、跨平台怎么处理

主线 4:发布与更新

  • 怎么打包成安装包
  • 怎么做自动更新
  • 怎么做增量更新
  • 怎么做 CI/CD 自动构建发布

主线 5:性能与稳定性

  • 为什么 Electron 应用容易大、容易吃内存
  • 如何优化加载和运行效率
  • 如何做桌面应用级别的安全控制

1.4 核心记忆主线

把 Electron 记成一句话:

界面在渲染进程,系统能力在主进程,Preload/Bridge 负责安全连接,打包更新和 CI/CD 负责真正交付。

再压缩成六个词:

进程、桥接、原生、更新、优化、交付


2. Electron 到底在解决什么

2.1 为什么前端团队会用 Electron

Electron 最大的吸引力是:

  1. 前端团队可用熟悉的技术栈做桌面应用
  2. React / Vue / Vite / Webpack 等生态可以复用
  3. 同时还能访问文件系统、托盘、菜单、通知等桌面能力
  4. 跨平台能力较强

2.2 Electron 适合哪些项目

常见场景:

  1. 编辑器类应用
  2. IM / 客户端工具
  3. 数据分析工具
  4. 开发者工具
  5. 企业内部桌面工作台
  6. 音视频辅助工具
  7. AI 桌面助手

2.3 Electron 的优势

  1. 前端开发成本低
  2. 跨平台开发效率高
  3. Node.js 能力丰富
  4. 社区成熟,周边工具多

2.4 Electron 的代价

  1. 安装包通常偏大
  2. 内存占用通常高于原生应用
  3. 进程模型更复杂
  4. 安全边界要自己管好
  5. 原生模块和签名发布链路更复杂

2.5 面试里怎么概括 Electron

可以答:

Electron 是一个基于 Chromium 和 Node.js 的桌面应用运行时,允许开发者用 Web 技术构建跨平台桌面应用。它的核心在于主进程、渲染进程和 Preload 的分工,以及通过 IPC 和 Bridge 安全地连接页面与系统能力。


3. Electron 核心架构

3.1 Electron 的核心组成

可以先记住这三块:

  1. Main Process
  2. Renderer Process
  3. Preload

3.2 Main Process 是什么

主进程可以理解成:

Electron 应用的后台控制中心。

它通常负责:

  1. 创建窗口
  2. 管理应用生命周期
  3. 控制菜单、托盘、通知
  4. 调系统 API
  5. 管理 IPC
  6. 调原生模块
  7. 做自动更新

3.3 Renderer Process 是什么

渲染进程本质上就是:

一个跑页面的浏览器环境。

它负责:

  1. 页面 UI
  2. 用户交互
  3. 业务状态展示
  4. 调用通过 Bridge 暴露出来的能力

3.4 Preload 是什么

Preload 可以理解成:

运行在渲染进程和页面之间的一层安全中介。

它常做的事情:

  1. 注入安全 API
  2. 收敛 IPC 能力
  3. 屏蔽主进程和 Node 细节
  4. 为页面提供白名单化接口

3.5 一个最重要的架构认识

在现代 Electron 项目里,推荐是:

  1. 页面不直接访问 Node
  2. 页面不直接拿 ipcRenderer
  3. 页面通过 contextBridge 暴露出来的 API 工作

也就是说:

Renderer 不应该直接拥有全部系统能力。

3.6 Electron 的典型调用链

最常见链路:

Renderer UI -> Preload Bridge -> ipcRenderer -> Main -> 系统能力 / 原生模块 -> 返回结果 -> Renderer 更新 UI

3.7 为什么 Electron 要分这么多层

因为它本质上是在解决两个矛盾:

  1. 前端想用系统能力
  2. 但系统能力不能直接无边界暴露给页面

所以必须分层:

  1. 页面层只关心业务
  2. Bridge 层负责授权和收口
  3. 主进程负责真正执行系统动作

3.8 一句话压缩架构

主进程管应用和系统,渲染进程管界面,Preload 管安全桥接。


4. 主进程、渲染进程、Preload

这一部分是 Electron 的绝对重点,很多面试题和工程设计题都绕不开这里。

4.1 主进程最常做的事情

常见职责:

  1. app.whenReady
  2. 创建 BrowserWindow
  3. 监听应用生命周期
  4. 注册菜单和托盘
  5. 注册 IPC
  6. 访问文件系统
  7. 启动自动更新

示例:

const { app, BrowserWindow } = require("electron");
const path = require("path");

function createWindow() {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
      contextIsolation: true,
      nodeIntegration: false,
    },
  });

  win.loadURL("http://localhost:5173");
}

app.whenReady().then(createWindow);

4.2 渲染进程最常做的事情

渲染进程更像普通前端项目:

  1. 页面渲染
  2. 状态管理
  3. 组件交互
  4. 调用 window.api.xxx

例如:

const version = await window.appBridge.getVersion();

4.3 Preload 的核心价值

Preload 最大价值不是“多了一层”,而是:

把不安全的大权限,变成安全的小接口。

例如:

页面不应该有:

window.require("fs")

更好的做法是:

window.fileBridge.readText(filePath)

4.4 为什么推荐 contextIsolation: true

因为它能把页面上下文和 Electron/Node 上下文隔离开。

这样做的好处:

  1. 降低页面被注入后直接拿到 Node 能力的风险
  2. 更适合通过 contextBridge 明确暴露能力
  3. 是现代 Electron 项目的推荐安全基线

4.5 为什么一般不推荐 nodeIntegration: true

因为一旦打开,页面脚本就可能直接访问 Node 能力:

  1. fs
  2. child_process
  3. 本地文件
  4. 系统命令

这对安全来说风险很大。

所以更推荐:

nodeIntegration: false + preload + contextBridge

4.6 主进程和渲染进程怎么分工才合理

推荐思路:

放在主进程的能力

  1. 文件系统
  2. 系统托盘
  3. 自动更新
  4. 原生模块
  5. 菜单与快捷键
  6. 子进程管理

放在渲染进程的能力

  1. UI 渲染
  2. 状态管理
  3. 视图逻辑
  4. 表单交互

放在 Preload 的能力

  1. 暴露安全 API
  2. 收敛 IPC
  3. 参数规整
  4. 错误格式统一

4.7 一个合理的目录结构示例

src/
  main/
    index.ts
    ipc/
    updater/
    native/
  preload/
    index.ts
    bridge/
  renderer/
    pages/
    components/
    store/
  shared/
    types/
    constants/
native/
  addon/
build/
  scripts/
.github/
  workflows/

4.8 一句话压缩职责分工

Main 做重权限和全局控制,Renderer 做界面,Preload 做授权桥。


5. Electron 中的 Bridge 交互

这里的 Bridge 不是移动端 H5 Bridge,而是 Electron 里 Renderer 和系统能力之间的安全桥接层,核心通常基于 contextBridge + IPC

5.1 为什么 Electron 也需要 Bridge

因为现代 Electron 项目里,一般不应该让页面直接拿到:

  1. Node 能力
  2. ipcRenderer
  3. 原生模块对象
  4. 任意系统命令能力

所以需要一层 Bridge:

把页面需要的能力白名单化、最小化、稳定化。

5.2 Bridge 的本质

你可以把 Electron 里的 Bridge 理解成:

Renderer 可调用的前端 SDK。

页面看到的是:

window.appBridge.getVersion()
window.fileBridge.readText(path)
window.nativeBridge.hash(data)

而不是:

ipcRenderer.invoke("sys:raw", ...)

5.3 一个最小 Bridge 示例

preload.js

const { contextBridge, ipcRenderer } = require("electron");

contextBridge.exposeInMainWorld("appBridge", {
  getVersion: () => ipcRenderer.invoke("app:getVersion"),
  selectFile: () => ipcRenderer.invoke("file:select"),
});

main.js

const { app, ipcMain, dialog } = require("electron");

ipcMain.handle("app:getVersion", () => app.getVersion());

ipcMain.handle("file:select", async () => {
  const result = await dialog.showOpenDialog({
    properties: ["openFile"],
  });
  return result.filePaths;
});

renderer.js

const version = await window.appBridge.getVersion();
console.log(version);

5.4 为什么不建议把 ipcRenderer 直接暴露给页面

因为这样页面就能自己拼各种消息:

  1. 权限边界不清晰
  2. 调用方式不可控
  3. 不利于统一日志和参数校验
  4. 容易被滥用

更好的方式是:

只暴露经过封装的业务 API

5.5 Bridge 设计的推荐原则

  1. 白名单化
  2. 参数结构稳定
  3. 返回结构统一
  4. 不暴露底层原始对象
  5. 能区分同步能力和异步能力
  6. 能做权限和版本控制

5.6 Bridge API 怎么设计更好记

推荐按领域分:

  1. appBridge
  2. fileBridge
  3. systemBridge
  4. nativeBridge
  5. updateBridge

这样比一个超大 window.api 更清晰。

5.7 Electron Bridge 和移动端 Bridge 的区别

共同点:

  1. 都是在“页面层”和“系统能力”之间搭桥
  2. 都要考虑协议、安全、回调

不同点:

  1. Electron 更常通过 contextBridge + IPC
  2. Hybrid App 更常是 WebView 与 Native 容器通信

5.8 一句话压缩 Electron Bridge

Electron Bridge 就是把系统能力包装成页面可安全调用的 API。


6. IPC 通信与工程化封装

6.1 什么是 IPC

IPC 是 Inter-Process Communication,即进程间通信。

在 Electron 里,常见的是:

  1. ipcRenderer
  2. ipcMain

用来完成:

渲染进程 <-> 主进程

之间的消息传递。

6.2 为什么 Electron 必须要 IPC

因为:

  1. UI 在渲染进程
  2. 很多系统能力在主进程

所以必须有通信机制把二者连起来。

6.3 两种最常见通信方式

invoke / handle

适合:

  1. 请求-响应型调用
  2. Promise 风格
  3. 获取结果型能力

send / on

适合:

  1. 单向通知
  2. 事件推送
  3. 状态广播

6.4 invoke / handle 示例

// preload
contextBridge.exposeInMainWorld("userBridge", {
  getProfile: () => ipcRenderer.invoke("user:getProfile"),
});

// main
ipcMain.handle("user:getProfile", async () => {
  return { name: "Tom", role: "admin" };
});

6.5 send / on 示例

// renderer
window.logBridge.startTask();

// preload
contextBridge.exposeInMainWorld("logBridge", {
  startTask: () => ipcRenderer.send("task:start"),
  onProgress: (handler) => ipcRenderer.on("task:progress", (_, data) => handler(data)),
});

// main
ipcMain.on("task:start", (event) => {
  event.sender.send("task:progress", { percent: 10 });
});

6.6 什么时候用 invoke,什么时候用事件

一个简单记法:

  1. 要结果:用 invoke
  2. 要通知:用事件

6.7 为什么要统一 channel 命名

推荐这样命名:

domain:action

例如:

  1. app:getVersion
  2. file:readText
  3. native:hash
  4. update:check

这样更清晰,也方便排查日志。

6.8 为什么 IPC 要做二次封装

因为直接在页面里写:

ipcRenderer.invoke("file:readText", path)

有这些问题:

  1. 业务层和底层通信耦合
  2. 参数不统一
  3. 日志和错误不集中
  4. 不利于未来重构

所以推荐:

页面 -> Bridge API -> IPC -> Main

6.9 IPC 的常见坑

  1. 事件监听不清理导致内存泄漏
  2. 同一个事件多次注册
  3. 传输大对象导致性能问题
  4. 渲染进程直接调用高危能力
  5. Main 中逻辑过重导致卡顿

6.10 一句话压缩 IPC

IPC 是 Electron 进程之间的通信总线,Bridge 是基于 IPC 暴露给页面的安全接口层。


7. Electron 调用 C++

这是你特别点名的重点。Electron 调用 C++,本质上是在“前端桌面壳”之外,接入真正的原生计算能力或已有本地 SDK。

7.1 为什么 Electron 项目还要调 C++

常见原因:

  1. 需要高性能计算
  2. 需要调用已有 C/C++ SDK
  3. 需要系统底层能力
  4. 需要加解密、编解码、图像处理、硬件控制
  5. 需要复用已有桌面端或客户端历史能力

7.2 Electron 调 C++ 的常见方式

你可以先记三种:

方式 适合场景 特点
Node-API / N-API 原生模块 深度集成、性能好、像普通 JS 模块一样调用 最主流
child_process 调本地二进制 已有独立 C++ 程序或 CLI 解耦强,部署稍重
FFI 调动态库 调现成 DLL / so / dylib 上手快,但维护性和兼容性要评估

学习主线建议优先掌握:

Node-API 原生模块

7.3 最推荐记住的方案:Node-API

Node-API 的核心价值是:

让 C++ 模块以稳定 ABI 方式暴露给 Node.js / Electron 使用。

你可以把它理解成:

把 C++ 封装成 JS 可 require/import 的模块

7.4 一个最小 C++ Addon 示例

native/addon/addon.cc

#include <napi.h>

Napi::Number Add(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  double a = info[0].As<Napi::Number>().DoubleValue();
  double b = info[1].As<Napi::Number>().DoubleValue();
  return Napi::Number::New(env, a + b);
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set("add", Napi::Function::New(env, Add));
  return exports;
}

NODE_API_MODULE(addon, Init)

binding.gyp

{
  "targets": [
    {
      "target_name": "addon",
      "sources": ["native/addon/addon.cc"]
    }
  ]
}

main.js

const addon = require("../build/Release/addon.node");

console.log(addon.add(1, 2));

7.5 更推荐的写法:node-addon-api

在实际项目里,很多人会配合 node-addon-api 使用,因为写法更现代、可读性更好。

学习时你要先理解本质:

  1. JS 调模块函数
  2. 模块内部执行 C++
  3. 结果再返回给 JS

7.6 Electron 调 C++ 的推荐链路

安全和工程化上,更推荐这样接:

Renderer -> Preload Bridge -> Main -> Native Addon -> Main -> Renderer

也就是说:

  1. 不建议页面直接接触原生模块
  2. 原生模块尽量在 Main 或专门服务层加载
  3. Renderer 通过受控 API 使用能力

7.7 为什么很多原生模块在 Electron 里会踩坑

最大原因之一是:

Electron 自带的 Node 运行时和普通 Node 环境并不完全等同。

所以常见问题有:

  1. 模块 ABI 不匹配
  2. 安装后能在 Node 跑,到了 Electron 里不能跑
  3. 打包后找不到 .node 文件

7.8 electron-rebuild 为什么重要

因为很多原生模块需要针对 Electron 当前使用的 ABI 重新编译。

所以项目里经常会看到:

  1. 安装依赖
  2. 执行 electron-rebuild

这是在做:

让原生模块适配 Electron 的运行时

7.9 原生模块打包时要注意什么

重点关注:

  1. .node 文件是否被正确打进安装包
  2. 平台相关二进制是否按目标平台打包
  3. asar 对原生模块的影响
  4. 动态库依赖是否跟着带上

7.10 为什么原生模块经常需要放到 asarUnpack

因为某些原生模块或二进制不能直接从 asar 压缩包里正常加载。

所以通常会配置:

  1. asar: true
  2. asarUnpack 指定原生模块目录

7.11 什么时候更适合 child_process 调 C++ 程序

适合这些情况:

  1. 你已经有成熟独立 C++ 可执行程序
  2. 任务耗时很重,不希望阻塞 Electron 主进程
  3. 想把崩溃隔离到独立进程
  4. 想更容易替换实现

7.12 child_process 方案的优缺点

优点

  1. 和 Electron 进程解耦
  2. 崩溃隔离更好
  3. 适合重计算任务

缺点

  1. 进程管理更复杂
  2. 需要处理 stdin/stdout 或 RPC 协议
  3. 部署多个文件更麻烦

7.13 Electron 调 C++ 的三种常见场景

  1. 视频/音频编解码
  2. 本地加密解密
  3. 设备驱动或串口/硬件接入

7.14 与 C++ 集成时的最佳实践

  1. 优先把原生能力封装成小而清晰的接口
  2. 尽量不要让渲染进程直接接触原生模块
  3. 做平台隔离层
  4. 做错误码和日志体系
  5. 在 CI 中按平台构建原生产物
  6. 用预编译二进制或自动重编译方案降低安装成本

7.15 一句话压缩 Electron 调 C++

Electron 调 C++ 最推荐通过 Node-API 原生模块或独立二进制,并通过 Main/Bridge 安全地暴露给页面。


8. 自动更新与增量更新

Electron 项目能不能真正上线,自动更新是非常关键的一环。尤其桌面应用发版频繁时,没有更新机制会很痛苦。

8.1 为什么 Electron 项目需要自动更新

因为桌面应用不像纯网页那样用户刷新就完成更新。

如果没有自动更新:

  1. 用户升级成本高
  2. 版本碎片化严重
  3. 线上问题难快速修复

8.2 自动更新在做什么

本质上是:

  1. 客户端检查是否有新版本
  2. 下载新版本包
  3. 安装或提示安装
  4. 重启生效

8.3 常见更新相关角色

  1. 应用客户端
  2. 更新服务端
  3. 安装包 / 更新包元数据
  4. 版本号策略

8.4 Electron 常见更新方案

学习时通常会接触:

  1. autoUpdater
  2. electron-updater
  3. electron-builder 发布能力

其中很多项目会用:

electron-builder + electron-updater

来完成较完整的更新流程。

8.5 什么是增量更新

增量更新可以理解成:

不是每次都下载完整安装包,而是尽量只下载变化部分。

它的价值:

  1. 减少下载体积
  2. 提升更新速度
  3. 降低带宽成本

8.6 为什么增量更新常和 blockmap 这类机制一起出现

因为系统需要知道:

  1. 哪些文件变了
  2. 哪些块变了
  3. 哪些内容可以复用

所以很多更新方案会生成额外元数据,帮助客户端按差异下载。

8.7 自动更新的一条典型链路

应用启动 -> 检查版本 -> 发现新版本 -> 下载更新 -> 下载完成 -> 提示重启安装

8.8 一份常见更新代码思路

const { autoUpdater } = require("electron-updater");

function setupUpdater(win) {
  autoUpdater.on("checking-for-update", () => {
    win.webContents.send("update:status", "checking");
  });

  autoUpdater.on("update-available", (info) => {
    win.webContents.send("update:available", info);
  });

  autoUpdater.on("download-progress", (progress) => {
    win.webContents.send("update:progress", progress);
  });

  autoUpdater.on("update-downloaded", () => {
    win.webContents.send("update:downloaded");
  });

  autoUpdater.checkForUpdates();
}

8.9 更新系统要关注哪些问题

  1. 版本号策略
  2. 灰度发布
  3. 回滚机制
  4. 更新通道区分
  5. 安装包签名
  6. 平台差异

8.10 更新通道怎么理解

常见会有:

  1. dev
  2. beta
  3. stable

这样做的价值:

  1. 方便测试版本先灰度
  2. 稳定版用户不被实验功能影响

8.11 自动更新为什么和签名强相关

因为桌面安装包如果没有签名或签名链路不规范:

  1. 系统可能阻止安装
  2. 更新包校验会有问题
  3. 用户安全提示会变差

8.12 自动更新的常见坑

  1. 版本号没变导致更新不触发
  2. 元数据没上传完整
  3. 平台产物和更新配置不匹配
  4. 包签名有问题
  5. 增量包损坏或 blockmap 不对应

8.13 为什么更新逻辑最好由主进程负责

因为:

  1. 更新是全局应用能力
  2. 需要系统级安装权限和生命周期控制
  3. 不适合放在页面业务层

8.14 一句话压缩自动更新

自动更新是桌面应用的交付基础,增量更新是降低升级成本的效率优化。


9. 性能优化与安全优化

Electron 项目最常被质疑的就是“大、慢、占内存”,所以这部分很重要。

9.1 Electron 性能问题常见在哪

  1. 首屏启动慢
  2. 安装包大
  3. 内存占用高
  4. 多窗口资源消耗大
  5. 进程间通信过重
  6. 主进程被重任务阻塞

9.2 启动优化怎么做

常见方向:

  1. 减少主进程启动时同步重活
  2. 只初始化必要能力
  3. 延迟加载不常用模块
  4. 首屏页面尽量轻量
  5. 使用更快的前端构建产物

9.3 渲染性能优化

常见方式:

  1. 页面按路由或模块懒加载
  2. 降低首屏渲染复杂度
  3. 避免大列表无优化渲染
  4. 尽量减少大对象跨进程传输
  5. 减少无必要频繁 IPC

9.4 主进程性能优化

主进程最怕被阻塞。

所以:

  1. 重 CPU 任务不要直接放主进程
  2. 可以交给 Worker、子进程或 C++ 模块
  3. 长时间 IO 任务要异步处理

9.5 安装包优化

常见方式:

  1. 清理无用依赖
  2. 区分 dependenciesdevDependencies
  3. 尽量精简静态资源
  4. 原生模块按平台拆分
  5. 利用 asar 打包普通资源

9.6 内存优化

重点关注:

  1. 窗口是否及时释放
  2. IPC 监听是否清理
  3. 定时器是否泄漏
  4. 大文件缓存是否无限增长
  5. WebContents 是否有长期堆积对象

9.7 安全优化的核心原则

  1. contextIsolation: true
  2. nodeIntegration: false
  3. Preload 白名单暴露能力
  4. Renderer 不直接接触敏感 API
  5. URL 导航和外链要控制
  6. 不要执行不可信内容

9.8 Electron 安全高频点

  1. XSS 可能升级成系统能力风险
  2. 不要把任意文件读写能力开放给页面
  3. 不要把 shell、exec 之类直接暴露给 Renderer
  4. 外部页面不要随意加载

9.9 为什么 XSS 在 Electron 里更危险

因为在普通网页里,XSS 主要是页面安全问题。

但在 Electron 里,如果权限配置不当,XSS 可能进一步拿到:

  1. Node 能力
  2. 文件系统
  3. 系统命令

这会让问题级别大很多。

9.10 一句话压缩优化

Electron 优化的核心是减启动、降内存、少跨进程、重任务下放;安全的核心是最小权限和隔离。


10. Electron 项目 CI/CD 与自动化打包发布

这一部分是你新增强调的重点。真正的 Electron 项目,不只是本地能跑,还要能稳定地自动构建、签名、产出安装包、上传发布并配合自动更新。

10.1 为什么 Electron 的 CI/CD 比普通前端项目更复杂

因为 Electron 项目除了前端构建,还要处理:

  1. 多平台产物
  2. 安装包格式
  3. 原生模块编译
  4. 代码签名
  5. macOS 公证
  6. 自动更新元数据
  7. 发布渠道

所以它不是简单的:

npm run build

而是:

前端构建 + 桌面打包 + 平台处理 + 产物发布 + 更新元数据同步

10.2 Electron 项目 CI/CD 的核心目标

  1. 自动安装依赖
  2. 自动构建前端和主进程代码
  3. 自动重编译原生模块
  4. 自动打包多平台安装包
  5. 自动签名和公证
  6. 自动上传产物
  7. 自动创建 Release
  8. 自动生成自动更新所需元数据

10.3 一个标准流水线通常有哪些阶段

你可以把 Electron CI/CD 记成这条链:

Checkout -> Install -> Build Renderer/Main -> Rebuild Native Modules -> Package -> Sign -> Publish -> Release

10.4 为什么多平台构建要特别注意

因为桌面应用发布一般不是只发 Web 产物,而是:

  1. Windows 安装包
  2. macOS 安装包
  3. Linux 安装包

而且:

  1. 原生模块通常与平台强相关
  2. 签名链路与平台强相关
  3. 部分平台最好在对应系统 runner 上构建

10.5 Electron 项目最常见的 CI/CD 工具组合

常见组合:

  1. GitHub Actions + electron-builder
  2. GitLab CI + electron-builder
  3. Jenkins + 自定义构建脚本
  4. CircleCI / Azure Pipelines + 打包工具

学习阶段最推荐先掌握:

GitHub Actions + electron-builder

10.6 为什么 Electron CI/CD 常和 electron-builder 放在一起

因为它不仅能打包,还通常承担:

  1. 安装包生成
  2. 平台配置
  3. 发布配置
  4. 更新元数据生成

这让它在桌面发布链路里很关键。

10.7 CI/CD 里原生模块为什么是重点

如果项目里接了 C++ / 原生模块,就要额外考虑:

  1. runner 上是否有编译环境
  2. 是否需要执行 electron-rebuild
  3. 是否要产出预编译二进制
  4. 不同平台 ABI 是否匹配

这一点在 Electron 项目里非常常见,也非常容易出问题。

10.8 Electron 调 C++ 时,流水线要多做什么

至少要考虑:

  1. 安装编译工具链
  2. 针对 Electron 版本重编译原生模块
  3. .node 和依赖动态库打进产物
  4. 对不同平台分别构建
  5. 对构建结果做 smoke test

10.9 为什么很多 Electron 项目不建议强行跨平台打包一切

因为:

  1. Windows、macOS、Linux 的签名和产物格式不同
  2. 原生模块往往依赖本地平台工具链
  3. macOS 签名、公证需要对应环境

所以更稳的做法通常是:

Windows 用 Windows runner,macOS 用 macOS runner,Linux 用 Linux runner

10.10 自动化发布的几种触发方式

常见触发:

  1. Push 到主分支
  2. 打 tag
  3. 创建 release 分支
  4. 手动 dispatch

桌面应用最常见的是:

打 tag -> 构建 -> 生成 Release -> 上传安装包

10.11 为什么版本号策略在 CI/CD 里很重要

因为:

  1. 自动更新依赖版本比较
  2. Release 名称和文件名依赖版本号
  3. 增量更新元数据依赖版本关系

所以要统一:

  1. package.json 版本
  2. 打包版本
  3. Release 标签

10.12 CI/CD 与自动更新的关系

这一点必须串起来理解:

  1. CI/CD 构建安装包
  2. 同时产出更新元数据
  3. 上传到发布服务
  4. 客户端自动更新系统去检查并拉取

也就是说:

没有稳定的发布流水线,自动更新就很难稳定落地。

10.13 CI/CD 的典型产物有哪些

  1. Windows 安装包
  2. macOS 安装包
  3. Linux 包
  4. 更新元数据文件
  5. blockmap 或差分相关元数据
  6. 构建日志和校验信息

10.14 签名和公证为什么一定要纳入流水线

因为手工签名很难稳定、也难审计。

自动化纳入流水线的好处:

  1. 减少人工错误
  2. 保证每次发布一致
  3. 更适合团队协作
  4. 更利于合规和审计

10.15 一个推荐的流水线分层

阶段 1:代码检查

  1. lint
  2. type-check
  3. 单元测试

阶段 2:应用构建

  1. 构建 renderer
  2. 构建 main
  3. 构建 preload

阶段 3:原生模块处理

  1. 安装原生依赖
  2. electron-rebuild
  3. 校验 .node 产物

阶段 4:打包

  1. 生成平台安装包
  2. 生成更新元数据

阶段 5:签名/公证

  1. 平台签名
  2. macOS 公证

阶段 6:发布

  1. 上传 Release
  2. 上传更新源
  3. 发布 release notes

10.16 一个 GitHub Actions 示例思路

name: electron-release

on:
  push:
    tags:
      - "v*"

jobs:
  build:
    strategy:
      matrix:
        os: [windows-latest, macos-latest, ubuntu-latest]
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - run: npm ci
      - run: npm run build
      - run: npx electron-rebuild
      - run: npm run package

      - uses: softprops/action-gh-release@v2
        with:
          files: dist/**

这段只是帮助你理解流水线结构,真实项目还会继续加:

  1. 平台签名变量
  2. 公证信息
  3. 发布条件
  4. 缓存策略

10.17 CI/CD 里的环境变量一般放什么

常见有:

  1. 签名证书相关密钥
  2. 发布 token
  3. 公证账号信息
  4. 私有更新服务地址

这些都不应写进仓库,而应放在:

CI Secrets / 环境变量管理系统

10.18 为什么缓存很重要

Electron 项目依赖多、构建重、原生模块慢。

所以流水线里一般要缓存:

  1. 包管理缓存
  2. Electron 二进制缓存
  3. 原生编译缓存

这能明显减少构建时间。

10.19 为什么要把“测试”放在打包前

因为安装包构建成本更高。

如果基本测试没过就去打包:

  1. 浪费 runner 时间
  2. 浪费签名和发布资源
  3. 容易把坏版本推进发布链路

10.20 自动化打包发布的高频坑

  1. 原生模块没重编译
  2. 平台构建产物混用
  3. .node 没被打进去
  4. asar 导致原生模块无法加载
  5. 签名变量缺失
  6. 更新元数据没上传完整
  7. 本地能打包,CI 缺系统依赖

10.21 一套更完整的工程建议

  1. 打 tag 触发正式发布
  2. 主分支只做测试包
  3. 预发布走 beta 通道
  4. 正式版走 stable 通道
  5. 更新包和完整包都保留
  6. release notes 自动生成

10.22 CI/CD 和增量更新怎么一起回答

面试里推荐这样说:

我们的 CI/CD 流水线会在 tag 发布后自动执行多平台构建、原生模块重编译、安装包打包、签名和发布,同时上传自动更新所需的元数据和差分信息。客户端启动时由更新模块检查新版本并执行增量更新下载,从而实现完整的自动发布和自动升级闭环。

10.23 一句话压缩 CI/CD

Electron 的 CI/CD 本质上是在自动化完成“构建、重编译、打包、签名、发布、更新元数据同步”整条交付链。


11. 实战项目架构建议

11.1 一个真实项目建议拆成哪些模块

可以拆成:

  1. main 应用控制层
  2. preload Bridge 层
  3. renderer UI 层
  4. native 原生能力层
  5. updater 更新模块
  6. build/release 打包发布模块

11.2 一个典型桌面项目链路

例如一个 AI 桌面工具:

Renderer 页面 -> Preload 暴露 API -> Main 调文件系统/原生模块/网络 -> 返回结果 -> Renderer 展示

如果有 C++ 能力:

Renderer -> nativeBridge -> Main -> Node-API Addon / 子进程 -> Main -> Renderer

如果有自动更新:

Main 启动 -> updater 检查 -> 通知 Renderer -> 下载 -> 安装

11.3 一个更适合维护的 API 分层

Renderer 可见 API

  1. window.appBridge
  2. window.fileBridge
  3. window.nativeBridge
  4. window.updateBridge

Main 内部服务

  1. fileService
  2. nativeService
  3. updateService
  4. windowService

这样更容易测试和替换实现。

11.4 为什么建议把更新和原生模块做成独立 service

因为这两块通常:

  1. 生命周期复杂
  2. 与平台强相关
  3. 出问题影响大

独立 service 后更利于:

  1. 日志
  2. 错误治理
  3. 单独演进

11.5 一句话压缩项目架构

Electron 项目最好按 Main、Preload、Renderer、Native、Updater、Build 六层拆。


12. 高频面试题

12.1 Electron 的核心架构是什么

可以答:

Electron 主要由主进程、渲染进程和 Preload 组成。主进程负责应用生命周期和系统能力,渲染进程负责页面 UI,Preload 负责安全桥接页面与底层能力。

12.2 为什么 Electron 要有主进程和渲染进程

可以答:

因为 Electron 一方面要承载浏览器页面渲染,一方面要控制桌面应用生命周期和系统能力,所以需要把界面渲染和全局控制拆分成不同进程。

12.3 Preload 的作用是什么

可以答:

Preload 的核心作用是通过 contextBridge 等方式,把底层能力以白名单 API 的形式安全地暴露给页面。

12.4 为什么不推荐开启 nodeIntegration

可以答:

因为开启后页面脚本可能直接访问 Node.js 能力,配合 XSS 会带来更高风险。更推荐 contextIsolation 配合 preload 做最小权限暴露。

12.5 Electron 中 Bridge 和 IPC 的关系是什么

可以答:

IPC 是主进程和渲染进程的底层通信机制,Bridge 是基于 IPC 暴露给页面的安全接口层。

12.6 Electron 调用 C++ 有哪些方式

可以答:

  1. Node-API 原生模块
  2. child_process 调独立二进制
  3. FFI 调动态库

其中最主流的是 Node-API。

12.7 为什么 Electron 原生模块常常需要重编译

可以答:

因为 Electron 的运行时和普通 Node 环境在 ABI 上可能不同,很多原生模块需要针对 Electron 当前版本重新构建,所以常会配合 electron-rebuild。

12.8 为什么不建议让 Renderer 直接调用原生模块

可以答:

因为原生模块通常权限更高、稳定性影响更大,放在 Main 或独立服务层更容易控制权限、隔离风险并统一日志。

12.9 什么是增量更新

可以答:

增量更新是指客户端更新时尽量只下载变化的部分,而不是每次都下载完整安装包,从而降低更新体积和带宽成本。

12.10 Electron 自动更新一般怎么做

可以答:

通常会在主进程中结合 electron-updater 或 autoUpdater 检查版本、下载更新并通知页面展示状态,发布侧再通过构建流水线上传安装包和更新元数据。

12.11 Electron 项目为什么 CI/CD 更复杂

可以答:

因为它除了前端构建,还涉及多平台安装包、原生模块编译、签名、公证、自动更新元数据上传等问题,所以发布链路比普通前端项目更重。

12.12 Electron 项目里的 CI/CD 主要做什么

可以答:

  1. 安装依赖
  2. 构建 renderer/main/preload
  3. 重编译原生模块
  4. 打包安装包
  5. 签名和公证
  6. 上传发布产物
  7. 同步自动更新元数据

12.13 Electron 性能优化一般从哪看

可以答:

主要看启动速度、内存占用、安装包体积、跨进程通信频率和主进程是否被重任务阻塞。

12.14 如果让你设计一个 Electron 项目架构,你会怎么做

推荐答法:

  1. 按 Main、Preload、Renderer 分层
  2. 所有系统能力通过 Bridge 暴露
  3. 原生模块收敛在 Main/Service 层
  4. 自动更新放主进程
  5. 打包和发布通过 CI/CD 自动化

13. 更好记的学习方法

13.1 用一句话记住 Electron

Electron = 浏览器界面 + Node 系统能力 + 桌面应用容器。

13.2 用“谁负责什么”来记

  • Main:管应用和系统
  • Renderer:管界面
  • Preload:管授权桥
  • Native Addon:管原生高性能能力
  • Updater:管升级
  • CI/CD:管交付

13.3 用对比法记

对比 1:Main vs Renderer

  • Main:全局控制
  • Renderer:页面交互

对比 2:Bridge vs IPC

  • IPC:底层通信
  • Bridge:面向页面的安全 API

对比 3:Node-API vs child_process

  • Node-API:深度集成
  • child_process:进程隔离强

对比 4:完整更新 vs 增量更新

  • 完整更新:整包下载
  • 增量更新:按差异下载

13.4 用口诀记

  1. 界面在 Renderer,系统在 Main,安全靠 Preload
  2. IPC 是通信总线,Bridge 是页面接口
  3. 原生能力走 Main,C++ 不直接给页面
  4. 自动更新靠发布链,CI/CD 把交付串起来

13.5 最有效的记忆方式

你最好能做到:

  1. 画出 Electron 三层架构图
  2. 手写一个 contextBridge + ipcMain.handle demo
  3. 说清一个 C++ 接入链路
  4. 说清一条自动更新 + CI/CD 流水线

如果这四件事能完成,说明你已经不是“知道概念”,而是“能讲项目”。


14. 8 小时学习节奏建议

第 1 小时:建立总框架

目标:

  1. 理解 Electron 在解决什么问题
  2. 记住 Main / Renderer / Preload 三层模型
  3. 明白 Bridge、C++、更新、CI/CD 为什么会一起出现

第 2-3 小时:集中突破进程模型与 Bridge

重点:

  1. BrowserWindow
  2. contextIsolation
  3. preload
  4. contextBridge
  5. IPC

输出目标:

能讲清 Renderer 如何安全调用系统能力

第 4 小时:集中突破 Electron 调 C++

重点:

  1. Node-API
  2. electron-rebuild
  3. asarUnpack
  4. child_process 方案
  5. 渲染层与原生层的安全隔离

输出目标:

能讲清 Electron 调 C++ 的推荐链路

第 5 小时:集中突破自动更新与增量更新

重点:

  1. 自动更新链路
  2. 更新元数据
  3. 增量更新思路
  4. 发布通道

输出目标:

能讲清桌面应用怎么从“有新包”到“自动升级”

第 6 小时:集中突破 CI/CD

重点:

  1. 多平台构建
  2. 原生模块重编译
  3. 签名与公证
  4. 自动发布
  5. 与更新系统联动

输出目标:

能讲一条完整的 Electron 自动打包发布流水线

第 7 小时:优化与安全

重点:

  1. 启动优化
  2. 内存优化
  3. 安装包优化
  4. 安全边界
  5. XSS 风险

第 8 小时:冲刺复盘

做三件事:

  1. 默写一页总结
  2. 回答面试题
  3. 用 5 分钟讲一个完整 Electron 项目架构

15. 一页速记总结

15.1 Electron 核心

Chromium 负责界面,Node.js 负责系统能力,Electron 负责把它们组织成桌面应用。

15.2 三层架构

  • Main:应用控制中心
  • Renderer:页面和交互
  • Preload:安全桥接层

15.3 通信核心

  • IPC:进程通信
  • Bridge:页面安全 API

15.4 C++ 接入核心

  • 首选 Node-API 原生模块
  • 重任务可走 child_process
  • 原生能力尽量收敛在 Main

15.5 更新核心

  • 自动更新:检查、下载、安装
  • 增量更新:只下载变化部分

15.6 CI/CD 核心

  • 自动构建
  • 原生模块重编译
  • 多平台打包
  • 签名与公证
  • 自动发布
  • 同步更新元数据

15.7 优化核心

  • 减启动
  • 降内存
  • 少跨进程
  • 重任务下放
  • 最小权限

15.8 最重要的一句话

Electron 真正难的不是把页面跑起来,而是把系统能力、安全边界、原生集成、更新发布和 CI/CD 整成一条稳定交付链。


16. 背诵口诀

16.1 总口诀

Main 管系统,Renderer 管页面,Preload 管桥,CI/CD 管交付。

16.2 通信口诀

IPC 传消息,Bridge 露接口,页面不碰底层。

16.3 原生口诀

C++ 走 Node-API,重编译看 Electron ABI,打包别忘 asarUnpack。

16.4 更新口诀

先发布,再检查;先下载,再安装;差分下载更省量。

16.5 发布口诀

先构建,再重编译;再打包,再签名;再发布,再更新。

16.6 面试口诀

回答 Electron 项目题时,尽量按这个顺序说:

  1. 架构分层
  2. Bridge 与 IPC
  3. 原生能力接入
  4. 自动更新
  5. CI/CD
  6. 性能与安全

只要你按这个顺序说,答案通常会显得很完整。


附:你可以怎么用这份笔记

第一次学习

重点看:

  1. Electron 到底在解决什么
  2. Electron 核心架构
  3. 主进程、渲染进程、Preload
  4. Bridge 与 IPC

第二次复习

重点看:

  1. Electron 调 C++
  2. 自动更新与增量更新
  3. 性能优化与安全优化
  4. CI/CD 自动化打包发布

第三次冲刺

只看:

  1. 一页速记总结
  2. 背诵口诀
  3. 高频面试题

如果你能把这三部分不看笔记讲出来,说明你已经真正掌握了 Electron 这套知识。