Electron 应用升级方案与 electron-vite 配置指南

概述

Electron 应用的升级机制是桌面应用开发中的核心功能之一。本文将深入探讨 Electron 应用的各种升级方案、代码签名要求,以及如何使用 electron-vite 构建现代化的 Electron 应用。

Electron 升级方案对比

方案平台支持复杂度适用场景
Electron autoUpdatermacOS, Windows中等需要完全控制更新流程
electron-updatermacOS, Windows, Linux推荐方案,功能全面
update-electron-appmacOS, Windows极低GitHub 开源项目
electron-delta-updaterWindows (NSIS)中等需要增量更新

方案一:Electron 内置 autoUpdater

The easiest and officially supported one is taking advantage of the built-in Squirrel framework and Electron’s autoUpdater module. — Electron 官方文档

基本用法

import { app, autoUpdater, dialog } from 'electron'
// 设置更新服务器 URL
const server = 'https://your-update-server.com'
const url = `${server}/update/${process.platform}/${app.getVersion()}`
autoUpdater.setFeedURL({ url })
// 检查更新
autoUpdater.checkForUpdates()
// 监听更新事件
autoUpdater.on('update-available', () => {
console.log('发现新版本')
})
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
dialog.showMessageBox({
type: 'info',
title: '更新已就绪',
message: `新版本 ${releaseName} 已下载完成,是否立即安装?`,
buttons: ['立即安装', '稍后']
}).then((result) => {
if (result.response === 0) {
autoUpdater.quitAndInstall()
}
})
})
autoUpdater.on('error', (error) => {
console.error('更新出错:', error)
})

平台差异

flowchart TB
    subgraph macOS
        A[Squirrel.Mac] --> B[需要代码签名]
        B --> C[自动后台更新]
    end

    subgraph Windows
        D[Squirrel.Windows] --> E[需要安装器]
        E --> F[首次启动有文件锁]
    end

    subgraph Linux
        G[无内置支持] --> H[使用包管理器]
        H --> I[apt/yum/pacman]
    end

macOS 注意事项:

  • 必须进行代码签名才能使用自动更新
  • 使用 Squirrel.Mac 框架

Windows 注意事项:

  • 需要使用 electron-winstaller 或 Electron Forge 生成安装包
  • 首次启动时 Squirrel.Windows 会获取文件锁,此时无法检查更新

Linux:

  • 无内置自动更新支持
  • 建议使用发行版的包管理器进行更新

方案二:electron-updater (推荐)

electron-updater 是 electron-builder 提供的更新解决方案,功能比内置 autoUpdater 更强大。

核心优势

  • 支持 Linux (AppImage)
  • Windows 和 macOS 均支持代码签名验证
  • 自动生成所需的元数据文件
  • 支持下载进度和分阶段发布
  • 内置多种发布源支持

安装

Terminal window
npm install electron-updater
# 或
pnpm add electron-updater

基础配置

src/main/updater.ts
import { autoUpdater } from 'electron-updater'
import log from 'electron-log'
// 配置日志
autoUpdater.logger = log
autoUpdater.autoDownload = false
autoUpdater.autoInstallOnAppQuit = true
export function initUpdater() {
// 检查更新
autoUpdater.checkForUpdates()
autoUpdater.on('checking-for-update', () => {
log.info('正在检查更新...')
})
autoUpdater.on('update-available', (info) => {
log.info(`发现新版本: ${info.version}`)
// 可以在这里提示用户是否下载
autoUpdater.downloadUpdate()
})
autoUpdater.on('update-not-available', () => {
log.info('当前已是最新版本')
})
autoUpdater.on('download-progress', (progress) => {
log.info(`下载进度: ${progress.percent.toFixed(1)}%`)
})
autoUpdater.on('update-downloaded', () => {
log.info('更新已下载,准备安装')
// 提示用户重启安装
})
autoUpdater.on('error', (error) => {
log.error('更新错误:', error)
})
}

发布源配置

package.jsonelectron-builder.yml 中配置:

