Github千星开源NSMusicS(1):使用Vite构建打包Electron全平台(win、linux、macos)

举报
Xiang Cheng 程翔 发表于 2025/01/01 12:16:29 2025/01/01
【摘要】 使用Vite构建打包Electron全平台(win、linux、macos)

Github千星开源NSMusicS(1):使用Vite构建打包Electron全平台(win、linux、macos)

引言

NSMusicS: https://github.com/Super-Badmen-Viper/NSMusicS 是一款旨在构建 云原生音乐服务器/全平台客户端 的Github开源项目,它的桌面客户端基于 Electron 开发,支持 Windows、Linux 和 macOS 三大操作系统。

本文将详细介绍如何使用 Vite 和 electron-builder 实现 NSMusicS 桌面客户端的全平台构建和打包,包括:

  • Windows 的 .exe(x64) 包
  • Linux 的 .AppImage(x86_64) 和 .deb(amd64) 包
  • macOS 的 .dmg(x64、arm64) 包

后续还会给大家带来Docker客户端与Flutter跨平台客户端(仅限移动端Android、IOS)的构建打包细则,当然还有NSMusicS-GO基于Golang的云原生音乐服务器的技术解析、Electron使用 node-gyp 开发并编译 C++ node高性能组件、一些产品上所应用的算法(前后端、人工智能等)解析及应用等等更多的技术分享。

1. 项目结构

在开始之前,先了解一下 NSMusicS 的项目结构:

NSMusicS/
├── src/ # 前后端代码
├── resources/ # 资源文件(图标、配置文件等)
├── dist/ # 构建输出目录
├── release/ # 打包输出目录
├── plugins/ # 自定义脚本
└── package.json # 项目配置文件

2. 安装依赖

