鸿蒙PC使用electron迁移:Joplin Electron 桌面适配全记录
一、写在前面
欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区:https://harmonypc.csdn.net/
适配仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_joplin
欢迎在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
这篇文章记录的是 Joplin 迁移到 OpenHarmony / HarmonyOS PC 过程中的一次完整适配实践。文章会按 Electron 桌面软件迁移的思路来写,重点放在鸿蒙 PC 的工程承载、窗口形态、资源打包、系统能力桥接和桌面交互改造上。
Joplin 是一个成熟的开源笔记软件,功能并不只是“写文本”。它包含笔记本、笔记列表、Markdown 编辑与预览、附件资源、图片、绘图、同步、加密、分享、文件系统、SQLite、WebView、语音输入等能力。也正因为这些能力都比较贴近系统底层,所以迁移到鸿蒙 PC 时,难点不在于让一个页面显示出来,而在于让整个笔记应用真正具备可用的本地数据和桌面窗口体验。
这次适配的目标不是把界面简单塞进 HAP,而是尽量保留 Joplin 桌面笔记软件的使用方式:左侧管理笔记本,中间浏览笔记列表,右侧查看和编辑内容,同时把文件系统、数据库、附件、图片选择、绘图、语音入口等能力接到鸿蒙 PC 环境里。
底层实现中确实会涉及 Harmony 原生桥、ArkTS、C++ 方法表和一些 OpenHarmony 版本的三方库,但这些不是文章主线。本文会把它们放在“三方库适配”和“原生桥接问题”里说明,避免把一篇鸿蒙 PC 桌面适配文章写成底层框架介绍。
先说结论:这次适配已经完成了 Joplin 在鸿蒙 PC 上的基础运行闭环。应用可以在 PC 窗口中启动,显示左侧笔记本、中间笔记列表、右侧笔记内容区域;可以选择笔记、查看内容、进入编辑;可以创建笔记本和笔记;附件、相机图片选择、绘图入口、语音转文字入口也做了鸿蒙侧适配。

二、项目背景
Joplin 的代码规模比较大,它不是一个单包应用,而是一个 monorepo。和本次鸿蒙迁移关系最密切的部分主要有:
packages/
├── app-mobile/
│ ├── components/
│ ├── utils/
│ ├── harmony/
│ ├── index.harmony.js
│ ├── tsconfig.harmony.json
│ └── tools/buildHarmonyBundle.js
├── lib/
├── renderer/
├── tools/
└── ...
其中 packages/app-mobile 是这次鸿蒙 PC 适配的主要入口。对外呈现上,我们希望它更接近一个桌面笔记软件:应用启动后直接进入三栏工作区,而不是只保留窄屏设备上的页面跳转体验。因此这里要处理两类问题:
第一类是运行时和系统能力问题。SQLite、文件系统、网络、剪贴板、WebView、Share、Picker、SVG、Zip 等能力在桌面 Electron 里通常有比较直接的 Node 或系统 API 路径,但放到鸿蒙 HAP 后,需要重新接到鸿蒙侧的系统能力和应用沙箱路径上。
第二类是产品形态问题。PC 端需要更稳定的多栏布局。Joplin 作为笔记应用,如果在鸿蒙 PC 上仍然只有单页跳转,会非常别扭。所以这次不仅要能启动,还要把 PC 上“左侧笔记本 + 中间笔记列表 + 右侧内容/编辑区”的主工作流适配出来。
这次新增或重点调整的文件包括:
packages/app-mobile/harmony/
packages/app-mobile/index.harmony.js
packages/app-mobile/tsconfig.harmony.json
packages/app-mobile/tools/buildHarmonyBundle.js
packages/app-mobile/utils/harmony/HarmonyNative.ts
packages/app-mobile/utils/pickDocument.harmony.ts
packages/app-mobile/components/ExtendedWebView/index.harmony.tsx
packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.harmony.tsx
packages/app-mobile/components/voiceTyping/SpeechToTextBanner.harmony.tsx
packages/app-mobile/components/screens/Note/Note.tsx
packages/app-mobile/components/screens/Notes/Notes.tsx
packages/app-mobile/components/screens/folder.tsx
三、整体迁移路线
这次迁移可以拆成四层。
第一层是 Harmony HAP 宿主工程。它相当于鸿蒙侧的应用容器,负责把 Joplin 的桌面应用资源、运行入口和系统权限组织进一个可安装的鸿蒙包里。它位于:
packages/app-mobile/harmony
这里负责 EntryAbility、ArkTS 页面入口、native bridge、HAP 构建和权限声明。
第二层是鸿蒙侧的桌面运行承载层。这里会注册一系列 OpenHarmony 版本的能力包,把原来桌面软件依赖的剪贴板、网络、文件选择、图片选择、WebView、SQLite、SVG、压缩等能力接到鸿蒙环境里:
clipboard
network info
device info
document picker
file viewer
image picker
in-app browser
locale
permissions
safe area
share
SQLite
SVG
WebView
zip archive
第三层是 Joplin 自己的 Harmony native bridge,也就是 JoplinHarmony。它负责把 Joplin 业务里需要的系统能力统一暴露给应用层,例如:
getConstants
fsReadFile / fsWriteFile / fsStat / fsReadDirStats
sqliteOpen / sqliteExec / sqliteSelectAll
fetchBlob / uploadBlob
networkInfo
pickDocument
shareFile
takePicture
speechToText
第四层是 Joplin JS / TS 业务层的 Harmony 分支。通过 .harmony.ts、.harmony.tsx、MobilePlatform.Harmony、JOPLIN_TARGET_PLATFORM=harmony 等方式,把原有多平台分支改造成能在 Harmony PC 上运行的路径。