GitHub Releases

electron-builder.yml
publish:
- provider: github
owner: your-username
repo: your-app
releaseType: release

Amazon S3

publish:
- provider: s3
bucket: my-app-releases
region: us-east-1
acl: public-read
path: /releases/${os}/${arch}

通用 HTTP 服务器

publish:
- provider: generic
url: https://releases.example.com
channel: latest

多发布源配置

# 第一个用于自动更新,其他用于备份
publish:
- provider: github
owner: your-username
repo: your-app
- provider: s3
bucket: backup-releases
region: eu-west-1

高级配置示例

src/main/updater.ts
import { NsisUpdater, MacUpdater, AppImageUpdater } from 'electron-updater'
import { app } from 'electron'
import log from 'electron-log'
interface UpdaterOptions {
provider: 'generic' | 'github' | 's3'
url?: string
owner?: string
repo?: string
token?: string
}
export class AppUpdater {
private autoUpdater: NsisUpdater | MacUpdater | AppImageUpdater
constructor(options: UpdaterOptions) {
const updaterOptions = this.buildOptions(options)
// 根据平台创建更新器
if (process.platform === 'win32') {
this.autoUpdater = new NsisUpdater(updaterOptions)
} else if (process.platform === 'darwin') {
this.autoUpdater = new MacUpdater(updaterOptions)
} else {
this.autoUpdater = new AppImageUpdater(updaterOptions)
}
this.autoUpdater.logger = log
this.autoUpdater.autoDownload = false
this.autoUpdater.autoInstallOnAppQuit = true
// 私有仓库需要认证
if (options.token) {
this.autoUpdater.addAuthHeader(`Bearer ${options.token}`)
}
this.setupEvents()
}
private buildOptions(options: UpdaterOptions) {
switch (options.provider) {
case 'generic':
return {
provider: 'generic' as const,
url: options.url!,
useMultipleRangeRequest: true
}
case 'github':
return {
provider: 'github' as const,
owner: options.owner!,
repo: options.repo!,
private: !!options.token
}
case 's3':
return {
provider: 's3' as const,
bucket: options.url!,
region: 'us-east-1'
}
default:
throw new Error(`未知的 provider: ${options.provider}`)
}
}
private setupEvents() {
this.autoUpdater.on('checking-for-update', () => {
log.info('检查更新中...')
})
this.autoUpdater.on('update-available', (info) => {
log.info(`发现新版本: ${info.version}`)
})
this.autoUpdater.on('download-progress', (progress) => {
log.info(`下载进度: ${progress.percent.toFixed(1)}%`)
})
this.autoUpdater.on('update-downloaded', () => {
log.info('更新已下载')
})
this.autoUpdater.on('error', (error) => {
log.error('更新错误:', error)
})
}
async checkForUpdates() {
return this.autoUpdater.checkForUpdates()
}
async downloadUpdate() {
return this.autoUpdater.downloadUpdate()
}
quitAndInstall() {
this.autoUpdater.quitAndInstall(false, true)
}
}

方案三:update-electron-app

update-electron-app 是 Electron 官方提供的零配置更新模块,专为在 GitHub 发布的开源应用设计。

安装使用

Terminal window
npm install update-electron-app
src/main/index.ts
import { updateElectronApp } from 'update-electron-app'
updateElectronApp()

只需这一行代码,它会自动:

  • 读取 package.json 中的 repository 字段
  • 使用 update.electronjs.org 服务检查更新
  • 定期检查更新并提示用户安装

适用条件

  • 应用必须开源并发布在 GitHub
  • 有公开的 GitHub Releases
  • 应用已进行代码签名 (macOS)

增量更新 (Delta Updates)

Electron 应用体积通常在 100-200MB,每次更新都下载完整包会消耗大量带宽。增量更新可以显著减少下载量。

更新方式对比

方式下载量实现复杂度平台支持
全量更新100-200MB全平台
Blockmap 差分更新10-50MBWindows, macOS
二进制差分 (Delta)0.1-1MBWindows

