欢迎光临
我们一直在努力

一个 Electron + Deno + Chromium 的桌面应用,是怎么把三套运行时塞进安装包的

桌面 RPA 有一个大部分 Electron 应用不会遇到的问题:除了应用本身,它还得把执行引擎和浏览器内核一起分发给用户。作为一个开源免费 RPA 项目,FreeRPA(https://github.com/freerpa/freerpa)的打包配置就是围绕这个问题展开的——它在 electron-builder.yml 和几个 scripts 里,把三套运行时和一个原生数据库模块安排得比较清楚。这篇就记录它的分发策略,以及背后的工程原因。

一、先看它要分发哪些东西

一个 RPA 客户端运行起来,需要的东西比普通应用多:

  • Electron 应用壳(主进程 + 渲染进程代码,Vue 3 那套);
  • Deno 运行时(执行引擎的宿主,作为独立二进制随包分发);
  • Worker 运行时资源(节点执行器、引擎源码、依赖闭包);
  • Chromium 浏览器内核(指纹浏览器内核,随包内置);
  • sqlite3 原生模块(N-API 二进制,本地数据层依赖)。

难点在于:这些组件对"打包进 asar 还是放 asar 外"的诉求完全不同。

二、三个分发区:asar / asarUnpack / extraResources

electron-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 → deno:Deno 二进制;
  • resources/worker → worker:worker 运行时资源,filter 排除了 node_modules;
  • resources/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>/V<n>/execute.js,含同目录相对依赖);
    • node_modules/**:deno cache 预填充的依赖闭包;
    • version.json。

    关键点是import map:deno 的模块解析依赖 import-map.json 把裸模块名映射到闭包路径,生产布局靠它才能解析节点里的 npm 依赖。构建脚本还区分了 dev / prod:–dev 只复制源码、跳过 deno cache,开发时直接用项目根 node_modules——这样开发迭代快,产物不脏。

    五、浏览器内核:按平台分发,只打包当前平台

    Chromium 内核体积大,不能每个平台都塞一遍。electron-builder.yml 里 Windows、macOS、Linux 各自的 extraResources 只打包当前平台的目录:

    • win:browser/win32 → browser/win32
    • mac:browser/darwin → browser/darwin
    • linux:browser/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 。

    赞(0)
    未经允许不得转载:171主机测评 » 一个 Electron + Deno + Chromium 的桌面应用,是怎么把三套运行时塞进安装包的
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址