需求开发右键点击文件上传到对应的页面中
- 开始整活
win mac liunx三端同时开发
win
遇到的坑先梳理下 刚开始一定是要下载依赖,我用的最多的是pnpm,
- 先决条件,我们需要在依赖拉完再进行原生的依赖拉取,加上我是
在mac的虚拟机里搞win那么系统的架构就区分成arm64和windown常见的x86两种
"postinstall": "electron-builder install-app-deps",
来拉取electron-builder的原生依赖
问题: 原因由于软连接找不到对应的包,或者说找到的不对,那么采用npm更扁平化的方式拉取所有依赖;然后将postinstall移除,将原生的依赖放到打包时来找
但是由于要在虚拟机里搞,所以一直认为要先搞定arm64的再到x86里面搞,那么就区分开了打包的两种情况
"build:win:x64": "npm run build && electron-builder --win --x64 --config -c.nsis.include=build/builder-installer.x64.nsh",
"build:win:arm64": "npm run build && electron-builder --win --arm64 --config -c.nsis.include=build/builder-installer.arm64.nsh",
上述命令就是再打包时命令式的让electron-builder读取builder-installer.x64.nsh和builder-installer.arm64.nsh
!macro customInstall
; --- 右键上传菜单注册 ---
; 创建一个名为 "UploadToPage" 的动作键 (* 代表所有文件类型)
WriteRegStr HKCR "*\shell\UploadToPage" "" "上传到 bridge"
; (可选) 为菜单项添加一个图标,使用你应用的主程序图标
WriteRegStr HKCR "*\shell\UploadToPage" "Icon" "$INSTDIR\${APP_FILENAME}.exe"
; 定义点击菜单项时要执行的命令 ("%1" 是占位符,代表被右键点击的文件的完整路径)
WriteRegStr HKCR "*\shell\UploadToPage\command" "" '"$INSTDIR\${APP_FILENAME}.exe" "%1"'
!macroend
; =========================================================================
; ▼▼▼ 卸载时触发的单个 customUnInstall 宏 ▼▼▼
; =========================================================================
!macro customUnInstall
; --- 删除右键菜单注册表项 ---
DeleteRegKey HKCR "*\shell\UploadToPage"
!macroend
上述是nsh特有的钩子,具体自己可以去找下相关的,这里不做多解释;
再于问题:我打包arm时,成功了,但是应用在运行时发生找不到原版 robotjs 还是 @jitsi/robotjs, 结果-查找发现robotjs 对 Windows ARM64 的支持情况总结 核心结论: 目前,无论是原版 robotjs 还是 @jitsi/robotjs,都没有为 Windows ARM64 (win32-arm64) 提供官方的、预编译好的二进制文件。
转念然后直接打x86的,这次成功,且可运行,留有疑问,这种arm上打x86的方式,说是electron-builder会去自动识别来做调整,这块需要进一步学习研究;
然后对于在arm上运行x86的应用看来也是可行的,给出的解释:
借助了 Windows on ARM 内置的 x86/x64 仿真层。这个兼容层非常高效,能让 32 位和 64 位的 Intel/AMD 应用在 ARM 架构的 CPU 上运行,用户几乎感觉不到差异。这是你目前问题最完美的解决方案。
好👌 结合业务思路大概说下:读取文件,将文件地址,以及文件转化成上传的格式,来实现结合到业务中
mac
接下来进入mac功能,也很简单就是在electron-builder中加入
# ▼▼▼ 在这里添加文件关联配置 ▼▼▼
fileAssociations:
# 为每个需要支持的扩展名创建一个条目
- ext: docx
name: Microsoft Word Document
role: Editor # 将你的应用注册为这些文件的“编辑器”角色
- ext: pptx
name: Microsoft PowerPoint Presentation
role: Editor
- ext: xls
name: Microsoft Excel 97-2003 Worksheet
role: Editor
- ext: xlsx
name: Microsoft Excel Worksheet
role: Editor
- ext: csv
name: Comma Separated Values File
role: Editor
- ext: txt
name: Text Document
role: Editor
- ext: pdf
name: PDF Document
role: Editor
上传逻辑
app.on('open-file', (event, filePath) => {
event.preventDefault();
log.info(`[macOS] Received open-file event for: ${filePath}`);
// 这里的逻辑与 Windows 的逻辑完美复用
if (mainWin) {
// 如果窗口已存在,直接发送任务并唤醒窗口
mainWin.webContents.send('upload-file-task', filePath);
if (mainWin.isMinimized()) mainWin.restore();
if (!mainWin.isVisible()) mainWin.show();
mainWin.focus();
} else {
// 如果应用是随文件首次启动,暂存路径,等待窗口创建
initialFilePath = filePath;
}
});
上传逻辑服用,但是app.on('open-file',)这个是mac特有的,
然后依然拉依赖时用npm,打包也是;
相关项目依赖
"electron": "^20.3.12",
"electron-builder": "^23.6.0",
"electron-vite": "^2.3.0",
"react": "^18.3.1",
扩展知识
虚拟机为啥可以? 他也是在宿主机上依赖于宿主机的架构啊,这个虚拟机到底有什么不同,以至于他可以成功的打包成功不同的版本?
这个问题直击了虚拟化技术的核心!你的困惑非常正常:“虚拟机不也是在我的 Mac 上跑吗?为什么它就能搞定,而我直接在 Mac 上就不行?”
答案是:虚拟机虽然物理上运行在你的 Mac 上,但它为 Windows 创造了一个完全独立、自给自足、与外界隔离的“模拟世界” 。
我们可以用一个生动的比喻来理解:
“房间里的小房子”的比喻
-
你的 Mac (宿主机) :是一个中式风格的大房间。房间里的所有工具(编译器、依赖包)都是中式的。
-
直接在 Mac 上打包 Windows 应用:相当于你试图在这个中式房间里,用中式工具去造一个欧式风格的椅子。你可能知道椅子的图纸(代码),但你手上的榫卯工具(macOS 的二进制依赖)根本没法处理欧式椅子需要的螺丝和钉子(Windows 的二进制依赖)。
-
虚拟机 (VM) :相当于你在中式大房间里,凭空建了一个完全封闭的、带有恒温恒湿系统的、欧式风格的小房子。
- 这个小房子有自己的墙壁、门窗、空气循环系统,它内部的环境完全是欧式的,与外面中式大房间的环境完全隔离。
虚拟机就是这个“欧式小房子”。 它虽然身处中式大房间,但它内部的一切都是纯正的欧式。
虚拟机到底有什么不同?
现在我们来看技术细节。这个“欧式小房子”到底和外面的大房间有什么本质区别,以至于它能成功?
1. 一个完整、独立的操作系统
这是最根本的区别。虚拟机里运行着一个完整、原生的 Windows 操作系统。它有:
- 自己的 C 盘、自己的文件系统。
- 自己的 Windows 注册表。
- 自己的网络配置、自己的系统服务。
- 当你在虚拟机里打开命令行时,它会告诉所有程序:“我现在就在一台 Windows 电脑上! ”
而你在 Mac 上直接操作时,命令行会说:“我现在在 macOS (Darwin) 上!”
2. 一个被“欺骗”的软件环境
你说的没错,虚拟机的底层确实依赖于你 Mac 的 ARM 架构 CPU。这里的魔法在于虚拟机软件(Hypervisor,如 Parallels Desktop 或 VMWare Fusion) 。
- Hypervisor 的作用:它就像一个极其聪明的“实时翻译官”。
- 当虚拟机里的 Windows 系统想要执行一个 x86/x64 指令时,Hypervisor 会立刻拦截这个指令,将它实时翻译成你 Mac 的 ARM 芯片能听懂的指令,然后交给 CPU 执行,再把结果翻译回去给 Windows。
- 对于虚拟机里的 Windows 和你运行的所有程序来说,它们完全不知道这个翻译过程的存在。它们感觉自己就运行在一台真正的 Intel/AMD 电脑上。
3. 独立且正确的依赖解析
这直接解决了你的打包问题。当你在虚拟机里的 Windows 中打开终端,并运行 pnpm install 时:
pnpm会检查当前环境,它得到的结果是:platform: 'win32',arch: 'x64'。- 于是,
pnpm会为node-screenshots这个包去下载专门为 Windows x64 编译好的二进制文件,也就是那个node-screenshots-win32-x64-msvc.node。 - 现在,你的
node_modules文件夹里,就有了打包 Windows 应用所需要的正确的“零件” 。
4. 独立的构建工具链
虚拟机里可以安装 Windows 专属的编译工具(如 Visual Studio Build Tools)。当 electron-builder 需要重新编译某些原生模块时,它可以在一个纯正的 Windows 环境中调用这些工具,确保编译出正确的文件。
对比表格
| 对比项 | 直接在 Mac 上打包 | 在 Mac 的 Windows 虚拟机里打包 |
|---|---|---|
| 操作系统环境 | macOS (Darwin) | Windows (win32) |
pnpm 看到的 CPU 架构 | arm64 (Apple Silicon) | x64 (被模拟的) |
下载的 .node 文件 | ...-darwin-arm64.node ❌ | ...-win32-x64-msvc.node ✅ |
| 最终结果 | 打包出的应用缺少 Windows 依赖,运行时崩溃 | 打包出的应用包含所有正确依赖,运行成功 |
总结: 虚拟机之所以可以,因为它提供了一个与宿主机完全隔离的、目标平台(Windows)的原生软件环境。在这个环境里,所有的工具(pnpm, node-gyp, electron-builder)都能获取到它们所期望的、正确平台的依赖和工具链,从而构建出一个完整且正确的应用。
关于麒麟系统(Kylin OS)这种国产系统
一个非常好的、更深入的问题。对于麒麟系统这种特定的国产操作系统,情况会稍微复杂一点,但核心思路不变。
麒麟系统本质上也是一个 Linux 发行版,它通常基于一个上游的开源版本(比如 Ubuntu 或 Debian)进行二次开发。
所以,你有两种策略:
策略一:在相似的“近亲”系统上构建 (成功率高)
- 查明基础:首先,你需要查一下你目标用户使用的麒麟系统是基于哪个版本的 Ubuntu 或 Debian 开发的。
- 构建环境:在你的虚拟机里,安装一个与麒麟系统基础版本最接近的 Ubuntu/Debian 系统。
- 打包
.AppImage(最推荐) :在这种情况下,打包成.AppImage格式是你的最佳选择。因为.AppImage会把你的应用和它所需要的大部分依赖库都打包在一起,这使得它具有非常好的可移植性,在各种不同的 Linux 发行版(包括麒麟)上运行的成功率最高。
策略二:在“目标系统”上构建 (100% 兼容,最可靠)
这是最稳妥、最专业的方法。
- 获取麒麟系统镜像:想办法获取到你目标麒麟操作系统的虚拟机镜像文件(
.iso)。 - 创建麒麟虚拟机:直接在你的 Mac 上创建一个运行着原生麒麟系统的虚拟机。
- 在麒麟系统里打包:在这个虚拟机里,搭建你的开发环境,运行
pnpm install和打包命令。
这样做的好处是:你的应用是在一个与最终用户一模一样的环境中编译和打包的,所有系统库、依赖版本都完全匹配,可以保证 100% 的原生兼容性。
总结
| 目标平台 | 最佳构建环境 | 推荐打包格式 | 备注 |
|---|---|---|---|
| Windows | Windows 电脑或 Windows 虚拟机 | .exe (NSIS) | 必须在 Windows 环境下构建原生模块。 |
| macOS | Mac 电脑 | .dmg | 这是你的原生开发环境,最简单。 |
| 主流 Linux (Ubuntu等) | Linux 虚拟机 (如 Ubuntu) | .AppImage (通用) 或 .deb (Debian系) | 虚拟机提供了正确的依赖环境。 |
| 国产 Linux (麒麟等) | 麒麟系统虚拟机 (最可靠) 或 与其基础版本相似的 Ubuntu 虚拟机 | .AppImage (最通用) | 直接在目标系统里打包可以避免任何库版本不兼容的风险。 |
总而言之,你的思路完全正确:要为哪个平台打包,最好就在哪个平台的(虚拟)环境里进行。 这条黄金法则可以帮你避免绝大多数跨平台打包的坑。
扩展:
那这种electron可以自动识别什么系统,用什么的方式,是如何实现的
这个问正是整个跨平台开发生态系统能够运转起来的“魔法”所在!
Electron 和它的工具链(如 pnpm, electron-builder)之所以能自动识别系统并采用不同的方式,是基于一套分层协作的机制,从底层到上层,每一环都提供了关键信息。
下面我分解这个实现过程:
第 1 层:基石 - Node.js 的 process 对象
这一切的根源在于 Node.js。任何 Electron 应用的核心都是一个 Node.js 运行时。Node.js 在启动时,就会检测当前的操作系统和硬件环境,并把这些信息存储在一个全局可访问的 process 对象里。
其中最重要的两个属性是:
-
process.platform: 返回一个字符串,用来识别操作系统。- 在 Windows 上,它的值是
'win32'(无论是32位还是64位系统)。 - 在 macOS 上,它的值是
'darwin'。 - 在 Linux 上,它的值是
'linux'。
- 在 Windows 上,它的值是
-
process.arch: 返回一个字符串,用来识别 CPU 架构。- 在 64 位的 Intel/AMD 电脑上,是
'x64'。 - 在 32 位的 Intel/AMD 电脑上,是
'ia32'。 - 在苹果的 M 系列芯片 (Apple Silicon) Mac 上,是
'arm64'。 - 在 ARM 架构的 Windows 电脑上,也是
'arm64'。
- 在 64 位的 Intel/AMD 电脑上,是
你可以把 process 对象想象成应用的“出生证明” ,它清楚地记录了应用当前“出生”在哪个国家(操作系统)和哪个民族(CPU架构)。这是所有上层工具获取环境信息的“唯一真相来源”。
第 2 层:依赖管理器 (如 pnpm) 的智能决策
当你运行 pnpm install 时,pnpm 的工作流程是这样的:
-
检查“出生证明” :pnpm 首先会读取
process.platform和process.arch的值,知道了自己当前所处的环境。 -
阅读“安装说明书” (
package.json) :pnpm 会去查看它要安装的每一个包(比如node-screenshots)的package.json文件。一个设计良好的原生模块,会在它的package.json里使用一些特殊的字段,比如optionalDependencies(可选依赖)。node-screenshots的package.json可能长这样(简化示例):JSON
{ "name": "node-screenshots", "version": "1.0.0", "main": "index.js", "optionalDependencies": { "node-screenshots-win32-x64-msvc": "1.0.0", "node-screenshots-darwin-arm64": "1.0.0", "node-screenshots-linux-x64-gnu": "1.0.0" } } -
做出智能选择:
- 当你在 Mac 上运行时:pnpm 看到
platform是'darwin',arch是'arm64'。它就会在optionalDependencies列表里精确地寻找并只下载node-screenshots-darwin-arm64这个包。 - 当你在 Windows 虚拟机里运行时:pnpm 看到
platform是'win32',arch是'x64'。它就会精确地只下载node-screenshots-win32-x64-msvc这个包。
- 当你在 Mac 上运行时:pnpm 看到
所以,pnpm 就像一个聪明的采购员,他先看了一眼自己身在何处(process 对象),然后对照采购清单(package.json),只购买当前地区需要的、正确规格的零件(平台专属的二进制包)。
第 3 层:打包工具 (electron-builder) 的角色
electron-builder 在这个链条中扮演着“总工程师”的角色。
-
确定默认目标:如果你只运行
electron-builder而不加任何参数,它也会先查看process.platform,然后默认打包你当前所在的操作系统平台。 -
响应明确指令:当你运行
electron-builder --mac或electron-builder --win时,你是在给它下达一个明确的指令:“我不管你现在在哪,你的目标是为 macOS/Windows 造一个产品! ” -
组装产品:
- 它会根据你的指令(比如
--win),去网上下载目标平台的“外壳”(Windows 版的 Electron 框架)。 - 然后,它会把你自己的代码和 当前环境 的
node_modules文件夹(也就是 pnpm 在你本地安装好的那些“零件”)一起打包进去。
- 它会根据你的指令(比如
这就解释了交叉编译失败的原因:当你在 Mac 上执行 electron-builder --win 时,它拿了 Windows 的“外壳”,却把你 Mac 上的“中式零件”(...-darwin-arm64.node)装了进去,造出来的产品自然无法在 Windows 上使用。
总结
这个自动化过程是一个漂亮的分层协作:
- Node.js 提供了最底层的环境信息。
- pnpm 根据这些信息,智能地安装了正确平台的依赖。
- electron-builder 根据你的指令,使用这些依赖来构建最终的应用。
理解了这个流程,你就能明白为什么“在目标平台(或其虚拟机)上进行构建”是处理原生模块时最可靠的黄金法则。