electron-updater 的 Blockmap 差分更新

electron-builder 内置的差分更新基于 blockmap 机制:

flowchart LR
    A[下载新版 blockmap] --> B[对比新旧 blockmap]
    B --> C[识别变化的块]
    C --> D[仅下载变化部分]
    D --> E[本地重组完整文件]

工作原理:

  1. 构建时生成 .blockmap 文件,记录文件的块哈希
  2. 更新时下载新旧版本的 blockmap
  3. 对比识别变化的块 (如 “File has 35 changed blocks”)
  4. 仅下载变化部分 (如 “Full: 57,990 KB, To download: 764 KB (1%)”)

配置方式:

electron-builder 默认启用差分更新,确保以下文件上传到服务器:

# Windows NSIS
app-setup.exe
app-setup.exe.blockmap
latest.yml
# macOS
app.zip
app.zip.blockmap
latest-mac.yml

验证差分更新是否生效:

查看更新日志,应出现类似信息:

Download block maps (old: 'app-1.0.0.exe.blockmap', new: 'app-1.1.0.exe.blockmap')
File has 35 changed blocks
Full: 57,990.1 KB, To download: 764.79 KB (1%)

真正的二进制差分:@electron-delta

The delta update mechanism based on the blockmap approach uses a very good amount of bandwidth. The binary diffing approach works better and downloads only the diff, generally less than 1MB to update the app. — electron-delta 文档

electron-delta 使用 HDiffPatch 库实现真正的二进制差分,可将更新包压缩到几 KB 到 1MB。

安装:

Terminal window
npm install @electron-delta/builder --save-dev
npm install @electron-delta/updater --save

配置 electron-builder:

.electron-delta.js
const DeltaBuilder = require('@electron-delta/builder')
const options = {
productIconPath: './build/icon.ico',
productName: 'YourApp',
getPreviousReleases: async () => {
// 返回之前版本的信息,用于生成 delta
return [
{
version: '1.0.0',
url: 'https://your-server.com/releases/app-1.0.0.exe'
}
]
},
sign: async (filePath) => {
// 可选:签名 delta 文件
}
}
exports.default = async function(context) {
const deltaBuilder = new DeltaBuilder(options)
await deltaBuilder.build({
context,
onProgress: (progress) => console.log(`Delta progress: ${progress}%`)
})
}
// package.json 或 electron-builder.yml
{
"build": {
"afterAllArtifactBuild": ".electron-delta.js"
}
}

在应用中使用:

src/main/updater.ts
import { DeltaUpdater } from '@electron-delta/updater'
import { app } from 'electron'
const deltaUpdater = new DeltaUpdater({
logger: console,
autoDownload: false,
autoInstallOnAppQuit: true
})
export async function checkForUpdates() {
try {
const result = await deltaUpdater.checkForUpdates()
if (result?.updateInfo) {
console.log(`发现新版本: ${result.updateInfo.version}`)
// Delta 更新会自动优先使用
await deltaUpdater.downloadUpdate()
}
} catch (error) {
console.error('更新检查失败:', error)
}
}
deltaUpdater.on('update-downloaded', () => {
// 提示用户重启安装
})

限制:

  • 目前仅支持 Windows (NSIS)
  • macOS 支持开发中
  • 需要保留历史版本用于生成 delta

更新服务器后端配置

根据项目需求,可以选择不同的更新服务器方案。

方案对比

方案适用场景维护成本功能
GitHub Releases开源项目基础
静态文件托管 (S3/OSS)私有项目基础
Hazel私有 GitHub 项目基础 + 缓存
Nucleus企业级多应用 + 渠道
electron-release-server完全自主控制全功能 + UI

静态文件托管方案

最简单的方案是使用静态文件托管服务 (S3、阿里云 OSS、Cloudflare R2 等)。

目录结构:

