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

相关推荐
AlienZHOU5 小时前
AI Coding 时代下,我的技术面试实践分享
前端·后端·面试
Captaincc8 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师9 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen10 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒10 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow11 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林81811 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang8007811 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明11 小时前
第 6 章 组件进阶
前端·vue.js
江华森11 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端