📋 目录
1. 打包基础概念
1.1 Electron 打包流程
┌─────────────────────────────────────────────────────────────┐
│ Electron 打包流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 开发阶段 │
│ src/main → src/preload → src/renderer │
│ ↓ ↓ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ electron-vite build │ │
│ └─────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ out/main/ out/preload/ out/renderer/│ │
│ └─────────────────────────────────────────┘ │
│ ↓ │
│ 2. 打包阶段 │
│ ┌─────────────────────────────────────────┐ │
│ │ electron-builder 打包 │ │
│ └─────────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Windows │ │ macOS │ │ Linux │ │
│ │ .exe/.msi │ │ .dmg/.pkg │ │ .AppImage │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 打包工具对比
| 工具 | 特性 | 适用场景 | 学习曲线 |
|---|
| electron-builder | 功能全面、生态成熟 | 通用场景 | ⭐⭐ |
| electron-forge | 官方推荐、模块化 | 新项目 | ⭐⭐ |
| electron-packager | 简单轻量 | 快速打包 | ⭐ |
1.3 打包产物类型
| 平台 | 产物格式 | 说明 |
|---|
| Windows | .exe (NSIS) | Windows 安装向导 |
| .msi | Microsoft Installer |
| .portable.exe | 便携版,无需安装 |
| macOS | .dmg | 磁盘镜像,最常用 |
| .pkg | macOS 安装包 |
| .zip | 压缩包 |
| Linux | .AppImage | 通用格式 |
| .deb | Debian/Ubuntu |
| .rpm | RedHat/Fedora |
2. electron-builder 配置详解
2.1 安装依赖
# 安装 electron-builder
npm install electron-builder --save-dev
# 可选:安装代码签名工具
npm install electron-builder-sodium --save-dev
npm install @electron/osx-sign --save-dev
2.2 基础配置
{
"name": "gamebox-app",
"version": "1.0.0",
"description": "GameBox - 跨平台游戏盒子",
"main": "./out/main/index.js",
"author": "erick",
"license": "MIT",
"scripts": {
"dev": "electron-vite dev",
"build": "electron-vite build",
"build:win": "npm run build && electron-builder --win",
"build:mac": "npm run build && electron-builder --mac",
"build:linux": "npm run build && electron-builder --linux",
"build:all": "npm run build && electron-builder --win --mac --linux"
},
"build": {
"appId": "com.erick.gamebox",
"productName": "GameBox",
"copyright": "Copyright © 2026 erick",
"directories": {
"output": "dist",
"buildResources": "build",
"outputVariation": "published"
},
"files": [
"out/**/*",
"!node_modules/**/*",
"package.json"
],
"extraResources": [
{
"from": "resources/",
"to": "resources/",
"filter": ["**/*"]
}
],
"win": {
"target": ["nsis"],
"icon": "build/icon.ico"
},
"mac": {
"target": ["dmg", "zip"],
"icon": "build/icon.icns",
"category": "public.app-category.games",
"hardenedRuntime": true,
"gatekeeperAssess": false
},
"linux": {
"target": ["AppImage", "deb"],
"icon": "resources/icon.png",
"category": "Game"
}
}
}
2.3 files 配置详解
{
"files": [
// 包含 out 目录下的所有文件
"out/**/*",
// 排除特定文件
"!out/**/*.map",
"!out/**/*.tmp",
// 包含特定文件
"package.json",
// 使用 glob 模式
"!**/*.ts",
"!**/*.map",
// 包含 node_modules 中的特定包
"!node_modules/**/*",
"node_modules/electron-store/**/*",
"node_modules/conf/**/*"
]
}
2.4 extraResources 配置
{
"extraResources": [
// 复制整个目录
{
"from": "resources/",
"to": "resources/",
"filter": ["**/*"]
},
// 复制单个文件
{
"from": "build/version.txt",
"to": "version.txt"
},
// 根据平台条件复制
{
"from": "build/",
"to": "build/",
"filter": ["**/*"],
"context": {
"electronPlatformName": "darwin"
}
}
]
}
3. 多平台打包
3.1 Windows 打包配置
{
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64", "ia32"]
},
{
"target": "msi",
"arch": ["x64"]
},
{
"target": "portable",
"arch": ["x64"]
}
],
"icon": "build/icon.ico",
"artifactName": "${productName}-${version}-${arch}-setup.${ext}",
"publisherName": "erick",
"requestedExecutionLevel": "asInvoker",
"applicationId": "GameBox"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "GameBox",
"installerIcon": "build/installer.ico",
"uninstallerIcon": "build/uninstaller.ico",
"installerHeaderIcon": "build/installer.ico",
"license": "LICENSE.txt",
"runAfterFinish": true,
"deleteAppDataOnUninstall": false,
"include": "build/installer.nsh"
},
"portable": {
"artifactName": "${productName}-${version}-portable.${ext}"
}
}
3.2 macOS 打包配置
{
"mac": {
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
},
{
"target": "zip",
"arch": ["x64", "arm64"]
},
{
"target": "pkg",
"arch": ["x64"]
}
],
"icon": "build/icon.icns",
"category": "public.app-category.games",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.plist",
"entitlementsInherit": "build/entitlements.plist",
"extendInfo": {
"NSHumanReadableCopyright": "Copyright © 2026 erick",
"NSPrincipalClass": "NSApplication"
},
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"dmg": {
"title": "${productName} ${version}",
"contents": [
{
"x": 130,
"y": 220,
"type": "file"
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
],
"window": {
"x": 100,
"y": 100,
"width": 540,
"height": 380
}
}
}
3.3 Linux 打包配置
{
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
},
{
"target": "rpm",
"arch": ["x64"]
}
],
"icon": "resources/icon.png",
"category": "Game",
"maintainer": "erick@example.com",
"vendor": "GameBox",
"synopsis": "A cross-platform game launcher",
"description": "GameBox is a cross-platform game launcher application",
"artifactName": "${productName}-${version}-${arch}.${ext}",
"executableName": "gamebox"
},
"deb": {
"depends": ["libgtk-3-0", "libnotify4", "libnss3"],
"recommends": ["libgnome-keyring0", "libasound2"]
},
"rpm": {
"depends": ["libgtk-3-0", "libnotify", "nss"],
"suggests": ["libgnome-keyring", "alsa-lib"]
}
}
3.4 跨平台打包命令
# Windows 打包
npm run build:win
# macOS 打包
npm run build:mac
# Linux 打包
npm run build:linux
# 全部平台打包
npm run build:all
# 指定架构
npx electron-builder --win --x64
npx electron-builder --mac --arm64
# 仅生成安装包(跳过应用包)
npx electron-builder --dir
# 查看所有可用选项
npx electron-builder --help
4. 安装包定制
4.1 NSIS 脚本定制
; build/installer.nsh
; NSIS 安装脚本自定义
!macro customHeader
; 自定义安装程序头部
!macroend
!macro preInit
; 安装前初始化
SetRegView 64
!macroend
!macro customInit
; 自定义初始化
!macroend
!macro customInstall
; 自定义安装逻辑
; 创建桌面快捷方式
CreateShortCut "$DESKTOP\GameBox.lnk" "$INSTDIR\GameBox.exe"
; 创建开始菜单快捷方式
CreateDirectory "$SMPROGRAMS\GameBox"
CreateShortCut "$SMPROGRAMS\GameBox\GameBox.lnk" "$INSTDIR\GameBox.exe"
CreateShortCut "$SMPROGRAMS\GameBox\卸载.lnk" "$INSTDIR\Uninstall GameBox.exe"
; 注册表配置
WriteRegStr HKCU "Software\GameBox" "" "$INSTDIR"
WriteRegStr HKCU "Software\GameBox" "Version" "${VERSION}"
; 关联文件类型(可选)
WriteRegStr HKCR ".gbx" "GameBox Game File" ""
WriteRegStr HKCR ".gbx\DefaultIcon" "" "$INSTDIR\GameBox.exe,0"
WriteRegStr HKCR ".gbx\shell\open\command" "" '"$INSTDIR\GameBox.exe" "%1"'
!macroend
!macro customUnInstall
; 自定义卸载逻辑
; 删除快捷方式
Delete "$DESKTOP\GameBox.lnk"
Delete "$SMPROGRAMS\GameBox\GameBox.lnk"
Delete "$SMPROGRAMS\GameBox\卸载.lnk"
RMDir "$SMPROGRAMS\GameBox"
; 清理注册表
DeleteRegKey HKCU "Software\GameBox"
DeleteRegKey HKCR ".gbx"
; 清理用户数据(可选)
RMDir /r "$LOCALAPPDATA\GameBox"
!macroend
4.2 macOS DMG 自定义
{
"dmg": {
"title": "${productName} ${version}",
"background": "build/dmg-background.png",
"icon": "build/dmg-icon.icns",
"iconSize": 100,
"iconTextSize": 12,
"window": {
"x": 100,
"y": 100,
"width": 600,
"height": 450
},
"contents": [
{
"x": 180,
"y": 170,
"type": "file",
"path": "dist/mac/GameBox.app"
},
{
"x": 410,
"y": 170,
"type": "link",
"path": "/Applications"
},
{
"x": 180,
"y": 360,
"type": "file",
"path": "../../LICENSE"
}
],
"sign": true
}
}
4.3 应用图标准备
# 创建图标目录
mkdir -p build resources
# 图标尺寸要求:
# Windows: .ico (256x256)
# macOS: .icns (16, 32, 64, 128, 256, 512, 1024)
# Linux: .png (最小 256x256)
# 使用工具转换图标(安装 imagemagick)
# convert source.png -resize 256x256 icon.png
# 生成多尺寸图标脚本
# icns 格式需要特殊处理,通常使用:
# - electron-icon-builder
# - icon-gen
4.4 图标生成工具
# 安装图标生成工具
npm install --save-dev electron-icon-builder
# 生成所有尺寸图标
npx electron-icon-builder --source=build/icon.png --output=build --flatten
// 或使用 JS 脚本
const { execSync } = require('child_process')
function generateIcons() {
const sizes = [16, 24, 32, 48, 64, 128, 256, 512, 1024]
sizes.forEach(size => {
console.log(`生成 ${size}x${size} 图标...`)
// 使用 sharp 或 jimp 处理
})
}
generateIcons()
5. 代码签名
5.1 Windows 代码签名
方式一:使用 Azure SignTool
# 安装 Azure SignTool
npm install --save-dev azure-signing-tool
# Windows 代码签名配置
{
"win": {
"sign": {
"tool": "azure-signing-tool",
" AzureVaultUrl": "https://your-keyvault.vault.azure.net",
"AzureClientId": "your-client-id",
"AzureClientSecret": "your-client-secret",
"AzureTenantId": "your-tenant-id",
"AzureCertificateName": "your-certificate-name",
"timestampServerUrl": "http://timestamp.digicert.com",
"digestAlgorithm": "sha256",
"description": "GameBox Application",
"descriptionUrl": "https://gamebox.example.com"
}
}
}
方式二:使用 osslsigncode
# 安装 osslsigncode(需要预装)
# 下载地址: https://github.com/mtrofin/osslsigncode/releases
# 签名脚本
const { execSync } = require('child_process')
function signWindowsExe(exePath, certPath, certPassword) {
const cmd = [
'osslsigncode',
'-ts', 'http://timestamp.digicert.com',
'-n', 'GameBox',
'-i', 'https://gamebox.example.com',
'-h', 'sha256',
'-pkcs12', certPath,
'-pass', certPassword,
'-in', exePath,
'-out', exePath.replace('.exe', '-signed.exe')
].join(' ')
execSync(cmd, { stdio: 'inherit' })
}
5.2 macOS 代码签名
签名配置文件
<!-- build/entitlements.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/>
<!-- 网络访问 -->
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- 文件访问 -->
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<!-- 游戏手柄 -->
<key>com.apple.security.game-center</key>
<true/>
</dict>
</plist>
签名配置
{
"mac": {
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.plist",
"entitlementsInherit": "build/entitlements.plist",
"sign": {
"tool": "electron-builder-sodium",
"identity": "Developer ID Application: Your Name (TEAM_ID)",
"keychain": "~/Library/Keychains/login.keychain-db",
"timestampServerUrl": "http://timestamp.apple.com/ts0416",
"digestAlgorithm": "sha256"
}
}
}
手动签名命令
# 1. 设置钥匙串访问
security unlock-keychain ~/Library/Keychains/login.keychain-db
# 2. 签名应用
codesign --force --deep --sign "Developer ID Application: Your Name (TEAM_ID)" \
--entitlements build/entitlements.plist \
--options runtime \
"dist/mac/GameBox.app"
# 3. 验证签名
codesign --verify --verbose=2 "dist/mac/GameBox.app"
spctl --assess --type exec --verbose=4 "dist/mac/GameBox.app"
# 4. 创建 DMG 并签名
hdiutil create -volname "GameBox" -srcfolder "dist/mac/GameBox.app" -ov -format UDZO "GameBox.dmg"
# 5. 签名 DMG
codesign --sign "Developer ID Application: Your Name (TEAM_ID)" "GameBox.dmg"
5.3 Linux 代码签名
# Linux 应用通常不需要代码签名
# 但可以添加 GPG 签名
# 创建 GPG 密钥
gpg --full-generate-key
# 导出签名密钥
gpg --armor --export your@email.com > public.key
# 签名打包文件
gpg --armor --detach-sign GameBox-1.0.0.AppImage
6. 自动更新机制
6.1 更新架构
┌─────────────────────────────────────────────────────────────┐
│ 自动更新流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 检查更新 │
│ ┌──────────┐ ┌─────────────┐ │
│ │ 客户端 │────→│ 更新服务器 │ │
│ │ 检测版本 │←────│ 返回新版本 │ │
│ └──────────┘ └─────────────┘ │
│ │
│ 2. 下载更新 │
│ ┌──────────┐ ┌─────────────┐ │
│ │ 客户端 │────→│ GitHub Releases│ │
│ │ 下载包 │←────│ 下载文件 │ │
│ └──────────┘ └─────────────┘ │
│ │
│ 3. 安装更新 │
│ ┌──────────┐ │
│ │ 客户端 │ │
│ │ 安装重启 │ │
│ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
6.2 安装 electron-updater
# 安装更新依赖
npm install electron-updater --save
npm install electron-log --save
6.3 主进程更新配置
// src/main/update.ts
import { autoUpdater } from 'electron-updater'
import { BrowserWindow, ipcMain, dialog } from 'electron'
import log from 'electron-log'
// 配置日志
autoUpdater.logger = log
log.transports.file.level = 'info'
// 自动更新配置
const updateConfig = {
// 允许预发布版本
allowPreRelease: false,
// 自动下载(false=手动触发下载)
autoDownload: false,
// 下载重试次数
downloadRetryCount: 3,
// 下载超时(毫秒)
downloadTimeout: 30000,
// 代理设置
proxy: undefined
}
export function setupAutoUpdater(mainWindow: BrowserWindow): void {
// 应用更新配置
autoUpdater.autoDownload = updateConfig.autoDownload
autoUpdater.allowPreRelease = updateConfig.allowPreRelease
// 监听进度
autoUpdater.on('checking-for-update', () => {
log.info('🔍 检查更新中...')
mainWindow.webContents.send('update-status', {
status: 'checking',
message: '检查更新中...'
})
})
// 发现新版本
autoUpdater.on('update-available', (info) => {
log.info('🎉 发现新版本:', info.version)
mainWindow.webContents.send('update-status', {
status: 'available',
message: `发现新版本 ${info.version}`,
version: info.version,
releaseDate: info.releaseDate,
releaseNotes: info.releaseNotes
})
})
// 没有可用更新
autoUpdater.on('update-not-available', (info) => {
log.info('✅ 当前已是最新版本')
mainWindow.webContents.send('update-status', {
status: 'not-available',
message: '当前已是最新版本'
})
})
// 下载进度
autoUpdater.on('download-progress', (progressObj) => {
const percent = Math.round(progressObj.percent)
log.info(`📥 下载进度: ${percent}%`)
mainWindow.webContents.send('update-status', {
status: 'downloading',
message: `下载中... ${percent}%`,
percent
})
})
// 下载完成
autoUpdater.on('update-downloaded', (info) => {
log.info('✅ 下载完成:', info.version)
mainWindow.webContents.send('update-status', {
status: 'downloaded',
message: '下载完成,可以安装更新',
version: info.version
})
// 提示用户安装
dialog.showMessageBox(mainWindow, {
type: 'info',
title: '更新下载完成',
message: `新版本 ${info.version} 已下载完成`,
detail: '是否立即安装更新并重启应用?',
buttons: ['立即重启', '稍后再说'],
defaultId: 0,
cancelId: 1
}).then(result => {
if (result.response === 0) {
autoUpdater.quitAndInstall()
}
})
})
// 更新错误
autoUpdater.on('error', (error) => {
log.error('❌ 更新错误:', error)
mainWindow.webContents.send('update-status', {
status: 'error',
message: `更新失败: ${error.message}`
})
})
// IPC 处理程序
ipcMain.handle('check-for-updates', async () => {
try {
const result = await autoUpdater.checkForUpdates()
return result
} catch (error) {
log.error('检查更新失败:', error)
return null
}
})
ipcMain.handle('download-update', async () => {
try {
const result = await autoUpdater.downloadUpdate()
return result
} catch (error) {
log.error('下载更新失败:', error)
return null
}
})
ipcMain.handle('install-update', () => {
autoUpdater.quitAndInstall()
})
}
6.4 渲染进程更新组件
<!-- src/renderer/src/components/UpdateDialog.vue -->
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const updateStatus = ref<'checking' | 'available' | 'downloading' | 'downloaded' | 'error' | 'idle'>('idle')
const updateMessage = ref('')
const downloadPercent = ref(0)
const newVersion = ref('')
const showDialog = ref(false)
onMounted(() => {
// 监听更新状态
window.electron.on('update-status', (status: any) => {
updateStatus.value = status.status
updateMessage.value = status.message
if (status.percent !== undefined) {
downloadPercent.value = status.percent
}
if (status.version) {
newVersion.value = status.version
}
if (status.status === 'available' || status.status === 'downloaded') {
showDialog.value = true
}
})
})
onUnmounted(() => {
window.electron.removeAllListeners('update-status')
})
async function checkUpdate() {
updateStatus.value = 'checking'
updateMessage.value = '检查更新中...'
await window.electron.checkForUpdates()
}
async function downloadUpdate() {
await window.electron.downloadUpdate()
}
function installUpdate() {
window.electron.installUpdate()
}
function dismissDialog() {
showDialog.value = false
}
</script>
<template>
<div class="update-dialog" v-if="showDialog">
<div class="dialog-overlay" @click="dismissDialog"></div>
<div class="dialog-content">
<div class="dialog-header">
<h3>🎉 发现新版本</h3>
</div>
<div class="dialog-body">
<template v-if="updateStatus === 'available'">
<p>新版本 <strong>{{ newVersion }}</strong> 已发布</p>
<p class="desc">{{ updateMessage }}</p>
<button class="btn-primary" @click="downloadUpdate">
立即下载
</button>
</template>
<template v-else-if="updateStatus === 'downloading'">
<p>正在下载更新...</p>
<div class="progress-bar">
<div
class="progress-fill"
:style="{ width: downloadPercent + '%' }"
></div>
</div>
<p class="percent">{{ downloadPercent }}%</p>
</template>
<template v-else-if="updateStatus === 'downloaded'">
<p>✅ 更新已下载完成</p>
<p class="desc">{{ updateMessage }}</p>
<button class="btn-primary" @click="installUpdate">
立即重启安装
</button>
</template>
<template v-else-if="updateStatus === 'error'">
<p class="error">❌ {{ updateMessage }}</p>
<button class="btn-secondary" @click="dismissDialog">
关闭
</button>
</template>
</div>
</div>
</div>
</template>
<style scoped>
.update-dialog {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.dialog-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.6);
}
.dialog-content {
position: relative;
background: #1e1e2f;
border-radius: 12px;
padding: 24px;
min-width: 320px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
}
.dialog-header h3 {
margin: 0 0 16px;
font-size: 20px;
color: #fff;
}
.dialog-body {
text-align: center;
}
.dialog-body p {
margin: 0 0 16px;
color: #a0a0a0;
}
.dialog-body .desc {
font-size: 14px;
color: #666;
}
.progress-bar {
height: 8px;
background: #333;
border-radius: 4px;
overflow: hidden;
margin: 16px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
transition: width 0.3s;
}
.percent {
font-size: 24px;
font-weight: bold;
color: #667eea;
}
.error {
color: #e74c3c;
}
.btn-primary {
width: 100%;
padding: 12px 24px;
background: linear-gradient(135deg, #667eea, #764ba2);
border: none;
border-radius: 8px;
color: #fff;
font-size: 16px;
cursor: pointer;
transition: opacity 0.3s;
}
.btn-primary:hover {
opacity: 0.9;
}
.btn-secondary {
width: 100%;
padding: 12px 24px;
background: #333;
border: none;
border-radius: 8px;
color: #fff;
font-size: 16px;
cursor: pointer;
}
</style>
6.5 GitHub Releases 配置
# .github/workflows/release.yml
name: Build and Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
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
- name: Build
run: npm run build
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build and release
uses: electron-builder/action@v1
with:
args: --publish always
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}-build
path: dist/
7. CI/CD 集成
7.1 GitHub Actions 工作流
# .github/workflows/build.yml
name: Electron Build
on:
push:
branches: [main]
tags:
- 'v*'
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run lint
build-windows:
needs: lint
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build:win
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: windows-build
path: dist/
build-mac:
needs: lint
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build:mac
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: mac-build
path: dist/
build-linux:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build:linux
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: linux-build
path: dist/
release:
needs: [build-windows, build-mac, build-linux]
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: dist/
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: dist/**/*.exe
files: dist/**/*.dmg
files: dist/**/*.AppImage
files: dist/**/*.deb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7.2 GitLab CI/CD
# .gitlab-ci.yml
stages:
- lint
- build
- release
variables:
NODE_VERSION: "20"
lint:
stage: lint
image: node:${NODE_VERSION}
script:
- npm ci
- npm run lint
only:
- main
- merge_requests
build:windows:
stage: build
image: electronuserland/builder:wine
script:
- npm ci
- npm run build:win
artifacts:
paths:
- dist/
only:
- main
- tags
build:mac:
stage: build
image: electronuserland/builder:mac
script:
- npm ci
- npm run build:mac
artifacts:
paths:
- dist/
only:
- main
- tags
build:linux:
stage: build
image: electronuserland/builder
script:
- npm ci
- npm run build:linux
artifacts:
paths:
- dist/
only:
- main
- tags
7.3 Jenkins 流水线
// Jenkinsfile
pipeline {
agent any
environment {
NODE_VERSION = '20'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install') {
steps {
bat 'npm ci'
}
}
stage('Lint') {
steps {
bat 'npm run lint'
}
}
stage('Build') {
parallel {
stage('Windows') {
steps {
bat 'npm run build:win'
archiveArtifacts artifacts: 'dist/**/*.exe', fingerprint: true
}
}
stage('macOS') {
when { not { equals expected: 'windows', actual: env.AGENT_OS } }
steps {
sh 'npm run build:mac'
archiveArtifacts artifacts: 'dist/**/*.dmg', fingerprint: true
}
}
stage('Linux') {
when { not { equals expected: 'windows', actual: env.AGENT_OS } }
steps {
sh 'npm run build:linux'
archiveArtifacts artifacts: 'dist/**/*.AppImage', fingerprint: true
}
}
}
}
}
post {
always {
cleanWs()
}
}
}
8. 实战:GameBox 完整打包配置
8.1 完整 package.json
{
"name": "gamebox-app",
"version": "1.0.0",
"description": "GameBox - 跨平台游戏盒子",
"main": "./out/main/index.js",
"author": {
"name": "erick",
"email": "erick@example.com"
},
"license": "MIT",
"scripts": {
"dev": "electron-vite dev",
"build": "electron-vite build",
"preview": "electron-vite preview",
"postinstall": "electron-builder install-app-deps",
"build:win": "npm run build && electron-builder --win",
"build:mac": "npm run build && electron-builder --mac",
"build:linux": "npm run build && electron-builder --linux",
"build:all": "npm run build && electron-builder --win --mac --linux",
"dist": "npm run build && electron-builder",
"dist:dir": "npm run build && electron-builder --dir",
"sign:mac": "electron-builder-sodium",
"sign:win": "azure-signing-tool sign"
},
"dependencies": {
"@vueuse/core": "^10.7.0",
"electron-log": "^5.0.0",
"electron-store": "^8.1.0",
"electron-updater": "^6.1.0",
"pinia": "^2.1.7",
"vue": "^3.4.0",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@electron-toolkit/preload": "^3.0.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@electron-toolkit/utils": "^3.0.0",
"@types/node": "^20.10.0",
"@vitejs/plugin-vue": "^5.0.0",
"electron": "^28.0.0",
"electron-builder": "^24.9.1",
"electron-icon-builder": "^2.0.1",
"electron-vite": "^2.0.0",
"typescript": "^5.3.0",
"vite": "^5.0.0",
"vue-tsc": "^1.8.25"
},
"build": {
"appId": "com.erick.gamebox",
"productName": "GameBox",
"copyright": "Copyright © 2026 erick",
"asar": true,
"asarUnpack": [
"resources/**"
],
"directories": {
"output": "release/${version}",
"buildResources": "build",
"app": "out"
},
"files": [
"out/**/*",
"!out/**/*.map",
"package.json"
],
"extraResources": [
{
"from": "resources/",
"to": "resources/",
"filter": ["**/*"]
}
],
"publish": [
{
"provider": "github",
"owner": "your-username",
"repo": "gamebox-app"
}
],
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64"]
},
{
"target": "portable",
"arch": ["x64"]
}
],
"icon": "build/icon.ico",
"artifactName": "${productName}-${version}-${arch}-setup.${ext}",
"publisherName": "erick"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "GameBox",
"license": "LICENSE.txt",
"include": "build/installer.nsh"
},
"portable": {
"artifactName": "${productName}-${version}-portable.${ext}"
},
"mac": {
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
},
{
"target": "zip",
"arch": ["x64", "arm64"]
}
],
"icon": "build/icon.icns",
"category": "public.app-category.games",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.plist",
"entitlementsInherit": "build/entitlements.plist",
"artifactName": "${productName}-${version}-${arch}.${ext}",
"extendInfo": {
"NSHumanReadableCopyright": "Copyright © 2026 erick"
}
},
"dmg": {
"title": "${productName} ${version}",
"contents": [
{
"x": 130,
"y": 220,
"type": "file"
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
]
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
}
],
"icon": "resources/icon.png",
"category": "Game",
"maintainer": "erick@example.com",
"vendor": "GameBox",
"artifactName": "${productName}-${version}-${arch}.${ext}",
"executableName": "gamebox"
}
}
}
8.2 版本管理脚本
// scripts/version.js
const { execSync } = require('child_process')
const fs = require('fs')
const path = require('path')
// 获取当前版本
function getCurrentVersion() {
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'))
return pkg.version
}
// 更新版本
function bumpVersion(type = 'patch') {
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'))
const [major, minor, patch] = pkg.version.split('.').map(Number)
let newVersion
switch (type) {
case 'major':
newVersion = `${major + 1}.0.0`
break
case 'minor':
newVersion = `${major}.${minor + 1}.0`
break
case 'patch':
default:
newVersion = `${major}.${minor}.${patch + 1}`
}
pkg.version = newVersion
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n')
console.log(`版本更新: ${pkg.version} → ${newVersion}`)
return newVersion
}
// 创建 Git tag
function createTag(version) {
try {
execSync(`git tag v${version}`, { stdio: 'inherit' })
console.log(`✅ Git tag v${version} 已创建`)
} catch (error) {
console.error(`❌ 创建 tag 失败: ${error.message}`)
}
}
// 主流程
const args = process.argv.slice(2)
const type = args[0] || 'patch'
const newVersion = bumpVersion(type)
createTag(newVersion)
# 使用脚本
node scripts/version.js patch # 1.0.0 -> 1.0.1
node scripts/version.js minor # 1.0.0 -> 1.1.0
node scripts/version.js major # 1.0.0 -> 2.0.0
# 发布
git push origin main --tags
9. 排错指南
9.1 常见错误与解决方案
| 错误类型 | 错误信息 | 原因 | 解决方案 |
|---|
| 依赖错误 | Failed to run install script | native 模块编译失败 | npm install --ignore-scripts 后手动编译 |
| 图标缺失 | icon.ico not found | 图标路径错误 | 检查 buildResources 配置 |
| ASAR 错误 | Cannot read property of undefined | 资源未正确打包 | 添加 asarUnpack 配置 |
| 代码签名失败 | sign failed with exit code 1 | 证书无效/过期 | 检查证书有效期 |
| 权限错误 | EACCES: permission denied | 文件权限问题 | 使用管理员权限运行 |
| 路径错误 | Cannot find module | 打包路径问题 | 检查 files 配置 |
| NSIS 错误 | Aborting creation of installer | 脚本语法错误 | 检查 .nsh 文件语法 |
9.2 Windows 常见问题
# 问题:缺少 Visual C++ 运行时
# 解决:在 NSIS 配置中添加依赖
{
"win": {
"target": "nsis"
},
"nsis": {
"include": "build/installer.nsh"
}
}
# build/installer.nsh
!include "WinMessages.nsh"
!include "FileFunc.nsh"
!macro customInstall
; 安装 VC++ 运行库
DetailPrint "Installing Visual C++ Runtime..."
ExecWait '"$TEMP\vcruntime140_amd64.exe /install /quiet /norestart"'
!macroend
# 问题:Windows Defender 阻止
# 解决:代码签名 + 白名单申请
# 问题:用户权限不足
# 解决:设置 requestedExecutionLevel
{
"win": {
"requestedExecutionLevel": "requireAdministrator"
}
}
9.3 macOS 常见问题
# 问题:Gatekeeper 阻止
# 解决:使用有效开发者签名
# 问题:应用崩溃 on macOS 13+
# 解决:添加 entitlements
{
"mac": {
"hardenedRuntime": true,
"entitlements": "build/entitlements.plist"
}
}
# 问题:DMG 签名失败
# 解决:先签名应用,再签名 DMG
codesign --sign "Developer ID" "GameBox.app"
hdiutil create -srcfolder "GameBox.app" "GameBox.dmg"
codesign --sign "Developer ID" "GameBox.dmg"
9.4 Linux 常见问题
# 问题:AppImage 无法运行
# 解决:确保可执行权限
chmod +x GameBox-*.AppImage
# 问题:依赖库缺失
# 解决:检查依赖
ldd GameBox
# 或使用 appimage-relate-tool
appimageextract GameBox.AppImage
9.5 更新相关问题
// 问题:更新检测失败
// 解决:检查网络和发布配置
// 1. 检查 publish 配置
{
"build": {
"publish": [
{
"provider": "github",
"owner": "your-username",
"repo": "your-repo"
}
]
}
}
// 2. 检查 GitHub Token 权限
// Settings -> Developer settings -> Personal access tokens
// 需要 repo:status 和 public_repo 权限
// 3. 添加调试日志
autoUpdater.logger = log
log.transports.file.level = 'debug'
9.6 构建诊断脚本
#!/bin/bash
# scripts/diagnose.sh - 构建诊断脚本
echo "======================================"
echo " Electron 构建环境诊断"
echo "======================================"
echo ""
echo "📋 系统信息:"
echo " 操作系统: $(uname -s)"
echo " 架构: $(uname -m)"
echo " Node 版本: $(node --version)"
echo " npm 版本: $(npm --version)"
echo ""
echo "📦 全局安装的工具:"
npm list -g --depth=0 2>/dev/null || echo " (无)"
echo ""
echo "🔍 electron-builder 版本:"
npx electron-builder --version
echo ""
echo "🖥️ Electron 版本:"
npm list electron
echo ""
echo "📂 项目文件检查:"
echo " package.json: $([ -f package.json ] && echo '✅' || echo '❌')"
echo " electron.vite.config.ts: $([ -f electron.vite.config.ts ] && echo '✅' || echo '❌')"
echo " build/icon.ico: $([ -f build/icon.ico ] && echo '✅' || echo '❌')"
echo " build/icon.icns: $([ -f build/icon.icns ] && echo '✅' || echo '❌')"
echo ""
echo "💾 磁盘空间:"
df -h . | tail -1
echo ""
echo "======================================"
echo " 诊断完成"
echo "======================================"
10. 最佳实践
10.1 打包最佳实践清单
✅ 必须:
- [ ] 使用 semver 规范版本号
- [ ] 配置 appId(反向域名格式)
- [ ] 准备各平台图标(256x256+)
- [ ] 设置合理的 files 配置
- [ ] 测试所有平台安装包
- [ ] 配置日志记录
- [ ] 处理未捕获异常
✅ 推荐:
- [ ] 启用 ASAR 打包
- [ ] 代码签名(生产环境)
- [ ] 自动更新机制
- [ ] CI/CD 自动化构建
- [ ] 构建产物校验(SHA256)
- [ ] 详细的构建日志
✅ 避免:
- [ ] 硬编码路径
- [ ] 打包不必要的文件
- [ ] 忽略代码签名
- [ ] 手动发布 Release
10.2 安全最佳实践
{
"build": {
"asar": true,
"afterSign": "scripts/notarize.js",
"win": {
"sign": {
"tool": "azure-signing-tool",
"timestampServerUrl": "http://timestamp.digicert.com"
}
},
"mac": {
"hardenedRuntime": true,
"gatekeeperAssess": true
}
}
}
10.3 性能优化
{
"build": {
"compression": "maximum",
"electronDist": "node_modules/electron/dist",
"electronVersion": "^28.0.0",
"directories": {
"output": "release/${version}",
"buildResources": "build"
},
"files": [
"out/**/*",
"!out/**/*.map"
],
"nsis": {
"compression": "lzma"
}
}
}
10.4 产物命名规范
{
"win": {
"artifactName": "${productName}-${version}-${arch}-setup.${ext}"
},
"mac": {
"artifactName": "${productName}-${version}-${arch}.${ext}"
},
"linux": {
"artifactName": "${productName}-${version}-${arch}.${ext}"
}
}
10.5 版本发布流程
┌─────────────────────────────────────────────────────────────┐
│ 版本发布流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 开发完成 │
│ ├── 代码审查 │
│ ├── 测试验证 │
│ └── 更新 CHANGELOG.md │
│ │
│ 2. 版本升级 │
│ ├── 更新版本号 │
│ ├── 创建 Git tag │
│ └── 推送到远程仓库 │
│ │
│ 3. CI/CD 构建 │
│ ├── GitHub Actions 自动构建 │
│ ├── 多平台打包 │
│ └── 上传构建产物 │
│ │
│ 4. 发布 Release │
│ ├── 创建 GitHub Release │
│ ├── 上传安装包 │
│ ├── 添加发布说明 │
│ └── 自动更新推送 │
│ │
│ 5. 通知用户 │
│ ├── 更新博客 │
│ └── 社区通知 │
│ │
└─────────────────────────────────────────────────────────────┘
📚 总结
核心知识点回顾
| 模块 | 核心技能 |
|---|
| 配置详解 | files/extraResources/publish 配置 |
| 多平台打包 | Windows/macOS/Linux 各平台配置 |
| 安装包定制 | NSIS/DMG 自定义脚本 |
| 代码签名 | Windows/macOS/Linux 签名方案 |
| 自动更新 | electron-updater 完整实现 |
| CI/CD | GitHub Actions/GitLab/Jenkins |
| 排错指南 | 常见错误与解决方案 |
延伸学习方向
- electron-forge – 官方推荐的替代打包工具
- appimage-builder – 高级 AppImage 定制
- Codesign 自动化 – 完整的签名流水线
- 差量更新 – 使用 electron-builder 的差量更新功能
- 多架构支持 – arm64/mips 等其他架构
暂无评论内容