releases/
├── win32/
│ ├── latest.yml
│ ├── app-setup-1.0.0.exe
│ ├── app-setup-1.0.0.exe.blockmap
│ ├── app-setup-1.1.0.exe
│ └── app-setup-1.1.0.exe.blockmap
├── darwin/
│ ├── latest-mac.yml
│ ├── app-1.0.0.zip
│ ├── app-1.0.0.zip.blockmap
│ ├── app-1.1.0.zip
│ └── app-1.1.0.zip.blockmap
└── linux/
├── latest-linux.yml
└── app-1.1.0.AppImage

latest.yml 示例:

version: 1.1.0
files:
- url: app-setup-1.1.0.exe
sha512: <sha512-hash>
size: 85000000
path: app-setup-1.1.0.exe
sha512: <sha512-hash>
releaseDate: '2026-02-01T10:00:00.000Z'

electron-builder 配置:

electron-builder.yml
publish:
- provider: generic
url: https://your-bucket.s3.amazonaws.com/releases
channel: latest

应用端配置:

import { autoUpdater } from 'electron-updater'
// 设置更新源
autoUpdater.setFeedURL({
provider: 'generic',
url: 'https://your-bucket.s3.amazonaws.com/releases'
})
autoUpdater.checkForUpdates()

Squirrel 更新服务器 API 规范

如果使用 Electron 内置的 autoUpdater (非 electron-updater),服务器需要实现 Squirrel 协议。

URL 格式:

const server = 'https://your-server.com'
const url = `${server}/update/${process.platform}/${app.getVersion()}`
autoUpdater.setFeedURL({ url })

macOS 响应格式 (Squirrel.Mac):

有更新时返回 200 OK + JSON:

{
"url": "https://your-server.com/releases/app-1.1.0.zip",
"name": "v1.1.0",
"notes": "Bug fixes and improvements",
"pub_date": "2026-02-01T10:00:00Z"
}

无更新时返回 204 No Content。

Windows 响应格式 (Squirrel.Windows):

客户端会请求 /RELEASES 子路径,返回 RELEASES 文件内容:

<sha1> app-1.1.0-full.nupkg <size>
<sha1> app-1.1.0-delta.nupkg <size>

Hazel - 轻量级更新服务器

Hazel 是 Vercel 官方维护的轻量级更新服务器,可免费部署在 Vercel 上。

特点:

  • 自动从 GitHub Releases 拉取更新
  • 利用 GitHub CDN 分发
  • 支持私有仓库
  • 自动缓存

部署到 Vercel:

  1. Fork hazel 仓库
  2. 在 Vercel 中导入项目
  3. 配置环境变量:
Terminal window
ACCOUNT=your-github-username
REPOSITORY=your-repo-name
TOKEN=your-github-token # 私有仓库需要
PRE=1 # 可选:仅提供预发布版本
INTERVAL=15 # 可选:缓存刷新间隔 (分钟)

应用端配置:

import { autoUpdater } from 'electron'
const server = 'https://your-hazel.vercel.app'
const url = `${server}/update/${process.platform}/${app.getVersion()}`
autoUpdater.setFeedURL({ url })
autoUpdater.checkForUpdates()

electron-release-server - 全功能自托管方案

electron-release-server 提供完整的发布管理功能。

特点:

  • Web 管理界面
  • 多渠道支持 (stable, beta, alpha)
  • 版本管理
  • 下载统计
  • 兼容 Squirrel 自动更新

Docker 部署:

docker-compose.yml
version: '3'
services:
app:
image: eoskin/electron-release-server
ports:
- "8080:80"
environment:
- APP_USERNAME=admin
- APP_PASSWORD=your-secure-password
- DB_HOST=db
- DB_PORT=5432
- DB_USERNAME=postgres
- DB_PASSWORD=postgres
- DB_NAME=electron_release_server
- TOKEN_SECRET=your-64-char-random-string
- DATA_ENCRYPTION_KEY=your-32-byte-base64-key
depends_on:
- db
volumes:
- ./assets:/app/assets
db:
image: postgres:14
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=electron_release_server
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:

生成密钥:

