Electron 打包与分发
构建、自动更新与代码签名
目录
打包工具
工具对比
| 工具 | 特点 | 推荐场景 |
|---|---|---|
| electron-builder | 功能全面、配置灵活 | 生产项目首选 |
| electron-forge | Electron 官方、插件化 | 新项目快速启动 |
| electron-packager | 轻量、仅打包 | 简单需求 |
配置实践
electron-builder 配置
json
// package.json
{
"name": "my-app",
"version": "1.0.0",
"main": "dist/main.js",
"scripts": {
"build": "electron-builder",
"build:mac": "electron-builder --mac",
"build:win": "electron-builder --win",
"build:linux": "electron-builder --linux"
},
"build": {
"appId": "com.mycompany.myapp",
"productName": "My App",
"directories": {
"output": "release",
"buildResources": "resources"
},
"files": [
"dist/**/*",
"node_modules/**/*",
"package.json"
],
"mac": {
"category": "public.app-category.productivity",
"icon": "resources/icon.icns",
"target": [
{ "target": "dmg", "arch": ["x64", "arm64"] },
{ "target": "zip", "arch": ["x64", "arm64"] }
],
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "resources/entitlements.mac.plist",
"entitlementsInherit": "resources/entitlements.mac.plist"
},
"win": {
"icon": "resources/icon.ico",
"target": [
{ "target": "nsis", "arch": ["x64"] },
{ "target": "portable" }
]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"installerIcon": "resources/icon.ico",
"uninstallerIcon": "resources/icon.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true
},
"linux": {
"icon": "resources/icons",
"target": ["AppImage", "deb"],
"category": "Utility"
},
"publish": {
"provider": "github",
"owner": "mycompany",
"repo": "my-app"
}
}
}electron-forge 配置
javascript
// forge.config.js
module.exports = {
packagerConfig: {
icon: './resources/icon',
asar: true
},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {
name: 'my_app'
}
},
{
name: '@electron-forge/maker-dmg',
config: {
format: 'ULFO'
}
},
{
name: '@electron-forge/maker-deb',
config: {}
}
],
plugins: [
{
name: '@electron-forge/plugin-webpack',
config: {
mainConfig: './webpack.main.config.js',
renderer: {
config: './webpack.renderer.config.js',
entryPoints: [
{
html: './src/index.html',
js: './src/renderer.js',
name: 'main_window',
preload: {
js: './src/preload.js'
}
}
]
}
}
}
]
};自动更新
electron-updater 配置
typescript
// main.js
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
// 配置日志
autoUpdater.logger = log;
autoUpdater.logger.transports.file.level = 'info';
// 检查更新
export function checkForUpdates() {
autoUpdater.checkForUpdatesAndNotify();
}
// 事件监听
autoUpdater.on('checking-for-update', () => {
log.info('Checking for update...');
});
autoUpdater.on('update-available', (info) => {
log.info('Update available:', info.version);
// 通知渲染进程
mainWindow.webContents.send('update-available', info);
});
autoUpdater.on('update-not-available', () => {
log.info('Update not available.');
});
autoUpdater.on('download-progress', (progress) => {
log.info(`Download progress: ${progress.percent}%`);
mainWindow.webContents.send('download-progress', progress);
});
autoUpdater.on('update-downloaded', (info) => {
log.info('Update downloaded');
// 提示用户重启
dialog.showMessageBox({
type: 'info',
title: 'Update Ready',
message: `Version ${info.version} has been downloaded. Restart to apply.`,
buttons: ['Restart', 'Later']
}).then(({ response }) => {
if (response === 0) {
autoUpdater.quitAndInstall();
}
});
});
autoUpdater.on('error', (err) => {
log.error('Update error:', err);
});更新服务配置
typescript
// GitHub Releases
autoUpdater.setFeedURL({
provider: 'github',
owner: 'mycompany',
repo: 'my-app'
});
// 自建服务器
autoUpdater.setFeedURL({
provider: 'generic',
url: 'https://updates.myapp.com'
});
// 私有 S3
autoUpdater.setFeedURL({
provider: 's3',
bucket: 'my-app-updates',
region: 'us-east-1'
});更新流程
应用启动 → 检查更新 → 下载更新 → 提示用户 → 重启应用
│
▼
/update/latest.yml (元数据)
/update/app-1.2.0.exe (安装包)
/update/app-1.2.0.exe.blockmap (增量)代码签名
macOS 签名
bash
# 1. 获取证书
# Apple Developer Program → Certificates
# 2. 配置 electron-builder
# package.json
{
"build": {
"mac": {
"identity": "Developer ID Application: Company Name (TEAM_ID)",
"hardenedRuntime": true,
"entitlements": "resources/entitlements.mac.plist",
"entitlementsInherit": "resources/entitlements.mac.plist"
},
"afterSign": "scripts/notarize.js"
}
}xml
<!-- 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>
<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/>
</dict>
</plist>javascript
// scripts/notarize.js
const { notarize } = require('@electron/notarize');
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') return;
const appName = context.packager.appInfo.productFilename;
await notarize({
appBundleId: 'com.mycompany.myapp',
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_PASSWORD,
teamId: process.env.APPLE_TEAM_ID
});
};Windows 签名
json
// package.json
{
"build": {
"win": {
"certificateFile": "path/to/certificate.pfx",
"certificatePassword": "${env.WIN_CSC_KEY_PASSWORD}",
"signingHashAlgorithms": ["sha256"]
}
}
}CI/CD 配置
GitHub Actions
yaml
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci
- run: npm run build
- name: Build Electron
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# macOS 签名
CSC_LINK: ${{ secrets.MAC_CERTS }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Windows 签名
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
run: npm run electron:build
- uses: actions/upload-artifact@v3
with:
name: release-${{ matrix.os }}
path: release/*高频面试题
Q1: 如何实现增量更新?
electron-updater 自动支持:
- 生成
.blockmap文件(块级差异) - 只下载变化的块
- 客户端合并块还原完整文件
Q2: macOS 公证 (Notarization) 是什么?
- 上传应用到 Apple 服务器
- Apple 扫描恶意代码
- 获取公证票据
- stapler 票据到应用
目的:Gatekeeper 验证,用户首次打开不会提示"无法验证开发者"
Q3: ASAR 打包的作用?
| 特性 | 不打包 | ASAR |
|---|---|---|
| 文件结构 | 暴露源码 | 归档隐藏 |
| 读取效率 | 多次 IO | 单次加载 |
| 路径长度 | Windows 260 限制 | 无限制 |
| 安全性 | 低 | 中(仍可解包) |
Q4: 如何减少安装包体积?
- 排除开发依赖:
devDependencies不打包 - 平台原生模块:只包含目标平台
- 分析依赖:删除未使用的 npm 包
- 代码压缩:production 构建
- 图标优化:只保留必要尺寸
json
{
"build": {
"files": [
"!**/node_modules/*/{CHANGELOG.md,README.md}",
"!**/node_modules/*/{test,__tests__,tests}"
]
}
}