ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Electron 打包后 Office 预览失效?从 asar 执行位到插件注入的三个真实暗坑

Electron 打包后 Office 预览失效?从 asar 执行位到插件注入的三个真实暗坑 记录一次桌面工作台Electron DeepSeek Harness 运行时的工程实践。 全文只讲实现与排查过程代码片段可直接对照。一、背景我们在做一个通用的桌面工作台不绑定行业外层是 Electron 壳内层跑随包的 DeepSeek Harness 运行时 再把「本机文件浏览 文档预览」「终端」「会话」这些能力聚合到同一个窗口里。需求里最不起眼、却最容易出事的一条是在侧栏里直接预览本机文件包括 Word / Excel / PPT。听起来像调个 API实际排查下来踩了三个坑都是同一类问题——开发环境能跑打包安装后不能跑。本文把排查过程与修复方式记录完整。二、整体结构先交代清楚三层的职责后面的坑都出在层与层的边界上。┌─────────────────────────────────────────────┐ │ Electron 主进程Node │ │ · IPC文件系统只读操作 / 面板内预览 │ │ · 文档转换调用随包的文档引擎 CLI │ │ · 自动更新electron-updater 自建 CDN │ │ · 注入脚本把 UI 扩展注入渲染层 │ └───────────────┬─────────────────────────────┘ │ IPC ┌───────────────▼─────────────────────────────┐ │ 渲染层DSH Web UI跑在 127.0.0.1:port │ │ · 注入脚本侧栏面板、卡片、进度条 │ │ · 客户端插件用运行时插槽注册原生 UI │ └─────────────────────────────────────────────┘ │ 插件清单下发 ┌───────────────▼─────────────────────────────┐ │ 随包运行时Node │ │ · Cordis 插件体系 / profile / patch 层 │ │ · 文档引擎libreoffice-kit 平台二进制 │ └─────────────────────────────────────────────┘其中注入脚本与客户端插件是两套并行机制注入脚本主进程在页面就绪后executeJavaScript一段 IIFE直接操作 DOM。 适合改动官方 UI例如往「开始」面板里加一张卡片。客户端插件包在package.json里声明dsh.client运行时把./client下发给浏览器 在插件里通过插槽注册组件。适合有官方插槽可用的场景。这里有个容易卡住人的细节客户端插件是靠活动 loader 行触发的。 也就是说宿主半边必须导出一个可挂载的最小插件形态// lib/index.js —— 宿主半边客户端插件必须能被挂载才会下发 export const name shell-brand-mark; export function apply() { /* 功能在 lib/client.js */ }如果只写export const name ...而没有apply()这一行不算活动 loader 行 浏览器侧就不会收到客户端插件——表现为插件装了但完全没效果。三、坑 1asar 归档里的文件没有可执行位现象开发环境直接跑源码目录文档转换正常docx → PDF 成功打包安装后侧栏预览报Office 预览不可用直接跑转换 CLI返回{code:unavailable,error:Installed LibreOfficeKit executable is not executable.}排查先把配置有没有生效排掉运行时的配置 dump 里能看到转换服务已被加载 说明问题不在插件注册而在引擎自检。接着对比两处路径跑同一条命令# ① 源码目录里的引擎 —— 成功 node node_modules/deepseek-ai/libreoffice-kit/lib/cli.js convert --input a.docx --output a.pdf # → {backend:native,outputPath:...} # ② 打包后 asar 内的引擎 —— 失败 .../Resources/app.asar/.../libreoffice-kit/lib/cli.js convert ... # → {code:unavailable,error:Installed LibreOfficeKit executable is not executable.}原因很清晰引擎包通过require.resolve(engine-pkg/package.json)定位自己的二进制 在打包环境里拿到的是asar 归档内部的逻辑路径而归档里的文件模式是0644没有执行位 引擎自检status.mode 0o111判定失败。:app.asar.unpacked解包能恢复执行位但只解包一部分会缺依赖Cannot find package fontkit——半解包是经典陷阱。修复启动时把转换栈物化到真实目录配置指向这份真实路径// 1) 纯 JS 的转换器 依赖闭包 → 递归复制到 userData/office-runtime/kit/ // 2) 平台引擎包体积大→ 软链到 app.asar.unpacked 里的真实路径那里有执行位 // 3) 版本变化时整目录重建避免旧版本写入的路径残留 const realCli path.join(kitRoot, deepseek-ai/libreoffice-kit/lib/cli.js);验收就用同一条命令现在这条命令已进我们的冒烟测试$USER_DATA/office-runtime/bin/node \ $USER_DATA/office-runtime/kit/node_modules/deepseek-ai/libreoffice-kit/lib/cli.js \ convert --input t.docx --output t.pdf # → {backend:native,missingFonts:[],outputPath:/…/t.pdf}四、坑 2渲染层读不到file://面板内预览的第二层问题是浏览器安全模型 面板跑在http://127.0.0.1:port的页面里直接给img srcfile:///…会被拦。所以预览链路必须绕一圈全部走主进程渲染层点击文件 → IPC localfiles:preview → 主进程分类图片/PDF → base64 data URI 文本 → 直出截断到 300KB Office → 调转换栈转 PDF → 再转 data URI → 渲染层用 img / iframe 呈现两个工程细节值得记下来大小上限要显式设图片 24MB、PDF 60MB、文本 300KB 否则一个几百兆的 PDF 会把主进程内存拉爆。失败要给退路任何不支持的格式都返回{ok:false, error} 界面上同时给一个用系统默认程序打开的按钮而不是白屏。五、坑 3往 SPA 里注入 DOM 的时机官方 UI 的「开始」面板只在空白会话时渲染所以注入脚本不能页面一加载就找元素。我们第一版写成了轮询 30 秒、找不到就放弃 结果用户是几分钟后才新建会话的——卡片就不出现。正确做法是常驻监听 去抖function ensure() { if (Date.now() - last 400) return; // SPA 界面很热闹必须去抖 last Date.now(); if (document.getElementById(CARD_ID)) return; build(); // 面板一出现就补插用户切会话重绘也会补 } new MutationObserver(ensure).observe(document.documentElement, { childList: true, subtree: true }); setInterval(ensure, 2000); // 双保险顺带一个教训注入的 UI不要依赖宿主页面的 CSS 变量。 同一个变量名在深浅色主题下的取值可能不同一旦兜底值取自另一套主题 就会出现白底 浅灰字这种一眼可见的坏效果。我们的做法是给注入面板自带一套色板 按宿主的主题标记切换。六、发布工程一次真实的 CI 事故桌面应用的发布流水线是这样串的push tag → CI 四平台构建mac arm64/x64、win、linux → 上传自建 CDN对象存储 → 校验三份更新清单与产物可下载HTTP 200 大小 → 打包态冒烟测试含 UI 断言与截图 → 标记 Latest → 官网落地页同步版本号这里踩过一次比较典型的坑单元脚本用纯 Node 跑却依赖了 Electron 的 API。// 出错代码 const cli materializeKit(home, dir, app.getVersion()); // ❌ 纯 node 下 app 是字符串 // 修复 const version appVersion ?? (typeof app?.getVersion function ? app.getVersion() : dev);在 CI 的离线守卫脚本里用node执行不加载 Electronimport { app } from electron拿到的是可执行文件路径字符串 于是app.getVersion()抛TypeError四个平台里三个直接失败、发布任务被跳过。结论主进程模块里凡是涉及运行环境的取值都要显式传参 安全兜底 不要假设自己一定在 Electron 里被加载。七、最终形态现在这条链路的完整行为侧栏「开始」面板 快捷键⌘⇧O/CtrlShiftO随时打开本机文件浏览单击文件 → 面板内直接预览图片 / PDF / 文本 / Word / Excel / PPT双击文件 → 系统默认程序打开路径条可编辑、可前进后退、记住上次位置、一键复制路径Office 文档在本机转换不依赖在线服务八、可复用的几条经验打包产物 另一个运行环境文件权限、路径语义asar 逻辑路径、模块解析都可能不同 凡是引擎/二进制类依赖优先物化到可写的真实目录。跨进程边界传数据要给上限base64 传输尤其如此。在 SPA 里注入 UI用常驻监听而不是有限轮询并且自带样式不依赖宿主变量。主进程模块要能在纯 Node 下被加载环境相关的值显式传参。冒烟测试要覆盖用户的真实组合深浅色主题、旧数据升级、真实文件转换—— 我们后来把双主题截图、旧缓存升级、真实 docx→pdf 都加进了冒烟才把这几类问题钉住。本文所述均为本地运行能力调用模型服务时内容发往使用者自行配置的服务地址。
返回列表