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# 需要 v18npm--version# 需要 v8如果没有安装 Node.js前往 nodejs.org 下载 LTS 版本。设置国内镜像加速依赖下载国内网络环境下Electron 二进制文件和 npm 包下载可能很慢建议配置镜像加速# npm 镜像可选npmconfigsetregistry https://registry.npmmirror.com# Electron 二进制镜像必须否则下载会卡住exportELECTRON_MIRRORhttps://npmmirror.com/mirrors/electron/注意每次打开新终端都需要重新执行export建议将其写入 shell 配置文件echoexport ELECTRON_MIRRORhttps://npmmirror.com/mirrors/electron/~/.zshrc3. 安装依赖cd~/Downloads/Mineradio-1.0.10# 确保 Electron 镜像已设置exportELECTRON_MIRRORhttps://npmmirror.com/mirrors/electron/# 安装所有依赖包括 Electronnpminstall验证安装# 检查 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 的主窗口创建代码如下mainWindownewBrowserWindow({...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;}windowFullscreenActivetrue;if(process.platformdarwin){// macOS: transparent frameless windows 无法使用原生 setFullScreenwin.setSimpleFullScreen(true);// 手动扩展窗口覆盖整个屏幕constdisplayscreen.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;windowFullscreenActivefalse;if(process.platformdarwinwin.isSimpleFullScreen()){win.setSimpleFullScreen(false);applyWindowedBounds(win);return;}if(!win.isFullScreen()){applyWindowedBounds(win);return;}letappliedfalse;constapplyOnce(){if(applied||!win||win.isDestroyed()||win.isFullScreen())return;appliedtrue;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.typekeyDown(input.keyEscape||input.codeEscape)){constisMacFullScreenprocess.platformdarwin(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,(){htmlFullscreenActivefalse;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_16x162x.png sips-z3232build/icon.png--outbuild/icon.iconset/icon_32x32.png sips-z6464build/icon.png--outbuild/icon.iconset/icon_32x322x.png sips-z128128build/icon.png--outbuild/icon.iconset/icon_128x128.png sips-z256256build/icon.png--outbuild/icon.iconset/icon_128x1282x.png sips-z256256build/icon.png--outbuild/icon.iconset/icon_256x256.png sips-z512512build/icon.png--outbuild/icon.iconset/icon_256x2562x.png sips-z512512build/icon.png--outbuild/icon.iconset/icon_512x512.png sips-z10241024build/icon.png--outbuild/icon.iconset/icon_512x5122x.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可执行文件名iconmacOS 图标路径.icns 格式categoryApp Store 分类音乐类为public.app-category.musichardenedRuntimemacOS 安全特性arch同时支持 Apple Siliconarm64和 Intelx64执行打包cd~/Downloads/Mineradio-1.0.10exportELECTRON_MIRRORhttps://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 错误。这时可以用分步方案第一步只打包 .appnpx electron-builder--macdir在dist/mac-arm64/下生成Mineradio.app。第二步使用 hdiutil 手动创建 DMGcd~/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-volnameMineradio 1.0.10\-srcfolderdmg_temp\-ov-formatUDZO\Mineradio-1.0.10-arm64.dmg# 清理临时文件rm-rfdmg_temp参数说明参数说明-volnameDMG 挂载后显示的卷名-srcfolder源文件夹路径-ov覆盖已有文件-format UDZOzlib 压缩格式最终产物位于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 上的功能限制功能限制原因影响说明桌面壁纸模式使用 WindowsWorkerWAPIuser32.dll无法将播放器嵌入桌面壁纸层桌面歌词鼠标穿透依赖 WindowsGetAsyncKeyState中键切换锁定/解锁不可用桌面快捷方式自动创建使用 Windows.lnk文件无影响拖拽到 Applications 即可正常运行的功能音乐播放、搜索、歌单管理网易云音乐登录与播放QQ 音乐登录与音源补充歌词显示全屏模式已修复粒子视觉 / Emily 播放态3D 歌单架天气电台更新检测附录完整修改文件清单文件修改内容desktop/main.jsmacOS 全屏逻辑修复4 处修改package.json添加build:mac/build:mac:dir脚本和mac/dmg构建配置build/icon.icns新增从icon.png转换生成的 macOS 图标本文发布于 tenxiaodao.top更多技术实践欢迎关注。