Electron

第一章:热更新概述

第一章:热更新概述

术语说明:本章中的“热更新”主要指 应用更新(OTA) 与增量更新策略, 不等同于前端开发期的 HMR(Hot Module Replacement)。

目录


为什么桌面应用需要热更新

Web 应用 vs 桌面应用的更新差异

Web 应用更新:

  开发者部署到服务器 → 用户刷新页面 → 立即获得新版本
  
  延迟: 0(用户无感知)
  用户参与: 无
  回滚: 服务端回滚,用户无感知

桌面应用"传统"更新:

  开发者发布新版本


  用户收到通知 → 下载完整安装包 (100-200MB)


  关闭应用 → 运行安装程序 → 重启应用
  
  延迟: 数分钟到数小时
  用户参与: 高(需要主动操作)
  用户可能: 忽略更新 → 使用旧版本 → 遇到已修复的 bug

SaaS 时代的需求

现代桌面应用(如 VS Code、Slack、Notion)需要接近 Web 应用的更新速度:

SaaS 快速迭代需求:

  ┌──────────────────────────────────────────────────┐
  │                                                   │
  │  需求1: 快速修复                                  │
  │  发现严重 bug → 修复 → 用户几小时内获得修复      │
  │                                                   │
  │  需求2: 灰度发布                                  │
  │  新功能先推给 10% 用户测试 → 确认无误 → 全量推送  │
  │                                                   │
  │  需求3: 强制更新                                  │
  │  安全漏洞修复必须推到所有用户                     │
  │                                                   │
  │  需求4: 最小打扰                                  │
  │  后台下载 → 下次启动时自动更新                    │
  │  或甚至不需要重启                                 │
  │                                                   │
  │  需求5: 可回滚                                    │
  │  新版本有问题时能快速回退                         │
  │                                                   │
  └──────────────────────────────────────────────────┘

更新策略全景图

三大类更新策略

更新策略分类:

  策略 1: 全量替换
  ┌──────────────────────────────────────────────┐
  │  下载完整的新版本安装包,替换整个应用          │
  │                                               │
  │  更新内容: Electron 运行时 + 应用代码 + 依赖  │
  │  下载量: 80-200MB                             │
  │  需要重启: ✅ 是                              │
  │  实现复杂度: ★☆☆☆☆                           │
  └──────────────────────────────────────────────┘

       │ 优化

  策略 2: 增量更新 (差分更新)
  ┌──────────────────────────────────────────────┐
  │  只下载新旧版本之间的差异部分                  │
  │                                               │
  │  更新内容: 变更的文件或二进制 diff             │
  │  下载量: 5-30MB                               │
  │  需要重启: ✅ 是                              │
  │  实现复杂度: ★★★☆☆                           │
  └──────────────────────────────────────────────┘

       │ 进一步优化

  策略 3: 热补丁 (Hot Patch)
  ┌──────────────────────────────────────────────┐
  │  只替换前端代码(HTML/CSS/JS),不动原生部分   │
  │                                               │
  │  更新内容: 渲染层代码                         │
  │  下载量: 0.1-5MB                              │
  │  需要重启: ⚠️ 可能不需要                      │
  │  实现复杂度: ★★★★☆                           │
  └──────────────────────────────────────────────┘

策略组合

实际项目中,通常组合使用多种策略:

典型的组合方案:

  ┌─────────────────────────────────────────────────────┐
  │                                                      │
  │  Electron 运行时更新     → 全量替换(低频,每月一次)│
  │  主进程代码更新          → 增量更新(需要重启)      │
  │  渲染层(前端)代码更新  → 热补丁(高频,无需重启)  │
  │  紧急安全修复           → 强制全量更新               │
  │                                                      │
  └─────────────────────────────────────────────────────┘

  更新频率与策略的关系:

  每天/每周    ────→  热补丁(前端代码)
  每两周       ────→  增量更新(含主进程变更)
  每月/每季度  ────→  全量更新(Electron 版本升级)
  紧急修复     ────→  强制更新(安全漏洞)