四、鸿蒙工程搭建
4.1 HAP 工程结构
本次鸿蒙工程放在:
packages/app-mobile/harmony
主要结构如下:
harmony/
├── AppScope/
├── entry/
│ ├── src/main/ets/
│ │ ├── entryability/EntryAbility.ets
│ │ ├── pages/Index.ets
│ │ └── rn/
│ │ ├── JoplinHarmonyModule.ets
│ │ ├── JoplinHarmonyPackage.ets
│ │ ├── JoplinHarmonyTextPromptHost.ets
│ │ └── JoplinHarmonyTextPromptService.ets
│ ├── src/main/cpp/
│ │ ├── JoplinHarmonyTurboModule.cpp
│ │ ├── JoplinHarmonyTurboModule.h
│ │ ├── JoplinHarmonyPackage.h
│ │ └── CMakeLists.txt
│ └── src/main/resources/rawfile/
│ ├── bundle.harmony.js
│ ├── assets/
│ └── fonts/
└── native-contract/
└── JoplinHarmonyModule.d.ts
module.json5 中需要声明 PC 窗口支持:
"deviceTypes": [
"2in1",
"tablet"
],
"supportWindowMode": [
"fullscreen",
"split",
"floating"
],
"minWindowWidth": 960,
"minWindowHeight": 640
这一步非常重要。Joplin 本身是信息密度较高的笔记软件,如果窗口最小宽高不受控,PC 端三栏布局很容易被压坏,出现标题、列表、编辑区重叠的问题。
4.2 JS Bundle 加载策略
Index.ets 里使用 AnyJSBundleProvider,按顺序尝试加载:
packaged hermes_bundle.hbc
packaged bundle.harmony.js
/data/storage/el2/base/files/bundle.harmony.js
Metro dev server
开发阶段可以走 Metro:
yarn workspace @joplin/app-mobile start:harmony
离线安装包需要先生成 bundle:
node packages/app-mobile/tools/buildHarmonyBundle.js
这里遇到过一个非常隐蔽的问题:只运行 tsc --noEmit 是不够的。因为最终 HAP 里的 bundle.harmony.js 来自已编译产物,如果只做类型检查,不生成新的 JS,HAP 里可能仍然是旧逻辑。比如相机入口已经在 TS 中改成系统图片选择,但包里还是旧逻辑,就是这个原因。
所以最后形成的构建顺序是:
node_modules/.bin/tsc --project packages/app-mobile/tsconfig.harmony.json
node packages/tools/gulp/tasks/updateIgnoredTypeScriptBuildRun.js
node packages/app-mobile/tools/buildHarmonyBundle.js
cd packages/app-mobile/harmony
DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
HOS_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony \
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
--mode module \
-p module=entry@default \
-p product=default \
assembleHap \
--analyze=normal \
--parallel \
--incremental \
--daemon
五、三方库适配
Joplin 的三方库适配是这次迁移里最耗精力的部分之一。
Electron 桌面应用迁到鸿蒙 PC 时,很多原本在桌面系统里天然存在的能力,都要重新确认一遍:剪贴板怎么接、文件选择器怎么弹、SQLite 数据库放在哪里、WebView 的消息通信是否一致、附件资源怎么落盘、压缩库是否能工作。Joplin 依赖的能力面比较宽,所以三方库适配不能只看能不能编译,还要看运行时行为是否符合桌面笔记软件的预期。
本次用到的三方库可以分成几类。
5.1 已有 OpenHarmony 版本的包
这类能力优先接入已有 OpenHarmony 版本的实现:
剪贴板
网络状态
设备信息
文档选择
文件预览
图片选择
内置浏览器
语言和区域
权限
安全区域
分享
SQLite
SVG
WebView
Zip
接入时主要做三件事:
- 在
package.json里加入对应 OpenHarmony 依赖 - 在鸿蒙入口中注册对应 package
- 在 JS 侧根据 Harmony 平台走对应分支或 polyfill
比如 SVG 能力不只是一个 UI 库。后面绘图功能无法继续走 WebView 时,就用它实现了一个 Harmony 原生绘图面板。
5.2 Joplin 自己封装的 native bridge
并不是所有能力都能直接靠三方库解决。Joplin 内部有大量平台能力被封装到自己的 shim 或 native module 里,例如:
- app/cache/external 路径
- SQLite 数据库
- 文件读写、复制、移动、删除、stat
- 文件分片读取
- HTTP 下载和上传
- blob 转换
- md5、随机数、crypto、RSA
- 文档选择
- 图片选择
- 分享文件
- 网络状态
- 相机、录音、语音转文字
这些能力通过 utils/harmony/HarmonyNative.ts 定义 JS contract,再由 ArkTS 的 JoplinHarmonyModule.ets 实现。
但只写 ArkTS 还不够。鸿蒙桥接层还需要在 C++ 方法表里暴露方法。例如这次语音转文字一开始 ArkTS 里已经写了:
speechToText
speechToTextFinish
speechToTextCancel
但模拟器点击录音入口时仍然报:
Harmony native method JoplinHarmony.speechToText is not implemented.
原因不是语音识别本身不可用,而是 JoplinHarmonyTurboModule.cpp 里没有把这三个方法加入 methodMap_。
最后补上:
ARK_ASYNC_METHOD_METADATA(speechToText, 1),
ARK_ASYNC_METHOD_METADATA(speechToTextFinish, 0),
ARK_ASYNC_METHOD_METADATA(speechToTextCancel, 0),
这类问题在鸿蒙 PC 适配中特别容易踩:JS contract、ArkTS implementation、C++ method metadata 三层必须一致。只改其中一层,运行时就会出现“方法不存在”或参数不匹配。
5.3 需要降级或替换的能力
部分能力在 Harmony PC 第一阶段没有完全等价的实现,需要换思路:
- 相机入口在 PC 上改为 Harmony 系统图片选择器
- WebView 绘图编辑器在鸿蒙运行环境下显示异常,改为 SVG 画板
- 录音附件不再直接走音频录制,先接入 Harmony 语音转文字入口
- Whisper 本地模型语音输入不适合第一阶段迁移,后续再按 Harmony native 能力补齐
- OneDrive OAuth 原生 redirect capture 还需要后续继续做
- tar/zip、资源导入导出、WebView postMessage parity 也需要真机继续验证
六、PC 端界面适配
Joplin 作为桌面笔记软件,PC 端体验的关键是信息密度和连续操作。迁到鸿蒙 PC 后,如果仍然只保留窄屏单页跳转,会出现几个明显问题:
- 选择笔记后右侧不显示内容
- 编辑区无法聚焦或无法编辑
- 新建笔记本流程只会创建到“欢迎”上下文
- 左侧菜单、中间列表、右侧内容区宽度不稳定
- 底部附件、录音、相机、绘图按钮容易被布局挤压
- 某些区域在窗口变化时出现空白或叠层问题
这次主要围绕三栏工作流做了适配:
左侧:笔记本、标签、设置、同步
中间:当前笔记本下的笔记列表和快捷操作
右侧:笔记标题、正文显示、编辑入口和预览
其中比较关键的是:PC 上选中一条笔记,右侧必须立即呈现内容,并且能进入编辑状态。这个行为在窄屏设备上可以通过页面跳转完成,但 PC 上需要让 note list 和 note detail 同时存在。
这部分主要调整了:
packages/app-mobile/components/app-nav.tsx
packages/app-mobile/components/screens/Notes/Notes.tsx
packages/app-mobile/components/screens/Note/Note.tsx
packages/app-mobile/components/NoteList.tsx
packages/app-mobile/components/NoteItem.tsx
packages/app-mobile/components/screens/folder.tsx
packages/app-mobile/root.tsx
packages/lib/components/shared/note-screen-shared.ts
适配完成后,PC 端可以保持左侧导航不丢失,中间列表可选中,右侧内容可显示和编辑,整体更接近桌面笔记软件的使用方式。
七、附件、相机、绘图和语音入口
7.1 相机入口改为 Harmony 图片选择
在 PC 端,尤其是鸿蒙 PC 形态,用户更自然的操作是从系统图库或文件选择器中选择图片,而不是直接进入一个全屏拍照界面。
所以这次把 Harmony 平台下的 TakePhoto 分支改成:
pickDocument({
multiple: true,
preferCamera: true,
mediaType: 'image',
});
并在 Harmony native picker 中根据 mediaType 设置:
photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
这样点击底部“相机”按钮时,打开的是 Harmony 系统图片选择器,而不是不适合 PC 窗口的拍照页面。