本文的构建是基于“小满zs”的构建脚本上的强化大升级实现全平台构建打包踩坑,请先根据此文章链接构建一个基础Electron-Vite: https://blog.csdn.net/qq1195566313/article/details/131713875?spm=1001.2014.3001.5501
当然,如果你在Electron-Vite基础构建上有Github网络访问问题,有两种解决方案:

  1. 梯子(个人建议开发主力机还是主用梯子吧,我只在vmware linux中使用dev-sidecar,不过dev-sidecar可以混用一下,毕竟梯子也有可能出现git拉取与提交不了的问题)
  2. dev-sidecar加速器(https://github.com/docmirror/dev-sidecar)

3.配置设置

假设你已经完成了这些基础构建,接下来就只需要修改构建打包的脚本,参考如下:

// 导入 Vite 的 Plugin 类型
import type { Plugin } from 'vite';
// 导入 electron-builder 模块
import * as electronBuilder from 'electron-builder';
// 导入 Node.js 的 path 模块,用于处理文件路径
import path from 'path';
// 导入 Node.js 的 fs 模块,用于文件系统操作
import fs from 'fs';

// 导出 Vite 插件函数
export const viteElectronBuild = (): Plugin => {
    return {
        // 插件名称
        name: 'vite-electron-build',
        // closeBundle 是 Vite 的一个插件钩子函数,用于在 Vite 构建完成后执行一些自定义逻辑。
        closeBundle() {
            // 定义初始化 Electron 的函数
            const initElectron = () => {
                // 使用 esbuild 编译 TypeScript 代码为 JavaScript
                require('esbuild').buildSync({
                    entryPoints: ['src/background.ts'], // 入口文件
                    bundle: true, // 打包为单个文件
                    outfile: 'dist/background.js', // 输出文件路径
                    platform: 'node', // 目标平台为 Node.js
                    target: 'node12', // 目标 Node.js 版本
                    external: ['electron'], // 排除 electron 模块
                });
            };

            // 调用初始化 Electron 函数
            initElectron();

            // 读取 package.json 文件并解析为 JSON 对象
            const json = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
            // 修改 package.json 的 main 字段为 background.js
            json.main = 'background.js';
            // 将修改后的 package.json 写入 dist 目录
            fs.writeSync(fs.openSync('dist/package.json', 'w'), JSON.stringify(json, null, 2));

            // 在 dist 目录下创建一个空的 node_modules 目录
            fs.mkdirSync(path.join(process.cwd(), 'dist/node_modules'));

            // 使用 electron-builder 打包 Electron 应用程序
            electronBuilder.build({
                config: {
                    // 应用 ID
                    appId: 'github.com.nsmusics.xiang.cheng',
                    // 产品名称
                    productName: 'NSMusicS',
                    // 目录配置
                    directories: {
                        output: path.join(process.cwd(), 'release'), // 输出目录
                        app: path.join(process.cwd(), 'dist'), // app 目录
                    },
                    // 启用 asar 打包
                    asar: true,
                    // Windows 平台配置
                    win: {
                        target: 'nsis', // 打包目标为 NSIS 安装程序
                        icon: 'resources/config/NSMusicS.ico', // 应用图标
                        artifactName: '${productName}-Win-${version}-${arch}.${ext}', // 输出文件名格式
                    },
                    // Linux 平台配置
                    linux: {
                        target: ['AppImage', 'deb'], // 打包目标为 AppImage 和 deb
                        icon: 'resources/config/NSMusicS.icns', // 应用图标
                        desktop: {
                            Icon: '/usr/share/icons/hicolor/512x512/apps/nsmusics.png', // 桌面图标路径
                        },
                        category: 'Audio', // 应用分类
                        maintainer: 'Xiang Cheng 1774148579@qq.com', // 维护者信息
                        artifactName: '${productName}-Linux-${version}-${arch}.${ext}', // 输出文件名格式
                    },
                    // macOS 平台配置
                    mac: {
                        target: 'dmg', // 打包目标为 DMG 安装包
                        icon: 'resources/config/NSMusicS.icns', // 应用图标
                        artifactName: '${productName}-Mac-${version}-${arch}.${ext}', // 输出文件名格式
                    },
                    // NSIS 安装程序配置
                    nsis: {
                        oneClick: false, // 禁用一键安装
                        perMachine: true, // 为所有用户安装
                        allowElevation: true, // 允许提升权限
                        allowToChangeInstallationDirectory: true, // 允许用户更改安装目录
                        installerIcon: 'resources/config/NSMusicS.ico', // 安装程序图标
                        uninstallerIcon: 'resources/config/NSMusicS.ico', // 卸载程序图标
                        installerHeaderIcon: 'resources/config/NSMusicS.ico', // 安装程序头部图标
                        createDesktopShortcut: true, // 创建桌面快捷方式
                        createStartMenuShortcut: true, // 创建开始菜单快捷方式
                        shortcutName: 'NSMusicS', // 快捷方式名称
                    },
                    // 额外资源文件配置
                    extraResources: {
                        from: './resources/', // 资源文件来源目录
                        to: '', // 资源文件目标目录
                    },
                },
            });
        },
    };
};

4.npm 构建

这就不得不谈到最常用的npm包管理了,但其实我最常用的还是cnpm,至于它们的差别,如下所示:
原生模块是指那些依赖本地环境(如操作系统、Node.js 版本、编译器工具链等)需要重新编译的模块,例如 better-sqlite3、node-sass、bcrypt 等。

4.1. npm 的行为

  • 重新编译:npm 在安装原生模块时,会根据当前环境(操作系统、Node.js 版本、CPU 架构等)自动下载源码并调用 node-gyp 进行编译。
  • 兼容性:由于是本地编译,生成的二进制文件与当前环境完全匹配,确保了模块的正确运行。
  • 依赖工具链:需要本地安装编译工具(如 Python、C++ 编译器等),否则可能会报错。

4.2. cnpm 的行为

  • 直接下载预编译包:cnpm 默认会从镜像源下载预编译的二进制包,而不是在本地重新编译。
  • 潜在问题:如果预编译的二进制包与当前环境不兼容(例如 Node.js 版本不匹配或操作系统不同),可能会导致模块无法正常工作。
  • 对于某些模块(如 better-sqlite3),如果镜像源没有提供预编译包,cnpm 可能会直接跳过编译步骤,导致安装失败或运行时出错。
  • 优点:安装速度更快,因为跳过了编译步骤。

4.3. 原生模块的处理

  • 当然,cnpm下载预编译包,跳过编译步骤,这就会导致某些原生库就会与当前环境不适配,就比如better-sqlite3(可用于Electron渲染层与node层同时使用的sqlite轻量级数据库)。
  • 但这也不代表npm下载就一定能解决这个编译问题,但同时这也是可以手动处理的,参考链接:https://www.camelgeek.net/117.html
  • 示例,假设你在安装better-sqlite3时出现以下报错:
de:electron/js2c/asar_bundle:2 Uncaught Error: The module '../node_modules/better-sqlite3/build/Release/better_sqlite3.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 108. This version of Node.js requires
NODE_MODULE_VERSION 114. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
   at process.func [as dlopen] (node:electron/js2c/asar_bundle:2:1822)
   at Module._extensions..node (node:internal/modules/cjs/loader:1326:18)
   at Object.func [as .node] (node:electron/js2c/asar_bundle:2:1822)
   at Module.load (node:internal/modules/cjs/loader:1096:32)
   at Module._load (node:internal/modules/cjs/loader:937:12)
   at f._load (node:electron/js2c/asar_bundle:2:13330)
   at o._load (node:electron/js2c/renderer_init:2:3109)
   at Module.require (node:internal/modules/cjs/loader:1120:19)
   at require (node:internal/modules/cjs/helpers:103:18)
   at new Database (../node_modules/better-sqlite3/lib/database.js:50:11)

这个错误是因为 Electron 内置的 Node.js 版本和编译到better-sqlite3的 Node.js 版本不同导致的。
安装electron-rebuild, 可以全局安装

npm install electron-rebuild -g

也可以在…/node_modules/better-sqlite3 目录下,局部安装

npm install electron-rebuild -D

在better-sqlite3的package.json中,添加如下scripts配置

"rebuild": "electron-rebuild -f -w better-sqlite3"

最后呢,就是执行npm run rebuild,但是我更加建议使用cnpm来执行,无非就是更快更稳定嘛,对于一个无关紧要的小问题,花费了大量时间走弯路,可不是一个工程师,一个学者该做的事情,要将时间花在更重要的事情上

cnpm run rebuild

出现以下类似的rebuild complete,只要执行没报错流程结束,就算是重编译成功了
image.png

4.4. 构建总结

特性 npm cnpm
原生模块处理 根据本机环境重新编译 下载预编译包,跳过编译步骤
兼容性 高,确保与当前环境匹配 低,预编译包可能与环境不兼容
安装速度 较慢(需要编译) 较快(跳过编译)
适用场景 需要原生模块支持的项目 无需原生模块或对速度要求高的项目

5.全平台打包

5.1.常规打包

假设你的package.json中scripts设置如下

"scripts": {
    "dev": "vite",
    "preview": "vite preview",
    "build-only": "vite build",
    "type-check": "vue-tsc --build --force"
},

全平台打包有多种方式,某些操作系统甚至支持在安装特定模块的情况下打包多种平台的应用。此外,Github 的工作流也支持全平台打包和发布。虽然我目前尚未使用过 Github 工作流进行全平台构建,但它在软件开发流程足够完善的情况下,确实是一个可以直接采用的解决方案。

在软件开发初期,我认为在各类操作系统上手动构建、调试和打包是非常必要的。毕竟,你不能在未经过充分测试的情况下直接发布应用。因此,我目前主要在 Windows、MacOS 主力机以及 VMware 的 Linux 虚拟机上进行实机开发和测试。

// 在对应的操作系统,拉取项目代码,手动构建项目
npm run dev //调试
npm run build-only //打包

5.2.打包同一操作系统的不同架构应用包(MacOS:arm64、x64)

在开发跨平台应用时,尤其是在 MacOS 上,我们常常需要为不同的 CPU 架构打包应用。
目前,MacOS 的主流架构已经从 Intel 的 x64 转向了 Apple Silicon 的 arm64(如 M1、M2、M3、M4 芯片)。
然而,仍然有一些用户在使用基于 Intel 芯片的 MacOS 设备。
因此,我们需要在 Apple Silicon 的 MacOS 上同时打包 arm64 和 x64 架构的应用包。

5.2.1.需求背景

  • arm64:Apple Silicon 芯片(如 M1、M2、M3、M4)使用的架构。
  • x64:Intel 芯片使用的架构。
  • 目标:在 Apple Silicon 的 MacOS 上同时打包 arm64 和 x64 架构的应用包。

5.2.2.需求背景实现方案

在 Apple Silicon 的 MacOS 上,可以通过 arch 命令切换架构环境,从而实现在同一台设备上为不同架构打包应用。以下是具体步骤:

5.2.2.1. 检查当前架构:在终端中运行以下命令,可以查看当前系统的架构:

node -p "process.arch"

如果输出 arm64,说明当前运行在 Apple Silicon 架构下。
如果输出 x64,说明当前运行在 Intel 架构下。

5.2.2.2. 切换架构环境

在 Apple Silicon 的 MacOS 上,可以通过 arch -x86_64 命令切换到 x64 架构环境。例如:

arch -x86_64 zsh

运行后,当前终端会话会切换到 x64 架构环境。

5.2.2.3. 创建新项目并初始化

为了确保环境隔离,可以为 x64 架构创建一个新的项目目录(例如命名为 _x64),并在其中重新拉取代码和安装依赖,记得不要忘了在x64环境下重新安装nodejs,你在arm64架构安装的nodejs是不适用于x64的窝:

mkdir _x64
cd _x64
git clone <your-repo-url> .
sudo npm install // sudo cnpm install

npm / cnpm需要全程使用 sudo 确保权限足够。
npm install 会安装适用于x64的项目依赖。

5.2.2.4. 手动编译原生模块

如果项目中使用了原生模块(如 better-sqlite3),需要手动重新编译以确保兼容性,见#4.3,还是一样的流程哈

5.2.2.5. 打包应用

在 x64 架构环境下,运行打包命令:

sudo npm run build-only

完成后,你会得到适用于 x64 架构的应用包。
这玩意也是能在 M 系列芯片上运行的,只是会在执行时通过 Rosetta 2 自动转译为 x64 架构的指令,因此启动速度会相对较慢,但对于日常调试和开发任务来说,性能完全足够。

5.2.2.6. 切换回 arm64 架构

完成 x64 架构的打包后,可以切换回 arm64 架构环境:

arch -arm64 zsh

然后,在默认的 arm64 环境下重新打包:

npm run build-only

完成后,你会得到适用于 arm64 架构的应用包。
这样适用于MacOS的arm64与x64的架构都打包完成。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。