Electron 跨平台移植实战:从 Windows 到 macOS 的适配与 DMG 打包全记录
Electron 跨平台移植实战:从 Windows 到 macOS 的适配与 DMG 打包全记录
作者:tenxiaodao.top
本文详细记录了将 Windows 版 Electron 应用 Mineradio 适配到 macOS 并打包为 .dmg 安装包的完整过程,涵盖全屏功能修复、图标生成、手动打包等关键环节,附带常见踩坑经验。
写在前面
最近把一个基于 Electron 的沉浸式音乐播放器 Mineradio 从 Windows 搬到了 macOS 上,整个过程比预想中曲折一些——主要是 macOS 上transparent + frameless窗口的全屏限制,以及 electron-builder 在国内网络环境下的 DMG 打包问题。这篇文章把踩过的坑和解决方案完整记录下来,希望对正在做 Electron 跨平台适配的朋友有帮助。
我的个人网站 tenxiaodao.top 上会持续更新更多技术实践,欢迎访问。
1. 项目分析
Mineradio 是一款基于Electron的沉浸式音乐播放器,原始版本仅提供 Windows NSIS 安装包。
核心文件结构
Mineradio-1.0.10/ ├── desktop/ │ ├── main.js # Electron 主进程入口 │ ├── preload.js # 预加载脚本 │ └── overlay-preload.js # 覆盖层预加载脚本 ├── build/ │ ├── icon.ico # Windows 图标 │ ├── icon.png # PNG 图标(可用于生成 macOS 图标) │ └── installer.nsh # NSIS 安装脚本 ├── public/ # 前端静态资源 ├── server.js # 本地 HTTP 服务器 ├── dj-analyzer.js # 节奏分析模块 └── package.json # 项目配置为什么可以在 macOS 上运行
Electron 本身是跨平台框架(Chromium + Node.js),所以只要项目结构是标准 Electron 架构,搬到 macOS 上跑就有先天基础:
package.json中"main": "desktop/main.js"是标准 Electron 入口,与平台无关- 代码中已有平台判断逻辑,例如
main.js中的if (process.platform !== 'darwin') app.quit() build/icon.png可以直接用来生成 macOS 所需的.icns图标
2. 环境准备
检查 Node.js 版本
node--version# 需要 v18+npm--version# 需要 v8+如果没有安装 Node.js,前往 nodejs.org 下载 LTS 版本。
设置国内镜像(加速依赖下载)
国内网络环境下,Electron 二进制文件和 npm 包下载可能很慢,建议配置镜像加速:
# npm 镜像(可选)npmconfigsetregistry https://registry.npmmirror.com# Electron 二进制镜像(必须,否则下载会卡住)exportELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"注意:每次打开新终端都需要重新执行
export,建议将其写入 shell 配置文件:echo'export ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"'>>~/.zshrc
3. 安装依赖
cd~/Downloads/Mineradio-1.0.10# 确保 Electron 镜像已设置exportELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"# 安装所有依赖(包括 Electron)npminstall验证安装
# 检查 Electron 二进制是否下载完成catnode_modules/electron/path.txt# 应该输出类似:node_modules/electron/dist/Electron.applsnode_modules/electron/dist/Electron.app# 应该存在 Electron.app 目录快速验证
npmstart如果一切正常,Mineradio 窗口将会弹出。
4. macOS 全屏功能修复
问题描述
macOS 上 Electron 窗口如果同时设置了transparent: true和frame: false,调用win.setFullScreen(true)会静默失败,窗口无法进入全屏。这是 Electron 在 macOS 上的一个已知限制。
Mineradio 的主窗口创建代码如下:
mainWindow=newBrowserWindow({...initialBounds,frame:false,// 无边框窗口transparent:true,// 透明背景// ...});这两个属性的组合导致原生setFullScreen()在 macOS 上失效。
解决方案
使用setSimpleFullScreen()替代setFullScreen()。setSimpleFullScreen不依赖 macOS 原生全屏动画,可以绕过透明无边框窗口的限制。
具体修改
1.toggleFullscreen函数
为 macOS 单独走setSimpleFullScreen逻辑:
functiontoggleFullscreen(win){if(!win||win.isDestroyed())return;if(win.isFullScreen()||windowFullscreenActive){exitFullscreenToWindow(win);return;}windowFullscreenActive=true;if(process.platform==='darwin'){// macOS: transparent frameless windows 无法使用原生 setFullScreenwin.setSimpleFullScreen(true);// 手动扩展窗口覆盖整个屏幕constdisplay=screen.getDisplayMatching(win.getBounds());win.setBounds(display.bounds,false);}else{win.setFullScreen(true);}sendWindowState(win);}2.exitFullscreenToWindow函数
退出时对应使用setSimpleFullScreen(false):
functionexitFullscreenToWindow(win){if(!win||win.isDestroyed())return;windowFullscreenActive=false;if(process.platform==='darwin'&&win.isSimpleFullScreen()){win.setSimpleFullScreen(false);applyWindowedBounds(win);return;}if(!win.isFullScreen()){applyWindowedBounds(win);return;}letapplied=false;constapplyOnce=()=>{if(applied||!win||win.isDestroyed()||win.isFullScreen())return;applied=true;applyWindowedBounds(win);};win.once('leave-full-screen',()=>setTimeout(applyOnce,50));win.setFullScreen(false);setTimeout(applyOnce,500);}3. Escape 键退出全屏
mainWindow.webContents.on('before-input-event',(event,input)=>{if(input.type==='keyDown'&&(input.key==='Escape'||input.code==='Escape')){constisMacFullScreen=process.platform==='darwin'&&(windowFullscreenActive||mainWindow.isSimpleFullScreen());if(mainWindow.isFullScreen()||isMacFullScreen){event.preventDefault();exitFullscreenToWindow(mainWindow);}}});原代码只判断mainWindow.isFullScreen(),macOS 上使用simpleFullScreen时该值为false,需要额外判断windowFullscreenActive和isSimpleFullScreen()。
4. HTML 全屏退出时避免冲突
mainWindow.on('leave-html-full-screen',()=>{htmlFullscreenActive=false;if(process.platform!=='darwin'||!windowFullscreenActive){setTimeout(()=>applyWindowedBounds(mainWindow),50);}});macOS 上如果处于simpleFullScreen状态,HTML 全屏退出时不应重置窗口尺寸,否则会与简单全屏冲突。
验证
npmstart启动后点击全屏按钮,确认窗口能正常进入和退出全屏。
5. 打包为 .dmg
生成 macOS 图标
macOS 应用需要.icns格式的图标,可以使用系统自带的iconutil工具从 PNG 生成:
cd~/Downloads/Mineradio-1.0.10# 创建 iconset 目录mkdir-pbuild/icon.iconset# 使用 sips 生成所有需要的尺寸sips-z1616build/icon.png--outbuild/icon.iconset/icon_16x16.png sips-z3232build/icon.png--outbuild/icon.iconset/icon_16x16@2x.png sips-z3232build/icon.png--outbuild/icon.iconset/icon_32x32.png sips-z6464build/icon.png--outbuild/icon.iconset/icon_32x32@2x.png sips-z128128build/icon.png--outbuild/icon.iconset/icon_128x128.png sips-z256256build/icon.png--outbuild/icon.iconset/icon_128x128@2x.png sips-z256256build/icon.png--outbuild/icon.iconset/icon_256x256.png sips-z512512build/icon.png--outbuild/icon.iconset/icon_256x256@2x.png sips-z512512build/icon.png--outbuild/icon.iconset/icon_512x512.png sips-z10241024build/icon.png--outbuild/icon.iconset/icon_512x512@2x.png# 转换为 .icnsiconutil-cicns build/icon.iconset-obuild/icon.icns# 验证ls-labuild/icon.icns配置 package.json
在package.json中添加 macOS 构建脚本和配置:
"scripts":{"start":"electron .","build:win":"electron-builder --win nsis","build:win:dir":"electron-builder --win dir","build:mac":"electron-builder --mac dmg","build:mac:dir":"electron-builder --mac dir"}在build字段中添加mac和dmg配置:
"mac":{"executableName":"Mineradio","icon":"build/icon.icns","category":"public.app-category.music","hardenedRuntime":true,"gatekeeperAssess":false,"target":[{"target":"dmg","arch":["arm64","x64"]}]},"dmg":{"title":"${productName} ${version}","icon":"build/icon.icns","contents":[{"x":130,"y":220},{"x":410,"y":220,"type":"link","path":"/Applications"}]}配置要点:
| 字段 | 说明 |
|---|---|
executableName | 可执行文件名 |
icon | macOS 图标路径(.icns 格式) |
category | App Store 分类,音乐类为public.app-category.music |
hardenedRuntime | macOS 安全特性 |
arch | 同时支持 Apple Silicon(arm64)和 Intel(x64) |
执行打包
cd~/Downloads/Mineradio-1.0.10exportELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"npmrun build:mac打包过程中 electron-builder 会:
- 下载 macOS 版 Electron 运行时(约 113MB)
- 将代码和运行时组合为
.app包 - 执行 ad-hoc 签名(无需 Apple 开发者账号)
- 生成
.dmg安装包
如果 electron-builder DMG 制作失败
国内网络环境下,electron-builder 的dmg-builder工具可能下载失败(404 错误)。这时可以用分步方案:
第一步:只打包 .app
npx electron-builder--macdir在dist/mac-arm64/下生成Mineradio.app。
第二步:使用 hdiutil 手动创建 DMG
cd~/Downloads/Mineradio-1.0.10/dist# 创建临时目录mkdir-pdmg_temp# 复制 .appcp-Rmac-arm64/Mineradio.app dmg_temp/# 创建 Applications 快捷方式ln-s/Applications dmg_temp/Applications# 生成 DMGhdiutil create-volname"Mineradio 1.0.10"\-srcfolderdmg_temp\-ov-formatUDZO\Mineradio-1.0.10-arm64.dmg# 清理临时文件rm-rfdmg_temp参数说明:
| 参数 | 说明 |
|---|---|
-volname | DMG 挂载后显示的卷名 |
-srcfolder | 源文件夹路径 |
-ov | 覆盖已有文件 |
-format UDZO | zlib 压缩格式 |
最终产物位于dist/Mineradio-1.0.10-arm64.dmg,约 141MB。
6. 安装与使用
安装步骤
- 双击
Mineradio-1.0.10-arm64.dmg - 将Mineradio拖到Applications文件夹
- 等待复制完成
首次启动注意事项
macOS 会提示"无法验证开发者",因为应用没有经过 Apple 公证签名。解决方法:
方案一:右键打开
右键点击 Mineradio.app → 选择「打开」→ 在弹出的对话框中再次点击「打开」
方案二:系统设置
系统偏好设置 → 隐私与安全性 → 找到被阻止的 Mineradio → 点击「仍要打开」
首次确认后,后续启动不再提示,也可以使用 Spotlight 搜索 “Mineradio” 快速启动。
7. macOS 上的功能限制
| 功能 | 限制原因 | 影响说明 |
|---|---|---|
| 桌面壁纸模式 | 使用 WindowsWorkerWAPI(user32.dll) | 无法将播放器嵌入桌面壁纸层 |
| 桌面歌词鼠标穿透 | 依赖 WindowsGetAsyncKeyState | 中键切换锁定/解锁不可用 |
| 桌面快捷方式自动创建 | 使用 Windows.lnk文件 | 无影响,拖拽到 Applications 即可 |
正常运行的功能
- 音乐播放、搜索、歌单管理
- 网易云音乐登录与播放
- QQ 音乐登录与音源补充
- 歌词显示
- 全屏模式(已修复)
- 粒子视觉 / Emily 播放态
- 3D 歌单架
- 天气电台
- 更新检测
附录:完整修改文件清单
| 文件 | 修改内容 |
|---|---|
desktop/main.js | macOS 全屏逻辑修复(4 处修改) |
package.json | 添加build:mac/build:mac:dir脚本和mac/dmg构建配置 |
build/icon.icns | 新增,从icon.png转换生成的 macOS 图标 |
本文发布于 tenxiaodao.top,更多技术实践欢迎关注。