各方案优劣对比

详细对比表

┌──────────────┬───────────────┬───────────────┬──────────────────┐
│              │ 全量替换      │ 增量/差分更新 │ 热补丁           │
├──────────────┼───────────────┼───────────────┼──────────────────┤
│ 下载量       │ 80-200MB      │ 5-30MB        │ 0.1-5MB          │
│ 更新速度     │ 慢            │ 中等          │ 快               │
│ 需要重启     │ 是            │ 是            │ 可能不需要       │
│ 可更新范围   │ 一切          │ 一切          │ 仅前端代码       │
│ 实现复杂度   │ 低            │ 中            │ 高               │
│ 回滚能力     │ 需要旧安装包  │ 需要旧版快照  │ 容易(换回旧包) │
│ 适用频率     │ 低频          │ 中频          │ 高频             │
│ 安全性       │ 高(签名)    │ 高(签名)    │ 需要额外校验     │
│ 代表方案     │ electron-     │ blockmap      │ asar 替换        │
│              │ updater       │ (electron-    │ web bundle       │
│              │               │  updater)     │ 更新             │
│ 用户体验     │ 差(等待久)  │ 中等          │ 好(几乎无感)   │
└──────────────┴───────────────┴───────────────┴──────────────────┘

数据流对比

全量替换:
  服务器 ──[完整安装包 150MB]──→ 客户端
  客户端 ──→ 关闭应用 → 安装 → 重启

增量更新 (blockmap):
  服务器 ──[差分数据 10MB]──→ 客户端
  客户端 ──→ 合并差异 → 关闭应用 → 替换 → 重启

热补丁 (asar 替换):
  服务器 ──[新 asar 包 3MB]──→ 客户端
  客户端 ──→ 下载 → 替换 asar → 刷新/重启渲染进程

热补丁 (Web Bundle):
  服务器 ──[前端资源包 0.5MB]──→ 客户端
  客户端 ──→ 下载 → 替换前端文件 → 刷新页面(无需重启应用)

Electron 生态方案总览

1. electron-updater(最主流)

electron-updater 是 electron-builder 的配套更新方案:

  特点:
  - 与 electron-builder 深度集成
  - 支持 GitHub Releases、S3、自建服务器
  - 支持 blockmap 差分更新
  - 支持代码签名校验
  - 完整的事件生命周期
  - Windows: NSIS 静默安装
  - macOS: zip 解压替换

  适用: 大多数 Electron 应用的首选方案

2. Sparkle(macOS 原生)

Sparkle 是 macOS 上历史悠久的更新框架:

  特点:
  - macOS 原生体验
  - 支持 EdDSA 签名
  - 支持增量更新
  - 需要额外集成到 Electron

  适用: 对 macOS 体验有极致要求的应用

3. 自建更新系统

完全自建更新服务:

  特点:
  - 完全控制更新逻辑
  - 可实现任意更新策略
  - 维护成本高
  - 需要自己处理签名校验

  适用: 大型团队、有特殊需求(灰度发布、A/B 测试等)

4. Hazel / Nuts(更新服务器)

开源的 Electron 更新服务器:

  Hazel: Vercel 一键部署,读取 GitHub Releases
  Nuts:  自托管服务,支持更多功能(下载统计、渠道管理)

  适用: 需要自定义更新服务器但不想从零开发

方案选择决策树

你需要什么样的更新?

  ├── 简单的全量更新
  │     └── electron-updater + GitHub Releases ✓

  ├── 需要灰度发布/渠道管理
  │     └── 自建服务器 + electron-updater

  ├── 需要无感知热更新(前端代码)
  │     └── Web Bundle 更新(自建)

  ├── 需要快速修复线上 bug
  │     └── asar 热补丁 + electron-updater 组合

  └── 需要完全可控
        └── 自建更新系统

更新的技术挑战

1. 权限问题

