跨平台打包与发布
本页说明 Hydrogen Music 桌面端如何由 Vite 构建前端资源,并由 electron-builder 配置 Windows、macOS 和 Linux 产物及发布目标。Android 在同一仓库有独立工程与脚本,本文只说明桌面端与其构建入口的边界。
Purpose and Scope(目的与范围)
面向需要维护桌面发行构建的开发者:涵盖根目录 npm 脚本、前端构建选项、electron-builder 的文件筛选、平台目标、资源和发布配置。Android 的签名、SDK、APK/AAB 流程请参见 Android 文档;播放器功能和运行期更新逻辑不在本页范围。根脚本虽然暴露 Android 命令,但这些命令委托给子项目,不能等同于桌面打包链路。依据:package.json、README.md。
Overview(概览)
桌面发行的可见入口为 dist 与 _dist:都先执行 vite build,再调用 node scripts/build.js,分别传入 -p never 和 -p always。package.json 的 Electron 主入口是 background.js;electron-builder 将此文件和 dist/**/*、桌面歌词页面、Electron 相关文件一起纳入产物。版本号由 package.json 提供,Vite 同时将其注入 __APP_VERSION__;发行输出目录配置为 release/${version}。依据:package.json、vite.config.js、electron-builder.config.cjs。
Architecture(架构)
Source: package.json、vite.config.js、electron-builder.config.cjs
图中的 scripts/build.js 是根脚本调用的路径;本页未读取该脚本实现,因此箭头表示配置与脚本入口的构建关系,不意味着已验证该脚本如何加载 builder 配置、选择平台或执行发布。dist 是 Vite 默认输出与打包文件模式相接的边界;实际发行文件由 builder 的 files 配置进一步筛选。依据:package.json、electron-builder.config.cjs。
构建流程与资源边界
两阶段桌面构建
根脚本明确先执行 vite build,只有成功后才经 && 继续执行打包脚本。_dist 和 dist 唯一可见的参数差别是 -p always 与 -p never;参数的解析、发布时机以及是否需要凭据,均取决于尚未核查的 scripts/build.js,不能仅凭参数名推断实际上传行为。build 则仅执行 Vite 构建,不触发后续打包脚本。依据:package.json。
"build": "vite build",
"_dist": "vite build && node scripts/build.js -p always",
"dist": "vite build && node scripts/build.js -p never"Source: package.json
Source: package.json、vite.config.js、electron-builder.config.cjs
前端输出与压缩
Vite 配置使用 Vue 插件、相对 base: './',以 index.html 与 desktop-lyric.html 为两个 Rollup 输入。编译目标为 es2018,terser 压缩删除 console 和 debugger、剔除注释;cssCodeSplit: true,块大小警告阈值 1000。因此生产包中的诊断输出不应依赖前端 console。版本常量通过读取包版本在构建时定义,不是运行时读取。依据:vite.config.js。
1 base: './',
2 define: { __APP_VERSION__: JSON.stringify(packageJson.version) },
3 build: {
4 target: 'es2018', // 更新到ES2018以支持async generator functions
5 rollupOptions: {
6 input: {
7 main: resolve(__dirname, 'index.html'),
8 'desktop-lyric': resolve(__dirname, 'desktop-lyric.html')
9 }
10 },Source: vite.config.js
文件收集与体积控制
BASE_FILE_PATTERNS 明确纳入 Electron 入口、两个 HTML、dist/**/*、图标、src/electron/**/* 和两个共享设置文件;排除发行目录、SSR 构建目录、字体目录以及指定依赖里的静态/开发资产。files 再按平台条件追加 Linux 专属依赖排除规则。模块目录与文件的批量裁剪模式意在避免将测试、示例、文档、类型声明等无关内容随应用分发;onNodeModuleFile 对匹配 LICENSE、LICENCE、NOTICE、THIRD-PARTY-NOTICES 的文件返回 true。注意具体筛选结果仍需对生成产物核对。依据:electron-builder.config.cjs、electron-builder.config.cjs、electron-builder.config.cjs。
1 files: [
2 ...BASE_FILE_PATTERNS,
3 ...(shouldExcludeLinuxOnlyDependencies() ? LINUX_ONLY_DEPENDENCY_EXCLUDES : []),
4 ],
5 onNodeModuleFile: (filePath) => {
6 const normalizedPath = filePath.replace(/\\/g, '/');
7 return KEEP_NODE_MODULE_FILE.test(normalizedPath);
8 },Source: electron-builder.config.cjs
ffmpeg-static 通过 asarUnpack 从 ASAR 中解包;resources/mpv 下与目标平台同名或以平台名加 - 开头的子目录会作为 extraResources 复制到 mpv/<目录名>。资源根目录不存在时该函数返回空数组,因此配置不会强制下载 mpv 资源。依据:electron-builder.config.cjs。
平台目标、发布与配置
| 配置位置 | 取值或默认值(配置中明确写出的值) | 作用 |
|---|---|---|
productName / appId | Hydrogen Music / com.hydrogenmusic.app | 应用产品名及标识 |
asar / compression | true / maximum | 启用 ASAR 与最大压缩 |
electronLanguages | en, en-US, zh_CN, zh_TW, zh-CN, zh-TW | 保留不同平台的 Electron 语言标识 |
directories.output | release/${version} | 按版本组织桌面发行文件 |
nsis.oneClick / allowToChangeInstallationDirectory | false / true | Windows NSIS 使用非一键安装并允许更改目录 |
mac.target | dmg | macOS 磁盘映像 |
win.target | nsis, portable, zip | Windows 三类产物;verifyUpdateCodeSignature: false |
linux.target | AppImage, deb, rpm,各 x64 | Linux 三种包类型及显式架构 |
publish | github;owner: ldx123000;repo: Hydrogen-Music;releaseType: draft | 发布目标配置为 GitHub 草稿发行版,实际上传触发逻辑未核查 |
配置均见 electron-builder.config.cjs。Windows NSIS 使用 Hydrogen.Music.Setup.${version}.${ext},Windows 总体 artifactName 为 Hydrogen.Music.${version}.${ext};macOS 的文件名包含 ${arch},Linux 的文件名包含 ${version}。依据:electron-builder.config.cjs。
1 linux: {
2 category: 'Audio',
3 icon: './src/assets/icon/icon.png',
4 extraResources: getMpvExtraResourcesForPlatform('linux'),
5 target: [
6 {
7 target: 'AppImage',
8 arch: ['x64'],
9 },
10 {
11 target: 'deb',
12 arch: ['x64'],
13 },
14 {
15 target: 'rpm',
16 arch: ['x64'],
17 },
18 ],
19 artifactName: 'Hydrogen.Music-${version}.${ext}',
20 },Source: electron-builder.config.cjs
1 publish: [
2 {
3 provider: 'github',
4 owner: 'ldx123000',
5 repo: 'Hydrogen-Music',
6 releaseType: 'draft',
7 },
8 ],Source: electron-builder.config.cjs
Usage Examples(实际入口)
先按 README 的环境要求使用 Node.js 20.19.0+ 或 22.12.0+ 与 npm;以下是项目自身的脚本定义,可用 npm run build 仅构建前端、npm run dist 或 npm run _dist 进入后续桌面脚本。是否会产生或上传产物以执行环境及未核查脚本为准。Android 入口独立委托给子项目,不应复用桌面目标配置:package.json。
"android:build": "npm --prefix apps/android run android:build",
"android:release": "npm --prefix apps/android run android:release",
"android:bundle": "npm --prefix apps/android run android:bundle"Source: package.json
README 明确将 Android APK/AAB 的签名与 SDK 配置交由 Android 子工程,并将桌面安装包按 Windows、macOS、Linux 三组列出;这解释了同仓库的两个发行边界。README.md、README.md。
内部配置函数与条件分支
detectRequestedTargets(argv) 接收参数数组、构造 Set,返回 { mac, win, linux } 三个布尔标志:识别 --mac/-m、--win/--windows/-w 和 --linux/-l。shouldExcludeLinuxOnlyDependencies() 无参数,从 process.argv 判断:显式 Linux 优先保留 Linux 依赖;仅显式 macOS/Windows 则排除;没有显式目标时在非 Linux 宿主排除,在 Linux 宿主保留。该函数仅用于 files 的条件扩展,而不是直接指定 builder 的 target。electron-builder.config.cjs、electron-builder.config.cjs。
1function detectRequestedTargets(argv) {
2 const args = new Set(argv);
3 return {
4 mac: args.has('--mac') || args.has('-m'),
5 win: args.has('--win') || args.has('--windows') || args.has('-w'),
6 linux: args.has('--linux') || args.has('-l'),
7 };
8}
9
10function shouldExcludeLinuxOnlyDependencies() {
11 const requestedTargets = detectRequestedTargets(process.argv);
12 if (requestedTargets.linux) return false;
13 if (requestedTargets.mac || requestedTargets.win) return true;
14 return process.platform !== 'linux';
15}Source: electron-builder.config.cjs
getMpvExtraResourcesForPlatform(platform) 接收平台字符串,返回适用于 builder extraResources 的 { from, to } 数组;首先判断 resources/mpv 是否存在,再仅读取其中平台同名目录或 <平台>-... 目录。其返回值分别由 macOS darwin、Windows win32、Linux linux 配置调用。若资源缺失则空数组,无异常分支记录在该代码中。electron-builder.config.cjs、electron-builder.config.cjs。
1function getMpvExtraResourcesForPlatform(platform) {
2 if (!fs.existsSync(MPV_RESOURCE_ROOT)) return [];
3
4 const platformDirs = fs.readdirSync(MPV_RESOURCE_ROOT, { withFileTypes: true })
5 .filter((entry) => entry.isDirectory() && (entry.name === platform || entry.name.startsWith(`${platform}-`)))
6 .map((entry) => entry.name);
7
8 return platformDirs.map((name) => ({
9 from: path.join(MPV_RESOURCE_ROOT, name),
10 to: path.posix.join('mpv', name),
11 }));
12}Source: electron-builder.config.cjs
边界情况、性能与维护注意
- 目标参数交叉:同时出现 Linux 和 Windows/macOS 参数时,函数先判断 Linux,因而不会追加 Linux 依赖排除项。未指定平台时依赖宿主
process.platform;维护交叉构建时要核对传入配置模块的process.argv。electron-builder.config.cjs。 - 可选 mpv 内容:资源目录缺失不会阻断
getMpvExtraResourcesForPlatform,但也意味着不会有该目录下的 mpv 文件被复制;构建前应检查所需平台目录是否存在。electron-builder.config.cjs。 - 包体积与运行资源:最大压缩、依赖裁剪、语言限制和
ffmpeg-static解包同时生效。扩展files排除模式时须验证应用真正需要的运行期资产未被删掉;不能把所有 Node 模块文件一概当作开发文件。electron-builder.config.cjs、electron-builder.config.cjs。 - 发布与可观测性:Vite 生产编译会移除
console;builder 的publish声明 GitHub 草稿目标,但没有从已读文件验证 CI、认证、版本标签或签名流程。不要将配置项解释为已经成功发布。vite.config.js、electron-builder.config.cjs。 - 扩展方式:新增平台原生 mpv 文件时沿用
resources/mpv/<平台>或<平台>-<变体>命名才能被当前过滤器发现;新增安装格式需修改对应平台target,新增必需文件需调整BASE_FILE_PATTERNS。这些是由当前配置推导出的维护位置,不代表在此页已验证所有平台实际构建测试。electron-builder.config.cjs、electron-builder.config.cjs、electron-builder.config.cjs。