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

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

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

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

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

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

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

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

这玩意儿有两个注意点:

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

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


第一道防线:保本地开发

位置 :vite.config.ts 的 plugins 里

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 应用在某个阶段必定崩给你看。下次再遇到原生模块加载失败,直接查这两个地方,基本应该没啥问题。

相关推荐
jktantan8 小时前
Vue keep-alive 切标签后滚动位置回顶?因为 onDeactivated 里读到的 scrollTop 是 0
前端·javascript·vue.js
2601_962885729 小时前
quotes 字段全解:涨跌幅、振幅、换手率的口径与单位
java·前端·spring
excel9 小时前
encodeURI 和 encodeURIComponent 有什么区别?
前端
前端精髓9 小时前
调用openrouter的免费API模型
java·前端·javascript
赛博守夜人10 小时前
AI安全攻防与治理之十四:AI 安全测试不能脱离传统安全,Web、API、移动端与模型攻击面的组合拳
前端·人工智能·安全
开开心心_Every10 小时前
快速隐藏程序工具自定义快捷键一键切换
java·开发语言·前端·spring·智能手机·word·intellij-idea
networknomad10 小时前
OpenReplay:自己搭的会话回放工具,给前端排错用的
前端·其他
缓慢更新11 小时前
AI 辅助生成单测:从接口定义到用例的流水线
java·服务器·前端·网络·数据库·人工智能
陈随易11 小时前
AI 运维服务器,安全又可靠的方法
前端·后端·程序员
风骏时光牛马12 小时前
AI稳定性治理体系构建与疑难问题深度分析
前端