一个 Electron + Deno + Chromium 的桌面应用,是怎么把三套运行时塞进安装包的 📅 发布时间:2026/9/3 6:56:41 👁 浏览次数: 桌面 RPA 有一个大部分 Electron 应用不会遇到的问题除了应用本身它还得把执行引擎和浏览器内核一起分发给用户。作为一个开源免费 RPA 项目FreeRPAhttps://github.com/freerpa/freerpa的打包配置就是围绕这个问题展开的——它在 electron-builder.yml 和几个 scripts 里把三套运行时和一个原生数据库模块安排得比较清楚。这篇就记录它的分发策略以及背后的工程原因。一、先看它要分发哪些东西一个 RPA 客户端运行起来需要的东西比普通应用多Electron 应用壳主进程 渲染进程代码Vue 3 那套Deno 运行时执行引擎的宿主作为独立二进制随包分发Worker 运行时资源节点执行器、引擎源码、依赖闭包Chromium 浏览器内核指纹浏览器内核随包内置sqlite3 原生模块N-API 二进制本地数据层依赖。难点在于这些组件对打包进 asar 还是放 asar 外的诉求完全不同。二、三个分发区asar / asarUnpack / extraResourceselectron-builder 提供了三种放东西的地方FreeRPA 对它们的使用很有代表性1. asar应用代码files里把src/*、scripts/**、resources/**、browser/**全排除——应用代码只进 asar其他东西按各自的方式走避免重复打包。注释里写得很直白浏览器内核只经 extraResources 按平台单独分发防止重复打进 asar 导致包体膨胀。2. asarUnpack保留在 asar 外的原生二进制esbuild的平台原生二进制走asarUnpack。原因很具体asar 是只读虚拟文件系统无法 spawn 里面的可执行文件——如果不 unpack打包插件功能会报spawn ENOTDIR开发环境下 node_modules 是实体文件所以正常打包后 asar 化才暴露。这类开发正常、打包炸的问题是打包工程里最值得写注释的地方这个文件里就写清楚了。3. extraResources完整的外部资源三样大件都走这里resources/deno→denoDeno 二进制resources/worker→workerworker 运行时资源filter 排除了 node_modulesresources/worker/node_modules→worker/node_modules依赖闭包单独显式复制。第三行的存在很关键。worker 的依赖闭包是 deno cache 预填充生成的目录布局已经实体化如果不单独复制运行时无法解析节点依赖——这是比漏文件更难排查的问题所以它宁可拆成两条 extraResources 规则也要保证依赖闭包一定在。三、原生依赖sqlite3 的 N-API 问题sqlite3 是 N-API 模块二进制靠prebuild-install下载或 node-gyp 本地编译。网络失败时node_sqlite3.node缺失打包产物里就没有它运行时直接报 “Could not locate the bindings file”。ensure-native.mjs解决得很幂等先用fs.realpathSync解析真实安装路径——因为 yarn 的.deno隔离布局下node_modules/sqlite3是个符号链接不解析真实路径会找错地方检查build/Release/node_sqlite3.node是否存在存在就跳过幂等缺失才跑prebuild-install失败回退 node-gyp。对应地配置里npmRebuild: false——原生模块的构建不交给 electron-builder 默认流程而是由这个脚本在打包前置阶段统一保证避免两套机制互相打架。四、worker 运行时构建、而不是复制worker 那部分不是简单把目录拷过去而是有专门的构建脚本build-worker.mjs产出到resources/worker/worker 源码host.js/engine.js/bridge.js/worker-common.js/data-bridge.js/electron-bridge.js/import-map.json/core/**节点执行器nodes/type/Vn/execute.js含同目录相对依赖node_modules/**deno cache 预填充的依赖闭包version.json。关键点是import mapdeno 的模块解析依赖import-map.json把裸模块名映射到闭包路径生产布局靠它才能解析节点里的 npm 依赖。构建脚本还区分了 dev / prod--dev只复制源码、跳过 deno cache开发时直接用项目根 node_modules——这样开发迭代快产物不脏。五、浏览器内核按平台分发只打包当前平台Chromium 内核体积大不能每个平台都塞一遍。electron-builder.yml 里 Windows、macOS、Linux 各自的extraResources只打包当前平台的目录winbrowser/win32→browser/win32macbrowser/darwin→browser/darwinlinuxbrowser/linux→browser/linux注释直接说明了意图“内置浏览器内核仅打包当前平台随包分发无需下载”。这套内置免费指纹浏览器内核随安装包走的设计从用户视角看就是免安装浏览器、开箱即用从工程视角看则是用 electron-builder 的按平台 extraResources 机制把包体翻倍的问题挡在了配置层。macOS 侧还配了 entitlements沙盒权限声明和相机/麦克风/文档目录的用途说明notarize: false关闭了公证本地分发场景的取舍Windows 侧 NSIS 允许用户改安装目录Linux 侧一次产出 AppImage / snap / deb 三个目标。六、一条命令的构建流水线最终打包不是散装的package.json 里把步骤串成了一条流水线npm run ensure:native npm run fetch:deno npm run build:worker npm run build即保证 sqlite3 原生二进制 → 下载当前平台 Deno → 构建 worker 运行时 → electron-vite 构建 → electron-builder 出包。前置步骤全部幂等重复执行不会重复下载或重复构建。小结这个项目打包设计最值得借鉴的地方是把分发什么、放哪个区、为什么用注释和脚本写得很清楚asar 管代码、asarUnpack 管必须在 asar 外还能执行的二进制、extraResources 管大件外部资源原生依赖用幂等脚本前置保证worker 运行时用构建而非复制的方式产出浏览器内核按平台分发。对要分发多运行时或原生依赖的跨平台桌面应用这份配置本身就是一份不错的参考模板。源码在 https://github.com/freerpa/freerpa 。