Electron 加载原生模块总崩溃?搞懂这两行配置就够了

Electron 加载原生模块总崩溃?搞懂这两行配置就够了

用 Electron + Vite 做桌面端开发,肯定会遇到这个情况:引入 C++ 原生模块。

比如你想搞个全局快捷键、监听鼠标宏,引入了 uiohook-napi 或者 koffi。 结果往往是:本地开发一启动就白屏报错,或者好不容易本地跑通了,打包成 exe 发给测试,双击秒退。 然后,弹出原生模块/依赖确实的弹窗。

如果你也经历过这种问题,大概率是没搞定这两部分:Vite 的打包拦截Electron Builder 的 ASAR 归档

今天我们就扒一扒 vite.config.tselectron-builder.js 里,那两段看起来毫不起眼、却救了无数命的配置。

先搞清楚:原生模块为什么这么"娇贵"?

平时我们写的 JS/TS,Vite 随便揉捏,打包成什么样子都能跑。但像 uiohook-napi 这种底层库,它们最终会编译成一个 .node 后缀的二进制文件(动态链接库)。

这玩意儿有两个注意点:

  1. Vite 不认识它:强行用 Esbuild/Rollup 去打包分析二进制文件,路径绝对错乱。
  2. 操作系统的底层加载机制很笨:它只能通过绝对路径去硬盘上找真实的物理文件,读不懂虚拟的压缩包。

针对这两点,我们需要在不同的阶段下药。


第一道防线:保本地开发

位置vite.config.tsplugins

typescript 复制代码
plugins: [
  isServe && notBundle({
    filter: ['electron-log', 'uiohook-napi']
  })
]

场景还原

当你敲下 npm run dev,Vite 开始接管主进程的编译。它的本能是把 node_modules 里的依赖全打包到一起。一旦它碰到 uiohook-napi,由于里面掺杂了 .node 文件的引用,打包过程就会出岔子,导致主进程直接挂掉。

这行代码干了啥?

notBundle 插件的作用很简单,就是"按兵不动"。配置了 filter: ['uiohook-napi'] 后,相当于告诉 Vite:"碰到这个库,你别管,别打包,把 import 原样转成 require() 留着。"

这样,代码运行时实际上是 Node.js 运行时直接去 node_modules 目录里动态加载真实的二进制文件,本地开发就顺滑了。

顺便提个坑 : 你可能会在网上看到另一种函数写法:filter: (id) => ['uiohook-napi'].includes(id) ? false : undefined

千万别这么写! 在这个插件里,返回 false 等于"放行让 Vite 打包",返回 undefined 才是"不打包"。上面那种函数写法相当于把 notBundle 给废了,原生模块照样崩溃。直接用数组 ['xxx'] 才是最稳的标准姿势。


第二道防线:保生产打包

位置electron-builder.js

javascript 复制代码
asarUnpack: [
  "**/*.node"
]

场景还原

本地 dev 好不容易没报错了,你信心满满地执行 npm run build,然后拿着生成的安装包去别人电脑上安装。双击图标,转圈,闪退。翻开日志一看:Error: dlopen failed...

这行代码又干了啥?

electron-builder 打包时,默认会把你的代码、资源全塞进一个叫 app.asar 的归档文件里(你可以理解为一种没有后缀的 zip 虚拟目录)。

但是!操作系统底层加载 .node 文件用的 dlopen 方法,它是个"老实人",它不认 asar 这种虚拟文件系统,它必须去硬盘的真实路径上找文件。找不到,就直接抛异常闪退。

加上 asarUnpack: ["**/*.node"] 后,打包工具在生成 app.asar 的同时,会像个扫地僧一样,把所有 .node 文件单独挑出来,放进一个叫 app.asar.unpacked 的真实物理文件夹里。

当用户打开你的软件,Electron 底层做了个聪明的兼容:发现你要加载原生模块,它会自动去 unpacked 目录里找。这样,生产环境也稳了。


总结:一个原生模块的"通关"历程

graph TD Start[.node原生模块] --> Vite subgraph 第一关 Vite本地编译 Vite{Vite默认打包} Vite -->|未拦截| Fail1[路径错乱 启动报错] Vite -->|notBundle拦截| Keep[保留require原样] Keep --> Load[动态加载node_modules] Load --> DevOK[开发环境通过] end DevOK --> Asar subgraph 第二关 Builder生产打包 Asar{默认压入asar} Asar -->|未拦截| Fail2[无法读取 线上闪退] Asar -->|asarUnpack拦截| Unpack[解压至unpacked目录] Unpack --> BuildOK[生产环境通过] end

如上图,我们可以把这两行配置看作是一个原生模块在项目里的两个关卡:

  1. 开发期(Vite)notBundle 把它拦下,不让进打包队列了,直接走 require cjs通道去 node_modules 找本体吧。"
  2. 打包期(ASAR)asarUnpack 把它拦下,不压进虚拟压缩包,去旁边的 unpacked 目录待着。"

两道关卡各司其职,少一个,你的 Electron 应用在某个阶段必定崩给你看。下次再遇到原生模块加载失败,直接查这两个地方,基本应该没啥问题。

相关推荐
拉拉肥_King1 小时前
pc端视频压缩:FFmpeg.wasm 实战指南
前端
0x861 小时前
基于 Dio 实现 SSE 流式通信
前端
ZC跨境爬虫1 小时前
跟着 MDN 学 HTML day_40:(DOMImplementation 接口完全解析)
前端·ui·html·媒体
Highcharts.js2 小时前
Highcharts 纯 JavaScript 图表库深度使用评测
开发语言·前端·javascript·功能测试·ecmascript·highcharts·技术评测
码码哈哈0.02 小时前
基于 RSA 非对称加密与挑战码机制的前端登录安全方案
前端·安全·状态模式
ZC跨境爬虫2 小时前
跟着 MDN 学 HTML day_39:(DOMException 异常接口完全解析)
前端·javascript·html·媒体
渐儿2 小时前
NestJS 教程 Part 2 — 数据层、API 设计与业务异步
前端
渐儿2 小时前
Next.js 教程 Part 2 — 数据获取、Server Actions 与状态
前端