Terminal window
# 生成 DATA_ENCRYPTION_KEY
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
# 生成 TOKEN_SECRET
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"

启动服务:

Terminal window
docker-compose up -d

Nginx 反向代理配置:

server {
listen 443 ssl;
server_name releases.your-domain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}

应用端配置:

import { autoUpdater } from 'electron'
const server = 'https://releases.your-domain.com'
const url = `${server}/update/${process.platform}/${app.getVersion()}`
autoUpdater.setFeedURL({ url })
autoUpdater.checkForUpdates()

Nucleus - 企业级方案

Nucleus 是 Atlassian 开源的更新服务器,支持多应用和多渠道。

特点:

  • 支持多个应用
  • 多渠道 (stable, beta, alpha)
  • 静态文件存储 (S3、本地)
  • 百分比灰度发布
  • 管理后台

适用场景:

  • 企业内部多个 Electron 应用
  • 需要精细化发布控制
  • 已有 S3/CloudFront 基础设施

自定义更新服务器 (Node.js 示例)

如果需要完全自定义逻辑,可以自己实现更新服务器:

server.ts
import express from 'express'
import path from 'path'
import fs from 'fs'
const app = express()
const RELEASES_DIR = './releases'
// 获取最新版本信息
function getLatestRelease(platform: string) {
const ymlFile = platform === 'darwin' ? 'latest-mac.yml' : 'latest.yml'
const ymlPath = path.join(RELEASES_DIR, platform, ymlFile)
if (fs.existsSync(ymlPath)) {
const content = fs.readFileSync(ymlPath, 'utf-8')
// 解析 YAML 返回版本信息
return parseYaml(content)
}
return null
}
// Squirrel.Mac 更新检查端点
app.get('/update/darwin/:version', (req, res) => {
const currentVersion = req.params.version
const latest = getLatestRelease('darwin')
if (!latest || latest.version === currentVersion) {
return res.status(204).end()
}
res.json({
url: `https://your-server.com/releases/darwin/${latest.path}`,
name: `v${latest.version}`,
notes: latest.releaseNotes || '',
pub_date: latest.releaseDate
})
})
// Squirrel.Windows RELEASES 端点
app.get('/update/win32/:version/RELEASES', (req, res) => {
const releasesPath = path.join(RELEASES_DIR, 'win32', 'RELEASES')
if (fs.existsSync(releasesPath)) {
res.type('text/plain').sendFile(releasesPath)
} else {
res.status(404).end()
}
})
// 静态文件服务
app.use('/releases', express.static(RELEASES_DIR))
app.listen(3000, () => {
console.log('Update server running on port 3000')
})

更新服务器最佳实践

代码签名详解

代码签名是桌面应用发布的核心环节。本节深入讲解 Windows 和 macOS 平台的签名方案、最佳实践以及 CI/CD 自动化配置。

为什么需要代码签名

flowchart LR
    A[未签名应用] --> B{操作系统检查}
    B -->|macOS| C[Gatekeeper 阻止]
    B -->|Windows| D[SmartScreen 警告]

    E[已签名应用] --> F{操作系统检查}
    F -->|macOS| G[正常运行]
    F -->|Windows| H[正常运行]

Code signing is a security technology to certify that an app was created by you. You should sign your application so it does not trigger any operating system security warnings. — Electron 官方文档

Windows 代码签名

证书类型对比

类型SmartScreen硬件要求价格适用场景
Azure Trusted Signing即时信任无 (云端)$9.99/月推荐方案
标准 OV 证书需积累信任HSM/云端$200-500/年小型项目
EV 证书即时信任USB Token$300-600/年驱动开发

重要变更 (2024)

EV certificates previously provided instant SmartScreen reputation, bypassing security warnings immediately. However, in March 2024, Microsoft changed how SmartScreen interacts with EV certificates - they no longer instantly remove warnings. — SSL Insights

2024 年 3 月起,EV 证书不再自动获得 SmartScreen 信任,OV 和 EV 证书现在都需要通过下载量积累信任。

