Electron 桌面应用开发学习笔记
适合目标:8 小时内建立 Electron 桌面应用完整知识框架,覆盖 Electron 核心架构、Bridge 交互、IPC、调用 C++、自动更新、性能优化、CI/CD 自动化打包构建发布与高频面试题。
学习重点:主进程/渲染进程/Preload 的职责边界,安全通信模型,原生能力接入,桌面端发布链路,以及真实项目中的性能和工程化方案。
学习原则:先理解进程模型,再记 API;先掌握安全边界,再写功能;先会搭架构,再做复杂集成。
目录
- 学习总览
- Electron 到底在解决什么
- Electron 核心架构
- 主进程、渲染进程、Preload
- Electron 中的 Bridge 交互
- IPC 通信与工程化封装
- Electron 调用 C++
- 自动更新与增量更新
- 性能优化与安全优化
- Electron 项目 CI/CD 与自动化打包发布
- 实战项目架构建议
- 高频面试题
- 更好记的学习方法
- 8 小时学习节奏建议
- 一页速记总结
- 背诵口诀
1. 学习总览
1.1 Electron 是在学什么
很多人第一次接触 Electron,会把它理解成:
用前端技术做桌面应用。
这句话没有错,但还不够完整。
更准确地说,Electron 学的是:
如何让 Web 技术跑在桌面容器里,并安全地接入操作系统能力。
所以 Electron 的关键不只是“会写页面”,而是下面这些问题:
- 页面和系统能力怎么连接
- 主进程和渲染进程怎么分工
- 前端代码如何安全访问 Node 和系统 API
- 如何调用本地原生模块或 C++
- 如何构建安装包、自动更新和发布
- 如何处理桌面应用的性能、内存和稳定性
1.2 你可以把 Electron 看成什么
把 Electron 压缩成一句话:
Chromium 负责界面,Node.js 负责系统能力,Electron 负责把两者组织起来。
所以 Electron 本质上是一个桌面应用运行时,它把:
- 浏览器渲染能力
- Node.js 系统能力
- 桌面应用容器能力
组合到了一起。
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 最大的吸引力是:
- 前端团队可用熟悉的技术栈做桌面应用
- React / Vue / Vite / Webpack 等生态可以复用
- 同时还能访问文件系统、托盘、菜单、通知等桌面能力
- 跨平台能力较强
2.2 Electron 适合哪些项目
常见场景:
- 编辑器类应用
- IM / 客户端工具
- 数据分析工具
- 开发者工具
- 企业内部桌面工作台
- 音视频辅助工具
- AI 桌面助手
2.3 Electron 的优势
- 前端开发成本低
- 跨平台开发效率高
- Node.js 能力丰富
- 社区成熟,周边工具多
2.4 Electron 的代价
- 安装包通常偏大
- 内存占用通常高于原生应用
- 进程模型更复杂
- 安全边界要自己管好
- 原生模块和签名发布链路更复杂
2.5 面试里怎么概括 Electron
可以答:
Electron 是一个基于 Chromium 和 Node.js 的桌面应用运行时,允许开发者用 Web 技术构建跨平台桌面应用。它的核心在于主进程、渲染进程和 Preload 的分工,以及通过 IPC 和 Bridge 安全地连接页面与系统能力。
3. Electron 核心架构
3.1 Electron 的核心组成
可以先记住这三块:
Main ProcessRenderer ProcessPreload
3.2 Main Process 是什么
主进程可以理解成:
Electron 应用的后台控制中心。
它通常负责:
- 创建窗口
- 管理应用生命周期
- 控制菜单、托盘、通知
- 调系统 API
- 管理 IPC
- 调原生模块
- 做自动更新
3.3 Renderer Process 是什么
渲染进程本质上就是:
一个跑页面的浏览器环境。
它负责:
- 页面 UI
- 用户交互
- 业务状态展示
- 调用通过 Bridge 暴露出来的能力
3.4 Preload 是什么
Preload 可以理解成:
运行在渲染进程和页面之间的一层安全中介。
它常做的事情:
- 注入安全 API
- 收敛 IPC 能力
- 屏蔽主进程和 Node 细节
- 为页面提供白名单化接口
3.5 一个最重要的架构认识
在现代 Electron 项目里,推荐是:
- 页面不直接访问 Node
- 页面不直接拿
ipcRenderer - 页面通过
contextBridge暴露出来的 API 工作
也就是说:
Renderer 不应该直接拥有全部系统能力。
3.6 Electron 的典型调用链
最常见链路:
Renderer UI -> Preload Bridge -> ipcRenderer -> Main -> 系统能力 / 原生模块 -> 返回结果 -> Renderer 更新 UI
3.7 为什么 Electron 要分这么多层
因为它本质上是在解决两个矛盾:
- 前端想用系统能力
- 但系统能力不能直接无边界暴露给页面
所以必须分层:
- 页面层只关心业务
- Bridge 层负责授权和收口
- 主进程负责真正执行系统动作
3.8 一句话压缩架构
主进程管应用和系统,渲染进程管界面,Preload 管安全桥接。
4. 主进程、渲染进程、Preload
这一部分是 Electron 的绝对重点,很多面试题和工程设计题都绕不开这里。
4.1 主进程最常做的事情
常见职责:
app.whenReady- 创建
BrowserWindow - 监听应用生命周期
- 注册菜单和托盘
- 注册 IPC
- 访问文件系统
- 启动自动更新
示例:
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 渲染进程最常做的事情
渲染进程更像普通前端项目:
- 页面渲染
- 状态管理
- 组件交互
- 调用
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 上下文隔离开。
这样做的好处:
- 降低页面被注入后直接拿到 Node 能力的风险
- 更适合通过
contextBridge明确暴露能力 - 是现代 Electron 项目的推荐安全基线
4.5 为什么一般不推荐 nodeIntegration: true
因为一旦打开,页面脚本就可能直接访问 Node 能力:
fschild_process- 本地文件
- 系统命令
这对安全来说风险很大。
所以更推荐:
nodeIntegration: false + preload + contextBridge
4.6 主进程和渲染进程怎么分工才合理
推荐思路:
放在主进程的能力
- 文件系统
- 系统托盘
- 自动更新
- 原生模块
- 菜单与快捷键
- 子进程管理
放在渲染进程的能力
- UI 渲染
- 状态管理
- 视图逻辑
- 表单交互
放在 Preload 的能力
- 暴露安全 API
- 收敛 IPC
- 参数规整
- 错误格式统一
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 项目里,一般不应该让页面直接拿到:
- Node 能力
ipcRenderer- 原生模块对象
- 任意系统命令能力
所以需要一层 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 直接暴露给页面
因为这样页面就能自己拼各种消息:
- 权限边界不清晰
- 调用方式不可控
- 不利于统一日志和参数校验
- 容易被滥用
更好的方式是:
只暴露经过封装的业务 API
5.5 Bridge 设计的推荐原则
- 白名单化
- 参数结构稳定
- 返回结构统一
- 不暴露底层原始对象
- 能区分同步能力和异步能力
- 能做权限和版本控制
5.6 Bridge API 怎么设计更好记
推荐按领域分:
appBridgefileBridgesystemBridgenativeBridgeupdateBridge
这样比一个超大 window.api 更清晰。
5.7 Electron Bridge 和移动端 Bridge 的区别
共同点:
- 都是在“页面层”和“系统能力”之间搭桥
- 都要考虑协议、安全、回调
不同点:
- Electron 更常通过
contextBridge + IPC - Hybrid App 更常是 WebView 与 Native 容器通信
5.8 一句话压缩 Electron Bridge
Electron Bridge 就是把系统能力包装成页面可安全调用的 API。
6. IPC 通信与工程化封装
6.1 什么是 IPC
IPC 是 Inter-Process Communication,即进程间通信。
在 Electron 里,常见的是:
ipcRendereripcMain
用来完成:
渲染进程 <-> 主进程
之间的消息传递。
6.2 为什么 Electron 必须要 IPC
因为:
- UI 在渲染进程
- 很多系统能力在主进程
所以必须有通信机制把二者连起来。
6.3 两种最常见通信方式
invoke / handle
适合:
- 请求-响应型调用
- Promise 风格
- 获取结果型能力
send / on
适合:
- 单向通知
- 事件推送
- 状态广播
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,什么时候用事件
一个简单记法:
要结果:用invoke要通知:用事件
6.7 为什么要统一 channel 命名
推荐这样命名:
domain:action
例如:
app:getVersionfile:readTextnative:hashupdate:check
这样更清晰,也方便排查日志。
6.8 为什么 IPC 要做二次封装
因为直接在页面里写:
ipcRenderer.invoke("file:readText", path)
有这些问题:
- 业务层和底层通信耦合
- 参数不统一
- 日志和错误不集中
- 不利于未来重构
所以推荐:
页面 -> Bridge API -> IPC -> Main
6.9 IPC 的常见坑
- 事件监听不清理导致内存泄漏
- 同一个事件多次注册
- 传输大对象导致性能问题
- 渲染进程直接调用高危能力
- Main 中逻辑过重导致卡顿
6.10 一句话压缩 IPC
IPC 是 Electron 进程之间的通信总线,Bridge 是基于 IPC 暴露给页面的安全接口层。
7. Electron 调用 C++
这是你特别点名的重点。Electron 调用 C++,本质上是在“前端桌面壳”之外,接入真正的原生计算能力或已有本地 SDK。
7.1 为什么 Electron 项目还要调 C++
常见原因:
- 需要高性能计算
- 需要调用已有 C/C++ SDK
- 需要系统底层能力
- 需要加解密、编解码、图像处理、硬件控制
- 需要复用已有桌面端或客户端历史能力
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 使用,因为写法更现代、可读性更好。
学习时你要先理解本质:
- JS 调模块函数
- 模块内部执行 C++
- 结果再返回给 JS
7.6 Electron 调 C++ 的推荐链路
安全和工程化上,更推荐这样接:
Renderer -> Preload Bridge -> Main -> Native Addon -> Main -> Renderer
也就是说:
- 不建议页面直接接触原生模块
- 原生模块尽量在 Main 或专门服务层加载
- Renderer 通过受控 API 使用能力
7.7 为什么很多原生模块在 Electron 里会踩坑
最大原因之一是:
Electron 自带的 Node 运行时和普通 Node 环境并不完全等同。
所以常见问题有:
- 模块 ABI 不匹配
- 安装后能在 Node 跑,到了 Electron 里不能跑
- 打包后找不到
.node文件
7.8 electron-rebuild 为什么重要
因为很多原生模块需要针对 Electron 当前使用的 ABI 重新编译。
所以项目里经常会看到:
- 安装依赖
- 执行
electron-rebuild
这是在做:
让原生模块适配 Electron 的运行时
7.9 原生模块打包时要注意什么
重点关注:
.node文件是否被正确打进安装包- 平台相关二进制是否按目标平台打包
asar对原生模块的影响- 动态库依赖是否跟着带上
7.10 为什么原生模块经常需要放到 asarUnpack
因为某些原生模块或二进制不能直接从 asar 压缩包里正常加载。
所以通常会配置:
asar: trueasarUnpack指定原生模块目录
7.11 什么时候更适合 child_process 调 C++ 程序
适合这些情况:
- 你已经有成熟独立 C++ 可执行程序
- 任务耗时很重,不希望阻塞 Electron 主进程
- 想把崩溃隔离到独立进程
- 想更容易替换实现
7.12 child_process 方案的优缺点
优点
- 和 Electron 进程解耦
- 崩溃隔离更好
- 适合重计算任务
缺点
- 进程管理更复杂
- 需要处理 stdin/stdout 或 RPC 协议
- 部署多个文件更麻烦
7.13 Electron 调 C++ 的三种常见场景
- 视频/音频编解码
- 本地加密解密
- 设备驱动或串口/硬件接入
7.14 与 C++ 集成时的最佳实践
- 优先把原生能力封装成小而清晰的接口
- 尽量不要让渲染进程直接接触原生模块
- 做平台隔离层
- 做错误码和日志体系
- 在 CI 中按平台构建原生产物
- 用预编译二进制或自动重编译方案降低安装成本
7.15 一句话压缩 Electron 调 C++
Electron 调 C++ 最推荐通过 Node-API 原生模块或独立二进制,并通过 Main/Bridge 安全地暴露给页面。
8. 自动更新与增量更新
Electron 项目能不能真正上线,自动更新是非常关键的一环。尤其桌面应用发版频繁时,没有更新机制会很痛苦。
8.1 为什么 Electron 项目需要自动更新
因为桌面应用不像纯网页那样用户刷新就完成更新。
如果没有自动更新:
- 用户升级成本高
- 版本碎片化严重
- 线上问题难快速修复
8.2 自动更新在做什么
本质上是:
- 客户端检查是否有新版本
- 下载新版本包
- 安装或提示安装
- 重启生效
8.3 常见更新相关角色
- 应用客户端
- 更新服务端
- 安装包 / 更新包元数据
- 版本号策略
8.4 Electron 常见更新方案
学习时通常会接触:
autoUpdaterelectron-updaterelectron-builder发布能力
其中很多项目会用:
electron-builder + electron-updater
来完成较完整的更新流程。
8.5 什么是增量更新
增量更新可以理解成:
不是每次都下载完整安装包,而是尽量只下载变化部分。
它的价值:
- 减少下载体积
- 提升更新速度
- 降低带宽成本
8.6 为什么增量更新常和 blockmap 这类机制一起出现
因为系统需要知道:
- 哪些文件变了
- 哪些块变了
- 哪些内容可以复用
所以很多更新方案会生成额外元数据,帮助客户端按差异下载。
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 更新系统要关注哪些问题
- 版本号策略
- 灰度发布
- 回滚机制
- 更新通道区分
- 安装包签名
- 平台差异
8.10 更新通道怎么理解
常见会有:
devbetastable
这样做的价值:
- 方便测试版本先灰度
- 稳定版用户不被实验功能影响
8.11 自动更新为什么和签名强相关
因为桌面安装包如果没有签名或签名链路不规范:
- 系统可能阻止安装
- 更新包校验会有问题
- 用户安全提示会变差
8.12 自动更新的常见坑
- 版本号没变导致更新不触发
- 元数据没上传完整
- 平台产物和更新配置不匹配
- 包签名有问题
- 增量包损坏或 blockmap 不对应
8.13 为什么更新逻辑最好由主进程负责
因为:
- 更新是全局应用能力
- 需要系统级安装权限和生命周期控制
- 不适合放在页面业务层
8.14 一句话压缩自动更新
自动更新是桌面应用的交付基础,增量更新是降低升级成本的效率优化。
9. 性能优化与安全优化
Electron 项目最常被质疑的就是“大、慢、占内存”,所以这部分很重要。
9.1 Electron 性能问题常见在哪
- 首屏启动慢
- 安装包大
- 内存占用高
- 多窗口资源消耗大
- 进程间通信过重
- 主进程被重任务阻塞
9.2 启动优化怎么做
常见方向:
- 减少主进程启动时同步重活
- 只初始化必要能力
- 延迟加载不常用模块
- 首屏页面尽量轻量
- 使用更快的前端构建产物
9.3 渲染性能优化
常见方式:
- 页面按路由或模块懒加载
- 降低首屏渲染复杂度
- 避免大列表无优化渲染
- 尽量减少大对象跨进程传输
- 减少无必要频繁 IPC
9.4 主进程性能优化
主进程最怕被阻塞。
所以:
- 重 CPU 任务不要直接放主进程
- 可以交给 Worker、子进程或 C++ 模块
- 长时间 IO 任务要异步处理
9.5 安装包优化
常见方式:
- 清理无用依赖
- 区分
dependencies和devDependencies - 尽量精简静态资源
- 原生模块按平台拆分
- 利用
asar打包普通资源
9.6 内存优化
重点关注:
- 窗口是否及时释放
- IPC 监听是否清理
- 定时器是否泄漏
- 大文件缓存是否无限增长
- WebContents 是否有长期堆积对象
9.7 安全优化的核心原则
contextIsolation: truenodeIntegration: false- Preload 白名单暴露能力
- Renderer 不直接接触敏感 API
- URL 导航和外链要控制
- 不要执行不可信内容
9.8 Electron 安全高频点
- XSS 可能升级成系统能力风险
- 不要把任意文件读写能力开放给页面
- 不要把 shell、exec 之类直接暴露给 Renderer
- 外部页面不要随意加载
9.9 为什么 XSS 在 Electron 里更危险
因为在普通网页里,XSS 主要是页面安全问题。
但在 Electron 里,如果权限配置不当,XSS 可能进一步拿到:
- Node 能力
- 文件系统
- 系统命令
这会让问题级别大很多。
9.10 一句话压缩优化
Electron 优化的核心是减启动、降内存、少跨进程、重任务下放;安全的核心是最小权限和隔离。
10. Electron 项目 CI/CD 与自动化打包发布
这一部分是你新增强调的重点。真正的 Electron 项目,不只是本地能跑,还要能稳定地自动构建、签名、产出安装包、上传发布并配合自动更新。
10.1 为什么 Electron 的 CI/CD 比普通前端项目更复杂
因为 Electron 项目除了前端构建,还要处理:
- 多平台产物
- 安装包格式
- 原生模块编译
- 代码签名
- macOS 公证
- 自动更新元数据
- 发布渠道
所以它不是简单的:
npm run build
而是:
前端构建 + 桌面打包 + 平台处理 + 产物发布 + 更新元数据同步
10.2 Electron 项目 CI/CD 的核心目标
- 自动安装依赖
- 自动构建前端和主进程代码
- 自动重编译原生模块
- 自动打包多平台安装包
- 自动签名和公证
- 自动上传产物
- 自动创建 Release
- 自动生成自动更新所需元数据
10.3 一个标准流水线通常有哪些阶段
你可以把 Electron CI/CD 记成这条链:
Checkout -> Install -> Build Renderer/Main -> Rebuild Native Modules -> Package -> Sign -> Publish -> Release
10.4 为什么多平台构建要特别注意
因为桌面应用发布一般不是只发 Web 产物,而是:
- Windows 安装包
- macOS 安装包
- Linux 安装包
而且:
- 原生模块通常与平台强相关
- 签名链路与平台强相关
- 部分平台最好在对应系统 runner 上构建
10.5 Electron 项目最常见的 CI/CD 工具组合
常见组合:
- GitHub Actions + electron-builder
- GitLab CI + electron-builder
- Jenkins + 自定义构建脚本
- CircleCI / Azure Pipelines + 打包工具
学习阶段最推荐先掌握:
GitHub Actions + electron-builder
10.6 为什么 Electron CI/CD 常和 electron-builder 放在一起
因为它不仅能打包,还通常承担:
- 安装包生成
- 平台配置
- 发布配置
- 更新元数据生成
这让它在桌面发布链路里很关键。
10.7 CI/CD 里原生模块为什么是重点
如果项目里接了 C++ / 原生模块,就要额外考虑:
- runner 上是否有编译环境
- 是否需要执行
electron-rebuild - 是否要产出预编译二进制
- 不同平台 ABI 是否匹配
这一点在 Electron 项目里非常常见,也非常容易出问题。
10.8 Electron 调 C++ 时,流水线要多做什么
至少要考虑:
- 安装编译工具链
- 针对 Electron 版本重编译原生模块
- 把
.node和依赖动态库打进产物 - 对不同平台分别构建
- 对构建结果做 smoke test
10.9 为什么很多 Electron 项目不建议强行跨平台打包一切
因为:
- Windows、macOS、Linux 的签名和产物格式不同
- 原生模块往往依赖本地平台工具链
- macOS 签名、公证需要对应环境
所以更稳的做法通常是:
Windows 用 Windows runner,macOS 用 macOS runner,Linux 用 Linux runner
10.10 自动化发布的几种触发方式
常见触发:
- Push 到主分支
- 打 tag
- 创建 release 分支
- 手动 dispatch
桌面应用最常见的是:
打 tag -> 构建 -> 生成 Release -> 上传安装包
10.11 为什么版本号策略在 CI/CD 里很重要
因为:
- 自动更新依赖版本比较
- Release 名称和文件名依赖版本号
- 增量更新元数据依赖版本关系
所以要统一:
package.json版本- 打包版本
- Release 标签
10.12 CI/CD 与自动更新的关系
这一点必须串起来理解:
- CI/CD 构建安装包
- 同时产出更新元数据
- 上传到发布服务
- 客户端自动更新系统去检查并拉取
也就是说:
没有稳定的发布流水线,自动更新就很难稳定落地。
10.13 CI/CD 的典型产物有哪些
- Windows 安装包
- macOS 安装包
- Linux 包
- 更新元数据文件
- blockmap 或差分相关元数据
- 构建日志和校验信息
10.14 签名和公证为什么一定要纳入流水线
因为手工签名很难稳定、也难审计。
自动化纳入流水线的好处:
- 减少人工错误
- 保证每次发布一致
- 更适合团队协作
- 更利于合规和审计
10.15 一个推荐的流水线分层
阶段 1:代码检查
- lint
- type-check
- 单元测试
阶段 2:应用构建
- 构建 renderer
- 构建 main
- 构建 preload
阶段 3:原生模块处理
- 安装原生依赖
electron-rebuild- 校验
.node产物
阶段 4:打包
- 生成平台安装包
- 生成更新元数据
阶段 5:签名/公证
- 平台签名
- macOS 公证
阶段 6:发布
- 上传 Release
- 上传更新源
- 发布 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/**
这段只是帮助你理解流水线结构,真实项目还会继续加:
- 平台签名变量
- 公证信息
- 发布条件
- 缓存策略
10.17 CI/CD 里的环境变量一般放什么
常见有:
- 签名证书相关密钥
- 发布 token
- 公证账号信息
- 私有更新服务地址
这些都不应写进仓库,而应放在:
CI Secrets / 环境变量管理系统
10.18 为什么缓存很重要
Electron 项目依赖多、构建重、原生模块慢。
所以流水线里一般要缓存:
- 包管理缓存
- Electron 二进制缓存
- 原生编译缓存
这能明显减少构建时间。
10.19 为什么要把“测试”放在打包前
因为安装包构建成本更高。
如果基本测试没过就去打包:
- 浪费 runner 时间
- 浪费签名和发布资源
- 容易把坏版本推进发布链路
10.20 自动化打包发布的高频坑
- 原生模块没重编译
- 平台构建产物混用
.node没被打进去asar导致原生模块无法加载- 签名变量缺失
- 更新元数据没上传完整
- 本地能打包,CI 缺系统依赖
10.21 一套更完整的工程建议
- 打 tag 触发正式发布
- 主分支只做测试包
- 预发布走 beta 通道
- 正式版走 stable 通道
- 更新包和完整包都保留
- release notes 自动生成
10.22 CI/CD 和增量更新怎么一起回答
面试里推荐这样说:
我们的 CI/CD 流水线会在 tag 发布后自动执行多平台构建、原生模块重编译、安装包打包、签名和发布,同时上传自动更新所需的元数据和差分信息。客户端启动时由更新模块检查新版本并执行增量更新下载,从而实现完整的自动发布和自动升级闭环。
10.23 一句话压缩 CI/CD
Electron 的 CI/CD 本质上是在自动化完成“构建、重编译、打包、签名、发布、更新元数据同步”整条交付链。
11. 实战项目架构建议
11.1 一个真实项目建议拆成哪些模块
可以拆成:
main应用控制层preloadBridge 层rendererUI 层native原生能力层updater更新模块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
window.appBridgewindow.fileBridgewindow.nativeBridgewindow.updateBridge
Main 内部服务
fileServicenativeServiceupdateServicewindowService
这样更容易测试和替换实现。
11.4 为什么建议把更新和原生模块做成独立 service
因为这两块通常:
- 生命周期复杂
- 与平台强相关
- 出问题影响大
独立 service 后更利于:
- 日志
- 错误治理
- 单独演进
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++ 有哪些方式
可以答:
- Node-API 原生模块
child_process调独立二进制- 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 主要做什么
可以答:
- 安装依赖
- 构建 renderer/main/preload
- 重编译原生模块
- 打包安装包
- 签名和公证
- 上传发布产物
- 同步自动更新元数据
12.13 Electron 性能优化一般从哪看
可以答:
主要看启动速度、内存占用、安装包体积、跨进程通信频率和主进程是否被重任务阻塞。
12.14 如果让你设计一个 Electron 项目架构,你会怎么做
推荐答法:
- 按 Main、Preload、Renderer 分层
- 所有系统能力通过 Bridge 暴露
- 原生模块收敛在 Main/Service 层
- 自动更新放主进程
- 打包和发布通过 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 用口诀记
界面在 Renderer,系统在 Main,安全靠 PreloadIPC 是通信总线,Bridge 是页面接口原生能力走 Main,C++ 不直接给页面自动更新靠发布链,CI/CD 把交付串起来
13.5 最有效的记忆方式
你最好能做到:
- 画出 Electron 三层架构图
- 手写一个
contextBridge + ipcMain.handledemo - 说清一个 C++ 接入链路
- 说清一条自动更新 + CI/CD 流水线
如果这四件事能完成,说明你已经不是“知道概念”,而是“能讲项目”。
14. 8 小时学习节奏建议
第 1 小时:建立总框架
目标:
- 理解 Electron 在解决什么问题
- 记住 Main / Renderer / Preload 三层模型
- 明白 Bridge、C++、更新、CI/CD 为什么会一起出现
第 2-3 小时:集中突破进程模型与 Bridge
重点:
- BrowserWindow
- contextIsolation
- preload
- contextBridge
- IPC
输出目标:
能讲清 Renderer 如何安全调用系统能力
第 4 小时:集中突破 Electron 调 C++
重点:
- Node-API
- electron-rebuild
- asarUnpack
- child_process 方案
- 渲染层与原生层的安全隔离
输出目标:
能讲清 Electron 调 C++ 的推荐链路
第 5 小时:集中突破自动更新与增量更新
重点:
- 自动更新链路
- 更新元数据
- 增量更新思路
- 发布通道
输出目标:
能讲清桌面应用怎么从“有新包”到“自动升级”
第 6 小时:集中突破 CI/CD
重点:
- 多平台构建
- 原生模块重编译
- 签名与公证
- 自动发布
- 与更新系统联动
输出目标:
能讲一条完整的 Electron 自动打包发布流水线
第 7 小时:优化与安全
重点:
- 启动优化
- 内存优化
- 安装包优化
- 安全边界
- XSS 风险
第 8 小时:冲刺复盘
做三件事:
- 默写一页总结
- 回答面试题
- 用 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 项目题时,尽量按这个顺序说:
- 架构分层
- Bridge 与 IPC
- 原生能力接入
- 自动更新
- CI/CD
- 性能与安全
只要你按这个顺序说,答案通常会显得很完整。
附:你可以怎么用这份笔记
第一次学习
重点看:
- Electron 到底在解决什么
- Electron 核心架构
- 主进程、渲染进程、Preload
- Bridge 与 IPC
第二次复习
重点看:
- Electron 调 C++
- 自动更新与增量更新
- 性能优化与安全优化
- CI/CD 自动化打包发布
第三次冲刺
只看:
- 一页速记总结
- 背诵口诀
- 高频面试题
如果你能把这三部分不看笔记讲出来,说明你已经真正掌握了 Electron 这套知识。