鸿蒙PC迁移:使用Electron`pencil-master-ohos` 鸿蒙适配全记录

文章目录

    • 一、写在前面
    • 二、项目背景
    • 三、总体变化概览
      • [3.1 新增鸿蒙工程壳](#3.1 新增鸿蒙工程壳)
      • [3.2 新增构建脚本](#3.2 新增构建脚本)
      • [3.3 新增 Electron 启动引导](#3.3 新增 Electron 启动引导)
      • [3.4 补充鸿蒙 remote/dialog 适配层](#3.4 补充鸿蒙 remote/dialog 适配层)
    • [四、为什么 Pencil 选择保留桌面 Electron 编辑器](#四、为什么 Pencil 选择保留桌面 Electron 编辑器)
    • 五、鸿蒙适配核心路径
      • [5.1 第一步:同步 Pencil 原应用资源](#5.1 第一步:同步 Pencil 原应用资源)
      • [5.2 第二步:写入鸿蒙启动入口](#5.2 第二步:写入鸿蒙启动入口)
      • [5.3 第三步:切换用户数据目录](#5.3 第三步:切换用户数据目录)
      • [5.4 第四步:处理文件默认路径](#5.4 第四步:处理文件默认路径)
      • [5.5 第五步:建立 dialog IPC 桥](#5.5 第五步:建立 dialog IPC 桥)
      • [5.6 第六步:把 picker URI 转成真实路径](#5.6 第六步:把 picker URI 转成真实路径)
      • [5.7 第七步:构建 HAP](#5.7 第七步:构建 HAP)
      • [5.8 第八步:保留 PDF 导出,降级实体打印](#5.8 第八步:保留 PDF 导出,降级实体打印)
    • 六、问题困难解决
      • [6.1 困难一:原应用资源不能简单当静态页面处理](#6.1 困难一:原应用资源不能简单当静态页面处理)
      • [6.2 困难二:桌面数据目录在鸿蒙侧不能直接套用](#6.2 困难二:桌面数据目录在鸿蒙侧不能直接套用)
      • [6.3 困难三:Export 点击后无响应](#6.3 困难三:Export 点击后无响应)
      • [6.4 困难四:picker 返回 URI,导出器需要真实路径](#6.4 困难四:picker 返回 URI,导出器需要真实路径)
      • [6.5 困难五:桌面端能力需要边界清晰](#6.5 困难五:桌面端能力需要边界清晰)
      • [6.6 运行链路确认顺序](#6.6 运行链路确认顺序)
    • 七、构建与运行方式
    • 八、适配成果
    • 九、功能验证结果
    • 十、结语

一、写在前面

欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区 :https://harmonypc.csdn.net/

开源项目地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_pencil

欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper

环境搭建文章:https://blog.csdn.net/lbcyllqj/article/details/161286249?sharetype=blogdetail\&sharerId=161286249\&sharerefer=PC\&sharesource=lbcyllqj\&spm=1011.2480.3001.8118

这篇文章记录的是 Pencil 在 OpenHarmony / HarmonyOS Electron 环境中的适配过程。

Pencil 是一款开源 GUI 原型设计与图形绘制工具。它的核心界面由 Electron 承载,编辑区基于 XHTML、SVG、DOM 和一套 stencil 元件库实现。用户平时的使用路径很直接:打开画布、拖拽元件、编辑属性、保存工程文件、导出 PDF 或图片。

针对鸿蒙适配,这次没有把 Pencil 的编辑器改成 ArkTS 原生界面,而是选择了一条更接近原项目的路线:

保留 Pencil 原有 Electron 编辑器和 Node/Web 运行方式,通过 OpenHarmony Electron runtime 承载原应用资源,并补齐鸿蒙侧文件、对话框、导出和构建链路。

这样做的核心目标,是让 Pencil 在鸿蒙设备上不只是"窗口能打开",而是能完成绘图软件最基本的闭环:界面加载、元件可用、工程文件可读写、导出功能可继续走通、HAP 包可以稳定构建。

先说一点个人判断:Pencil 这种工具,真正的适配难点不在首屏。首屏出现只能说明 Electron runtime 和静态资源基本连上了。它更关键的价值在后半段:用户画完图以后,能不能保存,能不能导出,导出的 PDF 能不能在系统文档目录中找到。导出按钮一旦没反应,前面所有"能打开"的成果都会打折。

所以这次适配的重点放在三条线上:

  • 保留原有 Pencil 编辑器和 stencil 元件库
  • 建立鸿蒙 HAP 工程、资源同步和命令行构建链路
  • 修通文件对话框、默认路径和 PDF 导出这类桌面软件高频能力

后面所有修改也都围绕这个目标展开:确保进入 HAP 的 app/ 资源能在 OpenHarmony Electron runtime 中启动,确保 Pencil 的配置和用户资源写入鸿蒙可用目录,确保 Export Document 不是停在界面层,而是真的能进入系统保存文件选择器并写出文件。


二、项目背景

Pencil 项目中的应用描述是:

text 复制代码
An open-source GUI prototyping tool that is available for ALL platforms.

当前应用版本为:

text 复制代码
3.1.1

它的仓库结构里和鸿蒙适配关系最密切的部分包括:

  • app/:Pencil 原 Electron 应用主体
  • app/index.js:Electron 主进程入口
  • app/app.xhtml:Pencil 主界面
  • app/pencil-core/:编辑器核心逻辑
  • app/stencils/:内置元件库
  • scripts/:鸿蒙资源同步和 HAP 构建脚本
  • ohos_hap/:本次接入的鸿蒙 HAP 工程

这种项目的特点是:

  1. 编辑器不是普通静态页面,而是带 Node/Electron 能力的桌面应用
  2. 图形编辑依赖大量 DOM、SVG、XHTML 和历史工具逻辑
  3. 文件保存、导入、导出、PDF 生成都是核心用户路径
  4. 内置元件库和用户扩展资源需要稳定加载
  5. 桌面截图、全局快捷键、实体打印等能力在鸿蒙侧需要降级处理

所以本次适配的切入点不是 Web 版静态产物,而是 app/ 原应用资源。也就是说,Pencil 的鸿蒙版尽量沿用桌面端的编辑体验,只在系统能力边界上做适配和替换。

这里的取舍比较关键。Pencil 原来的编辑器已经形成了一套完整生态:.ep.epz.epgz 工程文件,Common Shapes、Android、iOS、Web 等 stencil 集合,PDF/PNG/SVG/HTML 等导出器,以及用户自定义字体、私有收藏、导入模板等扩展能力。如果直接重写 UI,短期内很难把这些细节重新补齐。

因此这次采用的是"原编辑器整体承载 + 鸿蒙能力补桥"的方式。


三、总体变化概览

3.1 新增鸿蒙工程壳

适配后的项目新增或维护了:

text 复制代码
ohos_hap/
├── AppScope/
├── electron/
├── web_engine/
└── build-profile.json5

OpenHarmony Electron runtime 最终读取的应用资源目录是:

text 复制代码
ohos_hap/web_engine/src/main/resources/resfile/resources/app

这个目录中会包含同步后的 Pencil 运行资源:

text 复制代码
main.js
index.js
app.xhtml
pencil-core/
stencils/
node_modules/
package.json

3.2 新增构建脚本

项目根目录的 package.json 中新增了鸿蒙相关命令:

json 复制代码
{
  "build:ohos": "node scripts/build-ohos-package.js",
  "ohos:sync": "OHOS_PENCIL_OUT=ohos_hap/web_engine/src/main/resources/resfile/resources/app npm run build:ohos",
  "ohos:build": "node scripts/build-ohos-hap.js"
}

这三条命令分别对应:

  • 生成 Pencil 鸿蒙运行资源
  • app/ 同步到 HAP 的 Electron runtime 资源目录
  • 安装鸿蒙依赖并调用 Hvigor 构建 HAP

3.3 新增 Electron 启动引导

同步脚本会在 HAP 资源目录写入:

text 复制代码
resources/app/main.js

这个文件负责设置鸿蒙运行标记和默认降级开关:

js 复制代码
process.env.PENCIL_OPENHARMONY = '1';
process.env.PENCIL_DISABLE_SCREEN_CAPTURE = process.env.PENCIL_DISABLE_SCREEN_CAPTURE || '1';
process.env.PENCIL_DISABLE_NATIVE_PRINT = process.env.PENCIL_DISABLE_NATIVE_PRINT || '1';
process.env.PENCIL_DISABLE_GLOBAL_SHORTCUTS = process.env.PENCIL_DISABLE_GLOBAL_SHORTCUTS || '1';
process.env.PENCIL_DISABLE_GPU = process.env.PENCIL_DISABLE_GPU || '1';

require('./index.js');

也就是说,鸿蒙包里的 main.js 只做环境准备,真正的应用启动仍然回到 Pencil 原来的 index.js

3.4 补充鸿蒙 remote/dialog 适配层

Pencil 原代码里有不少地方会通过 Electron remote/dialog 能力访问窗口、路径、文件选择器和消息框。鸿蒙环境下这条链路不能完全照搬,所以新增了 app/ohos-remote.js 作为兼容层。

它主要做三件事:

  • 给渲染进程提供可用的 app.getPath()dialog.showSaveDialog()dialog.showOpenDialog() 等接口
  • 把渲染进程的文件对话框请求转成 IPC
  • 在 Electron 能力不完整时提供可控 fallback,避免直接崩溃

四、为什么 Pencil 选择保留桌面 Electron 编辑器

Pencil 和许多"Web 页面型"项目不太一样,它不是把一个 SPA 放进窗口就结束。它的编辑器长期围绕 Electron、XHTML、SVG 和本地文件能力演进。

本次鸿蒙适配中重点完成:

  • Pencil 主窗口启动
  • XHTML 编辑器加载
  • stencils 元件库加载
  • .ep / .epz 等工程文件路径适配
  • 用户配置和用户资源目录适配
  • 文件打开、保存、导出对话框适配
  • PDF 导出链路保留
  • HAP 构建链路打通

因此路线可以概括为:

app/ 原应用资源 + 鸿蒙 Electron runtime + HAP 工程承载 + 文件/导出能力补桥。

如果改走 ArkTS 重写,短期内可能能做出一个"看起来像 Pencil"的界面,但很难完整覆盖 Pencil 的真实工作流。比如元件库、连接线、富文本、导入导出模板、用户字体、私有收藏、PDF 导出这些能力,都不是简单重画 UI 就能解决的。

保留 Electron 编辑器的好处是边界清楚:编辑器内部的成熟逻辑尽量不动,鸿蒙差异集中在启动、路径、对话框、打印/导出、桌面能力降级这些点上。


五、鸿蒙适配核心路径

5.1 第一步:同步 Pencil 原应用资源

scripts/build-ohos-package.js 会把项目中的 app/ 复制到鸿蒙 runtime 资源目录。

同步时会过滤掉一些不应该直接进入运行包的内容:

js 复制代码
return name !== ".DS_Store" &&
  name !== ".cache" &&
  name !== "node_modules" &&
  name !== "package-lock.json" &&
  name !== "yarn.lock";

随后脚本会重新写入 runtime 使用的 package.jsonpackage-lock.json,并在目标目录安装运行时依赖。这样做可以避免把开发环境中的缓存和旧依赖状态直接带进 HAP。

5.2 第二步:写入鸿蒙启动入口

同步完成后,脚本会生成新的 main.js。这个文件不是 Pencil 业务代码,而是鸿蒙运行环境的启动引导。

它设置 PENCIL_OPENHARMONY=1 后,再进入原来的主进程:

js 复制代码
require('./index.js');

后续代码可以通过这个标记判断当前是否运行在鸿蒙环境里,并启用对应的路径、窗口、对话框和能力降级逻辑。

5.3 第三步:切换用户数据目录

桌面版 Pencil 默认可以把配置和用户资源写到用户主目录,比如 .pencil。HAP 环境下不能继续假设这些路径一定可写。

适配后通过 app/ohos-adapter.js 统一处理数据目录:

  • 非鸿蒙环境继续使用桌面路径
  • 鸿蒙环境优先使用 app.getPath("userData")
  • 如果运行时拿不到路径,再退回到可控的 .pencil-ohos 目录

这条路径承载的是配置、最近文档、用户 stencils、私有收藏、用户字体等长期数据。它不显眼,但决定了应用重启后状态是否还能保留。

5.4 第四步:处理文件默认路径

Pencil 的打开、保存、导出都需要默认路径。鸿蒙版优先从下面几类路径里取可用目录:

text 复制代码
documents
downloads
userData

这样用户导出 PDF 或保存工程时,默认不会落到资源目录或不可写目录里。

尤其是导出场景,默认路径通常会类似:

text 复制代码
/storage/Users/currentUser/Documents/Output.pdf

这也为后面的文件保存选择器提供了合理的默认文件名和默认目录。

5.5 第五步:建立 dialog IPC 桥

Pencil 的导出流程会调用 dialog.showSaveDialog()。在桌面端这很普通,但鸿蒙环境里渲染进程不能直接依赖完整 remote 能力。

适配后渲染侧由 app/ohos-remote.js 发起:

text 复制代码
pencil-ohos-dialog

主进程 app/index.js 接收后调用 Electron 的 dialog 方法,并把结果规范成 Pencil 能识别的格式:

js 复制代码
{ canceled: false, filePath: "/实际可写路径/Output.pdf" }

这一步是 Export Document 能继续往下走的关键。

5.6 第六步:把 picker URI 转成真实路径

鸿蒙文件选择器返回的结果有可能是 URI,例如:

text 复制代码
file://docs/...

Pencil 后续写文件需要的是 Node 可以直接处理的路径,所以 ArkTS 侧 DialogAdapter.ets 里增加了转换:

ts 复制代码
let decoded = decodeURI(uriOrPath);
let uri = new fileUri.FileUri(decoded);
return uri.path;

这样 DocumentViewPicker.save 返回后,主进程拿到的是后续 fs 和 PDF 导出器可以使用的路径。

5.7 第七步:构建 HAP

scripts/build-ohos-hap.js 会按顺序执行:

  1. 调用 build-ohos-package.js 同步 Pencil 资源
  2. ohos_hap 下执行 ohpm install
  3. 查找 DevEco / Hvigor
  4. 查找 DevEco SDK
  5. 调用 hvigor assembleHap

这就把"Pencil 应用资源同步"和"鸿蒙 HAP 构建"串成了一条完整链路。

5.8 第八步:保留 PDF 导出,降级实体打印

Pencil 桌面端原来既有 PDF 导出,也有实体打印能力。鸿蒙适配中没有默认启用实体打印,而是优先保留 PDF 导出。

原因很简单:PDF 是绘图软件最常用、最容易验证、也最适合跨设备流转的输出格式。实体打印依赖系统打印服务和运行时能力,先默认关闭更稳。


六、问题困难解决

Pencil 适配中最大的困难是:

让一个带桌面软件习惯的 Electron 绘图工具,在鸿蒙 HAP 环境里继续完成文件选择、路径写入和 PDF 导出,而不是只停留在"界面能打开"。

这个问题主要体现在几个方面。

6.1 困难一:原应用资源不能简单当静态页面处理

Pencil 的 app/ 里不只是 HTML、CSS、JS 静态资源。它还包含 Electron 主进程、Node 依赖、stencil 元件库、导出器、字体和模板相关逻辑。

如果只把 app.xhtml 当成普通页面加载,很快会遇到:

  • 主进程能力缺失
  • Node 模块不可用
  • 元件库路径不稳定
  • 导出器找不到运行上下文
  • 配置目录写入失败

解决方式是把 app/ 作为 Electron 应用资源整体同步进 HAP,而不是拆成单页资源。同步后再写入鸿蒙专用 main.js,让它回到原来的 index.js 启动。

6.2 困难二:桌面数据目录在鸿蒙侧不能直接套用

桌面端常见路径如用户主目录、下载目录、隐藏配置目录,在 HAP 中都需要重新确认。Pencil 如果继续把用户配置、stencil collection、字体、最近文件写到桌面路径,轻则保存失败,重则应用重启后状态丢失。

解决方式是把数据路径集中到 ohos-adapter.js,让鸿蒙环境优先使用运行时提供的 userData,并为 documentsdownloads 等常见用户目录设置 fallback。

这样打开、保存、导入、导出都不用在各个业务点散落判断。

6.3 困难三:Export 点击后无响应

这次排查中最典型的问题,是点击 Export Document 里的 Export 后界面没有反应。

日志里能看到这样的信息:

text 复制代码
[PencilOhos][remote-shim] showSaveDialog fallback
{"title":"Select output file","defaultPath":"/storage/Users/currentUser/Documents/Output.pdf", ...}

这说明导出流程已经进入保存文件阶段,并且默认文件名 Output.pdf 也准备好了。问题不在 Pencil 的导出向导,也不在 PDF 类型选择,而是在 showSaveDialog 没有真正接到鸿蒙系统保存文件选择器。

原来的 fallback 返回了:

js 复制代码
{ canceled: true, filePath: "" }

对 Pencil 来说,这就等价于用户取消导出,所以后续 PDF 写盘不会继续执行。

解决方式是给 dialog 补 IPC 桥:

  • 渲染进程调用 ohos-remote.js
  • ohos-remote.js 发送 pencil-ohos-dialog
  • 主进程 index.js 调用 Electron dialog
  • 返回规范化后的 filePath
  • Pencil 导出器继续写入文件

6.4 困难四:picker 返回 URI,导出器需要真实路径

文件保存框弹出来以后,还剩一个容易被忽略的问题:鸿蒙 picker 返回值可能是 URI,不一定是 Node 可以直接写入的路径。

如果把 file://docs/... 原样传给 Pencil,后续 fs 或 PDF 导出器仍然可能失败。

解决方式是在 DialogAdapter.ets 中增加路径转换,把 picker 返回值转换成真实路径后再回传给 JS 层。

这个改动不大,但它补上的是最后一段路:从"用户选择了保存位置"到"PDF 真的写入文件系统"。

6.5 困难五:桌面端能力需要边界清晰

Pencil 桌面端还包含一些强系统能力:

  • 全局快捷键
  • 桌面截图
  • 屏幕取色
  • 实体打印
  • 外部图片编辑器
  • 系统剪贴板图片读写

这些能力在鸿蒙侧不能假设都可用。适配时的处理方式是:

  1. 默认关闭全局快捷键
  2. 默认关闭桌面截图和屏幕取色入口
  3. 默认关闭实体打印
  4. 优先保留 PDF 文件导出
  5. 剪贴板、外部编辑器等能力继续留待真机逐项验证

这个边界要提前划清,否则应用可能在不常用功能上崩溃,反过来影响主流程稳定性。

6.6 运行链路确认顺序

Pencil 在鸿蒙中的运行链路可以按下面顺序确认:

  1. app/ 是否同步到 resources/app
  2. resources/app/main.js 是否写入鸿蒙启动标记
  3. resources/app/package.jsonmain 是否指向 main.js
  4. index.js 是否正常创建主窗口
  5. app.xhtml 是否完成加载
  6. pencil-core/stencils/ 是否能读取
  7. 用户数据目录是否切到鸿蒙可写路径
  8. showOpenDialogshowSaveDialog 是否进入 IPC 桥
  9. DocumentViewPicker.save 是否返回路径
  10. PDF 导出器是否写出最终文件

这个顺序可以把问题拆清楚:先确认资源和窗口,再确认数据路径,最后确认导出链路。否则只看界面,很容易漏掉文件系统和系统对话框问题。


七、构建与运行方式

同步资源:

bash 复制代码
cd ~/XM/pencil-master-ohos
npm run ohos:sync

构建 HAP:

bash 复制代码
cd ~/XM/pencil-master-ohos
npm run ohos:build

HAP 输出目录:

text 复制代码
ohos_hap/electron/build/default/outputs/default/

当前项目中可以看到已生成的签名 HAP:

text 复制代码
electron-default-signed.hap

同时也会生成未签名包:

text 复制代码
electron-default-unsigned.hap

八、适配成果

pencil-master-ohos 已经完成:

  • 鸿蒙 Electron HAP 工程接入
  • Pencil 原 app/ 资源同步到 HAP
  • 鸿蒙专用 main.js 启动引导
  • PENCIL_OPENHARMONY 运行环境标记
  • 用户配置和用户资源路径迁移
  • 默认文档/下载/用户数据路径适配
  • ohos-remote.js remote/dialog 兼容层
  • 文件打开、保存、消息框 IPC 桥接
  • 鸿蒙 picker URI 到真实路径转换
  • PDF 导出保存链路修复
  • 全局快捷键、截图、取色、实体打印默认降级
  • HAP 命令行构建链路

这次适配的结果不是把 Pencil 改成一个新的鸿蒙页面,而是尽量保留了原编辑器的操作方式,同时把鸿蒙侧必须补的系统能力补到可用状态。


九、功能验证结果

pencil-master-ohos 按实际绘图工具使用场景完成了功能验证。

第一组是基础启动:

  • 应用能在 OpenHarmony Electron runtime 中启动
  • 主窗口能创建并显示
  • app.xhtml 能正常加载
  • 顶部菜单、画布、页面树、属性区域能显示

第二组是编辑器资源:

  • 内置 stencil 元件库能加载
  • 常用图形可以拖拽到画布
  • 页面树和画布编辑逻辑可用
  • SVG 绘制、属性调整和连接线能力保持原有表现

第三组是文件和数据:

  • 用户配置写入鸿蒙可用目录
  • 默认打开/保存目录不再指向不可写资源目录
  • 用户 stencils、私有收藏、字体等数据路径进入适配层
  • 最近文件和用户数据具备继续验证基础

第四组是导出链路:

  • Export Document 弹窗可以正常打开
  • PDF 类型和导出模板由原 Pencil 逻辑提供
  • 点击 Export 后能进入系统保存文件选择器
  • 默认文件名 Output.pdf 能传入保存框
  • picker 返回结果能转换成 Node 可写路径
  • PDF 导出流程可以继续写盘

第五组是构建交付:

  • npm run ohos:sync 能同步应用资源
  • npm run ohos:build 能触发 ohpm 和 Hvigor 构建
  • HAP 输出目录生成 electron-default-signed.hap
  • 构建中出现的历史警告不阻断打包

第六组是仍需真机继续确认的能力:

  • 大文件 PDF 导出的耗时和内存占用
  • PNG / SVG / HTML 等其他导出类型
  • 剪贴板图片读写
  • 导入外部 stencil collection
  • 字体导入
  • 鼠标右键、拖拽、焦点和输入法细节
  • 实体打印是否有条件恢复

十、结语

pencil-master-ohos 的适配,本质上是一次"桌面图形编辑器鸿蒙承载"实践。

它的重点不是把界面截图跑出来,而是建立一个可继续迭代的完整运行包:

  • 工程能构建
  • 应用能启动
  • 编辑器能加载
  • 元件库能使用
  • 用户数据有可写目录
  • 文件对话框能进入系统能力
  • PDF 导出能写到用户选择的位置

Pencil 这类软件的迁移有一个明显特点:适配问题往往不在第一眼能看到的地方,而在用户完成工作的最后几步。画布打开了不代表能交付,导出按钮能点也不代表能写盘。真正让它从"能跑"变成"能用"的,是路径、对话框、权限、文件格式和导出链路这些细小但连续的环节。

这次适配把 Pencil 原有 Electron 编辑器放进了鸿蒙 HAP,也把导出无响应的问题定位到 showSaveDialog 链路,并通过 IPC 桥和 URI 转路径修通。后续继续补齐其他导出格式、剪贴板、外部资源导入和真机交互细节,Pencil 在鸿蒙上的可用性还可以继续往前走。