7.2 绘图从 WebView 改为 Harmony SVG 画布
Joplin 原来的绘图编辑器更依赖 WebView 和 JS 注入。但在鸿蒙 HAP 运行环境里,WebView 的注入、资源加载和 postMessage 行为还需要进一步验证。实际调试时,绘图入口曾经出现点击后没有明显反应、编辑器区域空白的问题。
为了解决这个问题,新增了:
packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.harmony.tsx
这个 Harmony 专用实现不再依赖 WebView,而是用 SVG 能力做一个轻量绘图面板。功能包括:
- 可见画布
- 颜色选择
- 撤销
- 清空
- 取消
- 保存
- 保存为 SVG 资源并插入笔记
这不是为了做一个完整替代 js-draw 的高级画板,而是先解决 PC 端最核心的问题:
点击“绘图”后必须有反馈,用户能画、能保存,笔记里能插入绘图资源。

7.3 录音入口改为语音转文字
PC 端的录音附件和桌面输入习惯并不完全一样。根据这次适配目标,Harmony 上点击“录音”不再直接弹音频录制页面,而是进入语音转文字。
新增了:
packages/app-mobile/components/voiceTyping/SpeechToTextBanner.harmony.tsx
JS 侧会调用:
JoplinHarmony.speechToText
JoplinHarmony.speechToTextFinish
JoplinHarmony.speechToTextCancel
ArkTS 侧接入了:
@kit.CoreSpeechKit
@ohos.multimedia.audio
ohos.permission.MICROPHONE
模拟器上语音识别能力不一定完整,所以这部分最终要以真机为准。但这次至少完成了入口、权限、native contract、ArkTS 实现和 C++ TurboModule 导出,避免真机上还没进入识别就被 JS 报“方法不存在”挡住。
八、Native Bridge 的坑
这次迁移过程中,native bridge 是最容易“看起来写了,运行时却没生效”的部分。
Joplin Harmony native bridge 至少有四份东西要同步:
utils/harmony/HarmonyNative.ts
harmony/native-contract/JoplinHarmonyModule.d.ts
harmony/entry/src/main/ets/rn/JoplinHarmonyModule.ets
harmony/entry/src/main/cpp/JoplinHarmonyTurboModule.cpp
其中 HarmonyNative.ts 是 JS 侧使用的类型和入口,JoplinHarmonyModule.d.ts 是 native contract,JoplinHarmonyModule.ets 是 ArkTS 实现,JoplinHarmonyTurboModule.cpp 是 C++ 层暴露给 JS 的方法表。
如果只在 ArkTS 里加方法,而 C++ 方法表没加,JS 侧拿到的 native module 仍然没有这个方法。语音转文字这次就踩到了这个点。
另一个坑是参数形态。系统 picker 返回的 URI 不能直接当作 Joplin 内部资源路径使用,需要复制到应用 cache 或 files 目录,再返回稳定的数据结构:
type
mime
uri
fileName
name
sourceUri
否则 JS 侧 attachFile 后续处理资源时,可能拿到的是系统临时 URI,导致资源无法持久化或预览失败。
SQLite 和文件系统也是同样道理。Joplin 对本地数据的一致性要求比较高,笔记、资源、设置和日志都依赖稳定的 sandbox path。Harmony 侧不能只做“能读写一个文件”的 demo,而要保持和 Joplin 原有平台层一致的行为。
九、WebView 和编辑器问题
Joplin 中有不少能力依赖 WebView:
- Markdown viewer
- rich text editor
- drawing editor
- plugin panels
- injected JavaScript
- postMessage
- resource rendering
鸿蒙侧 WebView 可以作为基础能力使用,但和桌面 Electron 里的 WebContents、以及其他端上成熟 WebView 的行为仍然有差异。迁移时最常见的问题不是编译不过,而是运行时某个区域空白、消息没有回传、注入脚本没有按预期执行。
这次策略是分层处理:
- 基础 note viewer 和编辑入口先保证能显示
- PC 主界面优先保证笔记列表和正文区域不空白
- 绘图这种强依赖 WebView 的能力先改成 Harmony 专用 SVG 实现
- WebView 注入和 postMessage 后续继续做更细的设备验证
这个选择是工程上比较现实的。鸿蒙适配第一版不一定要把所有复杂编辑器能力都一次性追平,但主流程不能断。用户至少要能创建、选择、查看、编辑笔记,附件入口要有合理反馈。
十、构建和验证流程
本次稳定下来的构建流程如下。
先做 TypeScript 检查:
node_modules/.bin/tsc --project packages/app-mobile/tsconfig.harmony.json --noEmit
然后生成 JS 编译产物:
node_modules/.bin/tsc --project packages/app-mobile/tsconfig.harmony.json
更新 ignore 列表:
node packages/tools/gulp/tasks/updateIgnoredTypeScriptBuildRun.js
生成 Harmony bundle:
node packages/app-mobile/tools/buildHarmonyBundle.js
构建 HAP:
cd packages/app-mobile/harmony
DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
HOS_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony \
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
--mode module \
-p module=entry@default \
-p product=default \
assembleHap \
--analyze=normal \
--parallel \
--incremental \
--daemon
安装到设备:
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc install -r \
~/XM/joplin-dev/joplin-ohos/packages/app-mobile/harmony/entry/build/default/outputs/default/entry-default-unsigned.hap
启动应用:
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc shell aa start \
-b net.cozic.joplin.harmony \
-a EntryAbility
验证时重点看这些路径:
- 应用是否能启动到 Joplin 主界面
- 左侧笔记本是否能显示
- 中间笔记列表是否能选中
- 右侧是否能显示当前笔记内容
- 是否能编辑标题和正文
- 新建笔记本是否能正常命名,而不是只能创建到默认欢迎上下文
- 相机是否打开系统图片选择器
- 绘图是否打开可见画布
- 录音是否进入语音转文字入口
- bundle 是否为最新 JS,不是旧逻辑
十一、这次迁移遇到的主要困难
11.1 三方库不是一换包名就结束
很多桌面应用里习以为常的三方能力,到了 Harmony PC 都需要重新确认:是否有 OpenHarmony 版本,是否需要注册到鸿蒙工程,是否还要在 JS 侧增加平台分支。
比如同样是 picker,文件选择、图片选择、拍照入口在 PC 上的产品语义并不完全一样,不能照搬其它端的逻辑。
11.2 原生方法导出有多层
JS 类型、ArkTS 实现、C++ methodMap 必须同步。漏一层就会出现“代码明明写了,运行时却说没实现”的问题。
11.3 PC 布局不能照搬窄屏体验
窄屏页面可以跳转,PC 端用户期待的是三栏同时存在。Joplin 这种笔记软件尤其明显:列表、内容、编辑入口需要在一个窗口中协同。
11.4 WebView 能力需要逐项验证
WebView 不是只要能显示网页就算完成。Joplin 里的 WebView 还承担资源渲染、编辑器、绘图、脚本注入、消息通信等能力。每一项都可能在鸿蒙运行环境下出现差异。
11.5 模拟器和真机能力不同
语音转文字就是典型例子。模拟器上很难完整验证麦克风、CoreSpeechKit 和识别结果,所以工程上先把入口、权限、native bridge 和导出链路打通,最终效果留到真机确认。
11.6 构建缓存和旧 bundle 容易误导
只改 TS 不重新生成 JS,或者只跑 --noEmit,最终 HAP 可能还是旧 bundle。这样会出现“明明代码改了,模拟器却还是旧行为”的错觉。Harmony 适配调试时必须确认 bundle 和 HAP 都是最新的。
十二、当前完成情况和后续方向
当前已经完成:
- Harmony HAP 工程接入
- 鸿蒙 HAP 启动 Joplin
- PC 窗口模式和基础三栏布局
- 笔记本、笔记列表、笔记内容区域联动
- 笔记选择后右侧内容显示
- 基础编辑入口
- 新建笔记本流程修正
- Harmony native module 基础能力
- 文件系统、SQLite、网络、picker、share 等能力桥接
- 相机入口改为系统图片选择
- 绘图入口改为 Harmony SVG 画布
- 录音入口改为语音转文字
- 语音转文字 native method 导出链路
- HAP 构建、安装、启动验证
十三、总结
这次 Joplin 迁移鸿蒙 PC 的核心感受是:一个成熟桌面软件迁到 Harmony,并不是“换一个打包方式”这么简单。
真正的工作分散在很多层:
- HAP 工程要能承载桌面应用运行入口
- JS bundle 要能稳定生成和加载
- 三方 native 库要找到 OpenHarmony 版本
- 没有现成实现的能力要自己写 Harmony adapter
- ArkTS、C++、JS contract 要保持一致
- PC 窗口布局要重新考虑
- WebView、SQLite、文件系统、picker、分享、权限都要逐项验证
Joplin 这种成熟笔记软件的迁移,很适合作为鸿蒙 PC 应用适配的案例。它覆盖了本地数据、资源文件、复杂 UI、编辑器、三方库、系统选择器、原生能力桥接等典型问题。做完这类项目之后,再看普通工具类应用迁移,会更容易判断哪些问题是框架层的,哪些问题是产品形态变化带来的。
第一阶段最重要的目标不是追求所有能力一次性完整,而是先建立稳定闭环:能启动、能显示、能选择、能编辑、能保存、关键入口有反馈。这个闭环打通后,后续再继续补齐同步、加密、导入导出、语音识别和 WebView 深层能力,就有了可以迭代的基础。
更多推荐

所有评论(0)