硬件存储要求 (2023.06 起)

Starting June 1, 2023, private keys for code signing certificates need to be stored on a hardware storage module compliant with FIPS 140 Level 2. — electron-builder 文档

这意味着纯软件证书已不再可用,必须使用:

  • 物理 USB Token (HSM)
  • 云端签名服务 (如 Azure、DigiCert KeyLocker)

Azure Trusted Signing (推荐方案)

Azure Artifact Signing (原 Azure Trusted Signing) 是 Microsoft 官方云端签名服务。

优势:

  • 价格最低 ($9.99/月)
  • 即时获得 SmartScreen 信任
  • 无需管理物理硬件
  • 证书有效期仅 3 天,安全性更高
  • CI/CD 友好

可用性限制 (2025):

  • 美国、加拿大、欧盟、英国的组织
  • 美国、加拿大的个人开发者
  • 组织需要 3 年以上可验证的经营历史

设置步骤:

  1. 创建 Azure 账户并设置 Trusted Signing Account
  2. 创建 App Registration 并生成 Secret
  3. 分配 “Trusted Signing Certificate Profile Signer” 角色
  4. 配置 electron-builder
electron-builder.yml
win:
azureSignOptions:
endpoint: https://eus.codesigning.azure.net
codeSigningAccountName: your-account-name
certificateProfileName: your-profile-name

环境变量配置:

Terminal window
# Azure 认证
export AZURE_TENANT_ID="your-tenant-id"
export AZURE_CLIENT_ID="your-client-id"
export AZURE_CLIENT_SECRET="your-client-secret"

传统证书签名

如果无法使用 Azure Trusted Signing,可以使用传统证书:

electron-builder.yml
win:
certificateFile: path/to/certificate.pfx
certificatePassword: ${env.WIN_CSC_KEY_PASSWORD}
# 或使用证书主题名称 (适用于 HSM)
certificateSubjectName: "Your Company Name"

云端签名服务选项:

  • DigiCert KeyLocker
  • SSL.com eSigner
  • GlobalSign

macOS 代码签名与公证

签名流程概览

flowchart TD
    A[构建应用] --> B[代码签名]
    B --> C[启用 Hardened Runtime]
    C --> D[上传到 Apple 公证服务]
    D --> E{公证结果}
    E -->|通过| F[Staple 票据]
    E -->|失败| G[查看日志修复问题]
    F --> H[分发应用]

前提条件

  1. Apple Developer Program ($99/年)
  2. Developer ID 证书
    • Developer ID Application (应用签名)
    • Developer ID Installer (PKG 签名)
  3. App-Specific Password (用于 notarytool)

Hardened Runtime

In the latest macOS versions, Apple requires that the hardened runtime is enabled to launch apps. It protects the app from malware injections, DLL attacks, and process memory space tampering. — Electron Forge

Electron 应用必须启用 Hardened Runtime 并配置正确的 entitlements:

build/entitlements.mac.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Electron 必需 -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<!-- 可选:根据应用需求添加 -->
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>

公证 (Notarization)

From macOS 10.15 (Catalina) onwards, your application needs to be both code signed and notarized to run on a user’s machine without disabling additional operating system security checks. — Apple Developer Documentation

使用 notarytool 命令行工具:

Terminal window
# 1. 存储凭证 (一次性设置)
xcrun notarytool store-credentials "notary-profile" \
--apple-id "your-apple-id@example.com" \
--team-id "ABCD123456" \
--password "app-specific-password"
# 2. 提交公证
xcrun notarytool submit MyApp.dmg \
--keychain-profile "notary-profile" \
--wait
# 3. Staple 票据
xcrun stapler staple MyApp.dmg
# 4. 验证 (可选)
xcrun stapler validate MyApp.dmg

重要提醒:

The Apple notary service will no longer accept uploads from Xcode 13 or earlier or from altool as of November 1, 2023. — Apple Developer Documentation

electron-builder macOS 完整配置