更新过程中的权限挑战:

  macOS:
  ┌──────────────────────────────────────────┐
  │  /Applications/MyApp.app/                │
  │  - 需要写入权限替换文件                  │
  │  - 如果用户从 DMG 安装到 /Applications   │
  │    需要 admin 权限                       │
  │  - 如果安装到 ~/Applications             │
  │    不需要额外权限                        │
  │                                          │
  │  解决: 更新助手进程 + 权限提升           │
  └──────────────────────────────────────────┘

  Windows:
  ┌──────────────────────────────────────────┐
  │  C:\Users\<user>\AppData\Local\Programs\ │
  │  - 通常安装在用户目录,不需要 admin      │
  │  - 但 per-machine 安装需要 admin         │
  │  - NSIS 静默安装模式处理                 │
  │                                          │
  │  解决: 使用用户级安装 (perMachine: false) │
  └──────────────────────────────────────────┘

  Linux:
  ┌──────────────────────────────────────────┐
  │  AppImage: 不需要安装,替换文件即可      │
  │  deb/rpm: 需要 root 权限                 │
  │                                          │
  │  解决: 推荐 AppImage 分发方式            │
  └──────────────────────────────────────────┘

2. 签名校验

签名校验流程:

  更新服务器

       │ 下载更新包 + 签名信息

  ┌─────────────────────────────┐
  │  客户端验证流程:            │
  │                             │
  │  1. 下载 latest.yml         │
  │     (包含版本号、文件名、   │
  │      SHA512 hash、大小)     │
  │                             │
  │  2. 下载更新包              │
  │                             │
  │  3. 校验 SHA512 hash        │
  │     hash(下载文件) === yml 中的 hash?
  │                             │
  │  4. (macOS) 验证代码签名    │
  │     codesign --verify       │
  │                             │
  │  5. (Windows) 验证 Authenticode │
  │     签名证书是否匹配        │
  │                             │
  │  6. 校验通过 → 执行安装     │
  │     校验失败 → 拒绝更新     │
  └─────────────────────────────┘

3. 回滚机制

回滚策略:

  方案 A: 保留旧版本文件
  ┌──────────────────────────────────────────┐
  │  更新前: 备份当前版本到 backup/          │
  │  更新后: 如果启动失败,恢复备份          │
  │  清理: 确认新版本稳定后删除备份          │
  └──────────────────────────────────────────┘

  方案 B: 服务端回滚
  ┌──────────────────────────────────────────┐
  │  发现问题: 在更新服务器上将 latest 指向  │
  │           旧版本                         │
  │  效果: 用户下次检查更新时"更新"到旧版本  │
  └──────────────────────────────────────────┘

  方案 C: 版本快照 (热补丁方案)
  ┌──────────────────────────────────────────┐
  │  本地保留最近 N 个版本的 asar/bundle     │
  │  出问题时切换到上一个版本                │
  │  不需要重新下载                          │
  └──────────────────────────────────────────┘

4. 更新中断与恢复

如果更新过程中发生意外:

  下载中断:
  ├── 网络断开 → 支持断点续传
  ├── 应用被关闭 → 下次启动继续下载
  └── 磁盘空间不足 → 提示用户清理

  安装中断:
  ├── 替换文件时崩溃 → 需要原子替换 (rename)
  ├── 签名校验失败 → 丢弃下载文件,不安装
  └── 安装后启动失败 → 自动回滚

  最佳实践: 原子更新
  ┌──────────────────────────────────────────┐
  │  1. 下载到临时目录 (不影响当前运行)      │
  │  2. 校验完整性 (hash + 签名)             │
  │  3. 原子替换 (rename,不是逐文件复制)    │
  │  4. 重启应用                             │
  │  5. 如果启动失败,自动恢复旧版本         │
  └──────────────────────────────────────────┘

深入理解

更新策略与应用架构的关系