electron-builder.yml
mac:
category: public.app-category.developer-tools
target:
- dmg
- zip
hardenedRuntime: true
gatekeeperAssess: false
entitlements: build/entitlements.mac.plist
entitlementsInherit: build/entitlements.mac.plist
notarize:
teamId: ${env.APPLE_TEAM_ID}
# DMG 配置
dmg:
sign: false # DMG 本身不需要签名,内部 .app 已签名
contents:
- x: 130
y: 220
- x: 410
y: 220
type: link
path: /Applications

环境变量配置:

Terminal window
# Apple 签名
export APPLE_ID="your-apple-id@example.com"
export APPLE_APP_SPECIFIC_PASSWORD="xxxx-xxxx-xxxx-xxxx"
export APPLE_TEAM_ID="XXXXXXXXXX"
# 证书 (CI/CD 使用)
export CSC_LINK="base64-encoded-p12-certificate"
export CSC_KEY_PASSWORD="certificate-password"

CI/CD 自动签名配置

GitHub Actions 完整示例

.github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
# macOS 签名设置
- name: Setup macOS signing
if: matrix.os == 'macos-latest'
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
# Windows Azure Trusted Signing 设置
- name: Setup Windows signing
if: matrix.os == 'windows-latest'
run: |
dotnet tool install --global AzureSignTool
- name: Build and Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# macOS
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_LINK: ${{ secrets.MACOS_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
# Windows Azure Trusted Signing
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
run: npm run release

所需 GitHub Secrets

Secret平台说明
MACOS_CERTIFICATEmacOSBase64 编码的 .p12 证书
MACOS_CERTIFICATE_PWDmacOS证书密码
KEYCHAIN_PASSWORDmacOS临时 Keychain 密码
APPLE_IDmacOSApple ID 邮箱
APPLE_APP_SPECIFIC_PASSWORDmacOSApp 专用密码
APPLE_TEAM_IDmacOS团队 ID
AZURE_TENANT_IDWindowsAzure 租户 ID
AZURE_CLIENT_IDWindowsAzure 应用 ID
AZURE_CLIENT_SECRETWindowsAzure 应用密钥

生成 Base64 证书

Terminal window
# macOS
base64 -i Certificates.p12 -o encoded.txt
# Linux
base64 -w 0 Certificates.p12 > encoded.txt

签名最佳实践

常见问题排查

macOS 公证失败

Terminal window
# 查看详细日志
xcrun notarytool log <submission-id> \
--keychain-profile "notary-profile"

常见原因:

  • 未启用 Hardened Runtime
  • 缺少必需的 entitlements
  • 使用了不允许的 entitlements
  • 包含未签名的二进制文件

Windows SmartScreen 警告

  • OV 证书需要积累下载量建立信任
  • 使用 Azure Trusted Signing 可即时获得信任
  • 确保签名时间戳有效

GitHub Actions 签名挂起

If your GitHub workflow just hangs forever at the Electron signing step, this appears to be a symptom of not properly configuring the macOS Keychain in the CI environment. — Code Jam

解决方案:确保正确创建和解锁 Keychain,设置 security set-key-partition-list

electron-vite 配置指南

electron-vite 是基于 Vite 的下一代 Electron 构建工具,提供极速的开发体验。

核心特性

创建项目

Terminal window
npm create electron-vite@latest my-app
cd my-app
npm install
npm run dev

项目结构

my-app/
├── electron.vite.config.ts
├── package.json
├── src/
│ ├── main/
│ │ └── index.ts
│ ├── preload/
│ │ └── index.ts
│ └── renderer/
│ ├── index.html
│ └── src/
│ ├── App.tsx
│ └── main.tsx

基础配置

electron.vite.config.ts
import { defineConfig } from 'electron-vite'
export default defineConfig({
main: {
// 主进程 Vite 配置
},
preload: {
// 预加载脚本 Vite 配置
},
renderer: {
// 渲染进程 Vite 配置
}
})

完整配置示例

electron.vite.config.ts
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import { resolve } from 'path'
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()],
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/main/index.ts')
}
},
outDir: 'dist/main'
}
},
preload: {
plugins: [externalizeDepsPlugin()],
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/preload/index.ts')
}
},
outDir: 'dist/preload'
}
},
renderer: {
root: 'src/renderer',
resolve: {
alias: {
'@renderer': resolve('src/renderer/src'),
'@': resolve('src/renderer/src')
}
},
plugins: [react(), tailwindcss()],
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/renderer/index.html')
}
},
outDir: 'dist/renderer'
}
}
})