你的 Electron 应用架构决定了可用的更新策略:

  架构 A: 单体应用(代码全在 main.js 和几个渲染页面中)
  ├── 任何改动都需要重启
  └── 推荐: electron-updater 全量/增量更新

  架构 B: 前后端分离(主进程薄层 + 前端 SPA)
  ├── 主进程: 窗口管理、IPC、原生 API
  ├── 渲染层: React/Vue SPA,独立构建产物
  ├── 主进程改动 → 需要重启
  ├── 前端改动 → 可以热替换
  └── 推荐: electron-updater + Web Bundle 热更新

  架构 C: 插件化架构
  ├── 核心: 稳定的 Electron 壳
  ├── 插件: 动态加载的功能模块
  ├── 核心更新 → 全量更新
  ├── 插件更新 → 独立热加载
  └── 推荐: 核心用 electron-updater + 插件用自建更新

更新频率与用户体验的平衡

更新频率 vs 用户感知:

  频率过低:
  ├── bug 修复慢
  ├── 用户版本碎片化
  └── 安全风险高

  频率过高:
  ├── 用户频繁被打扰
  ├── 更新疲劳 → 用户开始忽略更新
  └── 测试不充分 → 引入新 bug

  最佳实践:
  ┌──────────────────────────────────────────┐
  │  类型        │ 频率      │ 方式          │
  │──────────────│───────────│───────────────│
  │  安全修复    │ 立即      │ 强制更新      │
  │  bug 修复    │ 每周      │ 静默更新      │
  │  新功能      │ 每两周    │ 通知+自愿     │
  │  大版本      │ 每季度    │ 引导更新      │
  └──────────────────────────────────────────┘

常见问题

Q1: 能否在不重启应用的情况下更新?

部分可以。渲染层代码(HTML/CSS/JS)可以通过替换文件后刷新页面来更新。但主进程代码(main.js、preload.js)的变更必须重启应用才能生效,因为 Node.js 模块一旦加载就会被缓存。

Q2: 自动更新需要 HTTPS 吗?

强烈建议用 HTTPS。虽然 electron-updater 有 SHA512 校验,但没有 HTTPS 的话,中间人可以替换整个更新包(包括 yml 和安装包),只要它们的 hash 一致。

Q3: 用户拒绝更新怎么办?

策略分级:

  • 建议更新:通知用户有新版本,用户可选择稍后
  • 推荐更新:每次启动都提醒,但允许跳过
  • 强制更新:低于最低版本号时,必须更新才能使用

Q4: 离线用户怎么更新?

方案:

  • 提供离线安装包下载链接
  • 支持手动导入更新包
  • 在有网络时自动检查并缓存更新

Q5: 如何测试更新流程?

  • 本地起一个静态文件服务器模拟更新服务
  • 使用 electron-updater 的 dev 模式
  • staging 和 production 使用不同的更新渠道

实践建议

1. 方案选择

大多数项目: electron-updater(足够好,维护少)
需要热更新: electron-updater + asar/web bundle 补丁
大型产品:   自建更新服务 + 灰度 + 回滚 + 监控

2. 更新体验设计原则

  1. 尽量静默:后台下载,不打扰用户
  2. 给予控制:让用户选择何时安装
  3. 快速完成:差分更新减少下载量
  4. 安全第一:强制的安全更新不可跳过
  5. 透明沟通:告诉用户更新了什么(changelog)

3. 上线前检查

□ 更新服务器可用且返回正确的 yml
□ 代码签名正确
□ SHA512 hash 匹配
□ 不同版本之间的升级路径测试
□ 更新失败的处理(重试、回滚)
□ 灰度发布机制就绪
□ 监控和报警配置

本章小结

桌面应用的热更新是一个复杂但关键的基础设施:

  1. 三种策略:全量替换、增量更新、热补丁
  2. 组合使用:不同类型的变更用不同策略
  3. electron-updater 是大多数项目的起点
  4. 安全、回滚、灰度是生产环境的必需品
  5. 更新体验直接影响用户满意度

接下来几章将逐一深入各种更新方案的实现细节。


上一篇Part 1: 08 - 打包与分发
下一篇02 - electron-updater 详解