多窗口配置

electron.vite.config.ts
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import { resolve } from 'path'
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()],
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/main/index.ts')
}
}
}
},
preload: {
plugins: [externalizeDepsPlugin()],
build: {
rollupOptions: {
input: {
browser: resolve(__dirname, 'src/preload/browser.ts'),
webview: resolve(__dirname, 'src/preload/webview.ts')
}
}
}
},
renderer: {
build: {
rollupOptions: {
input: {
browser: resolve(__dirname, 'src/renderer/browser.html'),
settings: resolve(__dirname, 'src/renderer/settings.html')
}
}
}
}
})

主进程加载窗口

src/main/index.ts
import { app, BrowserWindow } from 'electron'
import path from 'path'
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, '../preload/browser.js'),
sandbox: false
}
})
// 开发环境使用 HMR
if (!app.isPackaged && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/browser.html'))
}
}
app.whenReady().then(createWindow)

资源路径处理

Vite 默认使用绝对路径,在 Electron 的 file:// 协议下可能出问题。解决方案:

electron.vite.config.ts
export default defineConfig({
renderer: {
base: './', // 使用相对路径
build: {
// ...
}
}
})

主进程中使用 loadFile 而非 loadURL

// 生产环境
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))

与 electron-builder 集成

package.json
{
"scripts": {
"dev": "electron-vite dev",
"build": "electron-vite build",
"postbuild": "electron-builder"
},
"build": {
"directories": {
"output": "release"
},
"files": [
"dist/**/*"
],
"mac": {
"target": ["dmg", "zip"]
},
"win": {
"target": ["nsis"]
},
"linux": {
"target": ["AppImage", "deb"]
},
"publish": {
"provider": "github"
}
}
}

完整项目示例

目录结构

my-electron-app/
├── electron.vite.config.ts
├── electron-builder.yml
├── package.json
├── src/
│ ├── main/
│ │ ├── index.ts
│ │ └── updater.ts
│ ├── preload/
│ │ └── index.ts
│ └── renderer/
│ ├── index.html
│ └── src/
│ ├── App.tsx
│ ├── main.tsx
│ └── components/
└── resources/
└── icon.png

package.json

{
"name": "my-electron-app",
"version": "1.0.0",
"main": "dist/main/index.js",
"scripts": {
"dev": "electron-vite dev",
"build": "electron-vite build",
"postbuild": "electron-builder",
"release": "electron-vite build && electron-builder --publish always"
},
"dependencies": {
"electron-updater": "^6.3.0"
},
"devDependencies": {
"electron": "^33.0.0",
"electron-builder": "^25.0.0",
"electron-vite": "^3.0.0",
"@vitejs/plugin-react": "^4.0.0",
"typescript": "^5.0.0"
}
}

electron-builder.yml

appId: com.example.myapp
productName: My Electron App
directories:
output: release
files:
- dist/**/*
- package.json
mac:
category: public.app-category.developer-tools
target:
- dmg
- zip
hardenedRuntime: true
notarize:
teamId: ${env.APPLE_TEAM_ID}
win:
target:
- nsis
certificateSubjectName: "Your Company"
linux:
target:
- AppImage
- deb
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
publish:
- provider: github
owner: your-username
repo: my-electron-app

参考资料

自动更新

代码签名

CI/CD 自动化

更新服务器

构建工具

Read Next

Vercel AI SDK Agent Loop 完全指南

Read Previous

American English Phonics - 美式英语自然拼读完全指南