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

一、写在前面

这篇文章记录的是一次比较完整的 Electron 应用鸿蒙适配实践,主角就是从 GitHub 拉下来的 marktext-develop 项目本身。

项目开源地址 :https://AtomGit.com/lqjmac/marktext-ohos

这次工作不是“从零开发一个鸿蒙应用”,而是一次典型的迁移改造:

把一个面向桌面端的复杂 Electron 工程,逐步改造成一个可以在鸿蒙 Electron 运行环境中启动和运行的版本。

为了把这个过程讲清楚,本文会把 marktext-develop 放在两个状态下进行分析:

  • 桌面原始态:刚从 GitHub 拉下来的标准 Electron 工程
  • 鸿蒙适配态:完成 OpenHarmony / HarmonyOS 兼容改造后的工程

本文不会只讲“最后改了哪些文件”,而是会按照 变化过程 来分析:

  1. 原始版本是什么状态
  2. 适配后的版本增加了什么
  3. 哪些修改是工程层面的
  4. 哪些修改是运行时兼容层面的
  5. 哪些修改是为了解决真正的白屏问题
  6. 为什么这些改动组合起来最终让应用跑了起来

同时,文末会明确标出截图放置位置说明,方便后续排版发布。


二、项目背景

MarkText 是一个成熟的 Markdown 编辑器,技术栈并不轻:

  • Electron
  • Vue 2
  • Vuex
  • Vue Router
  • Element UI
  • Muya 编辑器内核
  • 大量本地文件、窗口、菜单、快捷键、剪贴板、字体、编码检测等桌面能力

这类项目的特点是:

  1. 功能完整
  2. 启动链复杂
  3. 原生依赖较多
  4. 对 Electron 运行时的默认能力假设很强

而鸿蒙下的 Electron 运行环境,与 Windows / macOS / Linux 桌面端并不完全一致。
这就导致一个很典型的问题:

项目在桌面端完全正常,但搬到鸿蒙环境后,应用能起,窗口能出,界面却卡在加载动画。

这次适配的目标,就是把 marktext-develop 从原始桌面工程,逐步演化成最终可在鸿蒙 Electron 运行环境中正常启动和运行的版本。


三、总体变化概览

在排除 node_modules、构建产物和 IDE 缓存后,marktext-develop 从桌面原始态演进到鸿蒙适配态的变化,大致可以概括为:

3.1 变化规模

  • 新增文件:约 240
  • 修改文件:28
  • 删除文件:0

这个数字本身已经说明:

这不是一个“改 3 个配置文件就结束”的适配,而是一套完整的迁移工程。

3.2 变化层次

这些变化基本可以分成三层:

  1. 工程层

    • 增加鸿蒙 HAP 工程壳
    • 增加打包与同步脚本
    • 增加适配说明文档
  2. 主进程兼容层

    • 增加 Harmony 平台识别
    • 降级 GPU 和桌面端特有能力
    • 保护原生依赖
    • 加强主进程对渲染层的诊断
  3. 渲染进程启动层

    • 重构 renderer 入口
    • 缩短首屏同步依赖链
    • 增加错误可视化兜底
    • 对不稳定 native / remote 能力做兼容处理

如果要一句话总结:

marktext-develop 的原始桌面版是标准 Electron 应用,而鸿蒙适配版则是在保留原有业务结构的前提下,补出了一整套鸿蒙运行时兼容层。


在这里插入图片描述

四、原始桌面版 marktext-develop 的状态

原始项目的特点非常鲜明:

4.1 默认只考虑桌面端平台

src/main/index.js 中,原始逻辑只允许这些平台:

  • darwin
  • win32
  • linux

这意味着如果直接在鸿蒙环境运行,主进程在平台识别阶段就已经处于不被支持的状态。

4.2 启动链默认依赖“桌面 Electron 全能力”

原始项目默认假设以下能力都可正常工作:

  • GPU 加速
  • @electron/remote
  • native-keymap
  • keytar
  • ced
  • fontmanager-redux
  • 桌面端标题栏、窗口偏移等接口

在桌面端这是合理的,但在 HarmonyOS 环境中,这种假设会让很多启动路径变得脆弱。

4.3 渲染进程入口是重同步启动

原始 src/renderer/main.js 在文件顶层就同步导入了:

  • Vue
  • VueRouter
  • Element UI
  • Store
  • Router
  • 样式
  • 服务模块
  • 一整套渲染入口依赖

App.vue 又继续同步引入:

  • EditorWithTabs
  • SideBar
  • CommandPalette
  • AboutDialog
  • ImportModal
  • Tweet
  • 更多弹窗和上下文能力

这个结构对桌面环境没有问题,但对鸿蒙环境来说,意味着:

只要首屏依赖链中有任意一个模块在鸿蒙运行时不兼容,整个应用就会在 Vue 根实例挂载前失败。

4.4 错误暴露能力不足

原始项目虽然有自己的异常处理体系,但在鸿蒙环境下,渲染层日志并不一定能自然透出到可见位置。
这就会导致一个现实问题:

  • 主窗口已经出来了
  • 页面却还停在 loading
  • 但你看不到真正的渲染层报错

这也是这次适配初期最难受的地方。


在这里插入图片描述

五、鸿蒙适配版新增了什么

5.1 新增鸿蒙工程壳 ohos_hap/

这是本次变化里最直观的一部分。

适配后的项目新增了完整的:

  • ohos_hap/AppScope
  • ohos_hap/electron
  • ohos_hap/web_engine
  • ohos_hap/hvigor
  • 模块配置
  • 资源目录
  • HAP 构建脚本
  • OpenHarmony 相关说明和依赖资源

这说明 marktext-develop 不再只是一个普通 Electron 源码仓库,而是已经具备了:

“桌面应用资源 + 鸿蒙壳工程 + 资源注入打包链路”

5.2 新增适配脚本

相较于原始桌面版,package.json 中新增了三个关键命令:

  • build:ohos
  • ohos:sync
  • ohos:build

它们分别承担:

  1. 构建鸿蒙需要的应用资源包
  2. 把产物同步到鸿蒙工程资源目录
  3. 进一步触发 HAP 构建

这一步是从“源码项目”走向“可交付鸿蒙包工程”的关键桥梁。

5.3 新增适配文档

新增文件:

  • OHOS_ADAPTATION.md

这代表适配过程已经不再只是临时试验,而是开始沉淀成可复用的方法和流程。

在这里插入图片描述


六、marktext-develop 的鸿蒙适配核心演进路径

如果按适配过程来理解,这次演进大致可以拆成下面六步。


七、第一步:补齐鸿蒙工程与打包链

7.1 为什么要先做这一步

如果没有鸿蒙工程壳,桌面端 Electron 产物根本没有地方挂载到 HarmonyOS 的运行环境中。

所以第一步不是修白屏,而是先让整个工程拥有进入鸿蒙壳的能力。

7.2 关键变化

marktext-develop 中新增:

  • scripts/build-ohos-package.js
  • scripts/build-ohos-hap.js
  • ohos_hap/ 工程目录

7.3 这一层改造的本质

原始项目只有 Electron 应用本身。
适配后项目则多了一套“鸿蒙承载层”。

也就是说:

  • marktext-develop 关心的是“如何打包桌面应用”
  • marktext-develop 还要关心“如何把桌面应用资源投喂给鸿蒙 Electron 壳”

7.4 这一阶段的结果

最终 MarkText 的桌面产物会被同步到:

ohos_hap/web_engine/src/main/resources/resfile/resources/app

这个目录是鸿蒙侧 Electron 壳实际读取应用资源的位置。


八、第二步:让主进程承认 HarmonyOS 平台存在

8.1 原始状态

marktext-developsrc/main/index.js 中只允许 darwin / win32 / linux

8.2 适配后的变化

src/main/config.js 中新增:

  • isHarmony

并在 src/main/index.js 中将原来的平台判定改为:

  • HarmonyOS 作为可接受平台
  • HarmonyOS 启动时自动禁用 GPU

8.3 这一步的意义

这一步解决的是一个最基础的问题:

项目不能一上来就把鸿蒙判成“不支持的平台”。

同时,自动禁用 GPU 也是非常重要的保守策略。因为在鸿蒙日志中可以明显看到:

  • GPU 进程会反复拉起
  • 图形栈会降级
  • use-gl=angleuse-gl=disabled 会切换

这意味着 GPU 在鸿蒙环境中并不稳定。
所以在 marktext-develop 里,主进程会主动把这类风险前置规避掉。


九、第三步:把桌面端特有能力降级掉

这一阶段是从“能启动”走向“别轻易被桌面能力拖死”。

9.1 src/main/app/index.js 的变化

相对于原始桌面版,鸿蒙适配版在这个文件里做了几类保护:

  1. HarmonyOS 下不再依赖 native theme 的完整行为
  2. HarmonyOS 下跳过 Dock 相关逻辑
  3. HarmonyOS 下跳过 JumpList 相关逻辑
  4. HarmonyOS 下不注册拼写检查监听

9.2 为什么这些改动必要

因为这些能力本质上都更偏桌面平台特征:

  • macOS Dock
  • Windows JumpList
  • Chromium / Electron 对桌面环境的主题假设
  • 桌面拼写检查链路

如果不做隔离,应用可能在主进程初始化后半段才暴露异常,定位会更困难。

9.3 窗口接口保护

在:

  • src/main/windows/editor.js
  • src/main/windows/setting.js

中,适配版增加了对 setSheetOffset 的保护,并加入 HarmonyOS 条件判断。

这类接口在桌面端正常,在鸿蒙下则不应默认可用。


十、第四步:给原生依赖都加上“能失败”的出口

这一部分是适配过程中非常关键的一层。

原始项目里有不少原生依赖,在桌面端理所当然,但在鸿蒙环境下不一定成立。

10.1 keytar:从硬依赖变为可选依赖

原始版本在 src/main/dataCenter/index.js 中直接引入 keytar
适配版改成了:

  • try/catch 延迟 require
  • 加载失败则打印告警
  • 安全存储相关能力失效,但整体应用仍可运行

这一步很重要,因为鸿蒙日志中确实出现了:

  • Cannot find module '../build/Release/keytar.node'

如果不改成可选依赖,应用很容易在数据中心初始化阶段就崩掉。

10.2 native-keymap:从直接使用到回退为 en-US

src/main/keyboard/index.js 中,适配版增加了:

  • 原生绑定文件存在性判断
  • 动态 require('native-keymap')
  • 失败时回退为 en-US

这意味着:

  • 即使鸿蒙环境不支持原生键盘布局读取
  • 应用也不会因为快捷键布局模块失败而无法启动

10.3 ced:编码探测从“必须存在”变成“存在更好”

src/main/filesystem/encoding.js 中,适配版把 ced 改成了惰性获取。

逻辑从:

  • 启动时默认依赖 ced

改成:

  • 如果存在就用它猜编码
  • 如果不存在,也能继续使用 BOM 或 UTF-8 路径完成文件处理

10.4 fontmanager-redux

src/renderer/prefComponents/common/fontTextBox/index.vue 中,适配版对系统字体枚举做了 try/catch

这意味着:

  • 设置页字体能力可能受限
  • 但不会因为本地字体枚举失败而拖垮设置页

十一、第五步:解决 @electron/remote 带来的渲染层脆弱性

11.1 原始状态

marktext-develop 的渲染层中,很多地方直接使用:

  • @electron/remote
  • getCurrentWindow
  • Menu
  • clipboard

11.2 适配后的变化

适配版新增:

  • src/renderer/util/remote.js

这个文件专门包了一层安全访问接口:

  • getCurrentRemoteWindow
  • getRemoteMenu
  • getRemoteMenuItem
  • getRemoteClipboard
  • isRemoteAvailable

11.3 这一步的意义

它不是为了“代码更优雅”,而是为了:

当 HarmonyOS 环境里的 @electron/remote 行为不完整时,渲染层不至于在顶层求值阶段直接崩掉。

11.4 影响到的典型文件

相较于 marktext-develop,下面这些文件都被改造成走安全 remote 包装:

  • src/renderer/commands/index.js
  • src/renderer/components/titleBar/index.vue
  • src/renderer/prefComponents/common/titlebar.vue
  • src/renderer/contextMenu/sideBar/index.js
  • src/renderer/contextMenu/tabs/index.js
  • src/renderer/util/clipboard.js

这说明适配版在做的,不是简单修一个地方,而是在 系统性收缩 remote 风险面


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

十二、第六步:对白屏问题动手术

这是整个 marktext-develop 鸿蒙适配过程中,最关键也最“有含金量”的部分。

下图所示:日志中已经可以看到标题设置、窗口显示与激活,说明问题并不在主进程建窗阶段。

在这里插入图片描述
在这里插入图片描述

12.1 白屏问题的本质

初看现象是:

  • 窗口已经显示
  • 标题已经变成 MarkText
  • 但界面一直停在绿色加载动画

这说明主进程建窗已经完成,真正的问题发生在:

渲染层入口到 Vue 根实例挂载这段路径上。

12.2 src/index.ejs 的升级

原始版 src/index.ejs 只是一个普通的加载页面。
适配版在这里新增了非常关键的一层壳级诊断:

  • window.onerror
  • unhandledrejection
  • 资源加载失败监听
  • 直接把错误渲染回 loading 页面

这个思路非常接近参考项目 electron-markdownify-master
在应用业务脚本真正跑起来之前,就先让 HTML 壳层具备错误感知能力。

12.3 src/renderer/main.js 的重构

这是变化最大的一处。

原始版做法:

  • 顶层同步 import
  • 一次性把 Vue、Store、Router、Element UI、样式、服务全部拉进来

适配版做法:

  • 分阶段 require
  • 每个阶段带标签打印
  • 可选依赖允许失败
  • 失败时直接把错误显示到 loading 页

这一步的意义非常大,因为它把原本“黑盒式崩溃”变成了“带阶段信息的可见崩溃”。

12.4 src/renderer/bootstrap.js 的保守化

原始版在文件顶层直接依赖 electron-log
适配版把它改成惰性加载:

  • 能加载则启用
  • 失败则退回 console

并增加:

  • 渲染层 error 日志
  • unhandledrejection 日志
  • 启动链埋点

12.5 src/renderer/node/paths.js 的容错

原始版直接依赖 vscode-ripgrep
适配版改成:

  • try/catch require
  • 失败时搜索能力降级,但首屏继续

这类改动体现的是一个非常成熟的迁移策略:

非首屏核心能力绝不能阻塞首屏。


十三、第七步:把重组件从首屏同步链路里拆出去

这一步是适配版能真正摆脱加载动画卡死的核心操作之一。

13.1 原始版 App.vue

marktext-developApp.vue 中同步引入了大量重组件:

  • EditorWithTabs
  • SideBar
  • CommandPalette
  • AboutDialog
  • ExportSettingDialog
  • Rename
  • Tweet
  • ImportModal

这意味着:

  • 只要这些组件树里有任何一个模块在鸿蒙环境下不兼容
  • 整个 App 根组件都可能在挂载前失败

13.2 适配版 App.vue

适配版做法是:

  • 将这些组件改成异步加载
  • 增加按组件粒度的失败日志
  • 给异步加载保留简单 loading 占位

13.3 这一步的效果

它等于把原本这条链:

App -> EditorWithTabs -> editor.vue -> Muya -> CodeMirror -> 搜索/打印/通知/剪贴板

从“同步硬链”改成了“分段可失败链”。

这意味着:

  1. Vue 根实例更容易先挂起来
  2. loading overlay 更容易先被移除
  3. 后续哪个组件出问题,也更容易单独定位

十四、第八步:把“鸿蒙下看不到 renderer 日志”的问题彻底补上

这一层是鸿蒙适配版相对原始桌面版的另一个质变。

14.1 原始状态

原始项目虽然有异常处理,但并没有专门针对鸿蒙环境做:

  • console-message
  • did-fail-load
  • render-process-gone
  • dom-ready
  • did-finish-load

这类事件的统一桥接。

14.2 适配后的做法

src/main/windows/base.js 中新增了统一诊断函数:

  • _attachWebContentsDiagnostics

并在:

  • src/main/windows/editor.js
  • src/main/windows/setting.js

中挂接。

14.3 这一步为什么很值钱

因为在鸿蒙环境下,很多时候问题不是“没有报错”,而是:

报错发生在 renderer,但你根本看不见。

诊断桥接之后:

  • renderer 的控制台日志会被带回主进程
  • 页面加载失败会被带回主进程
  • 渲染进程退出会被带回主进程

这让适配版真正拥有了“可继续调试”的能力。


十五、参考项目的价值:为什么要借鉴 electron-markdownify-master

这次适配过程中,electron-markdownify-master 不是直接被照搬,而是被当成一个“已经在鸿蒙环境中验证过的运行时样板”。

它带来的启发主要有三点:

15.1 鸿蒙下要先做能力判断,再做业务

参考项目先判断:

  • 是否为 OHOS
  • 哪些能力该关闭
  • 哪些模块该保守加载

这个思路直接影响了 marktext-develop 中的:

  • isHarmony
  • 可选依赖降级
  • 主进程窗口能力保护

15.2 主进程必须桥接 renderer 日志

参考项目显式监听:

  • console-message
  • did-fail-load
  • render-process-gone

这直接启发了适配版中的窗口诊断桥。

15.3 首屏一定要少做事

参考项目的 renderer 启动很轻。
这让我们反过来意识到 marktext-develop 的问题并不是“某一个 API 不支持”那么简单,而是:

首屏同步依赖链太长,任何一个节点的不兼容都会被放大成整体白屏。

这也是为什么适配版必须对 renderer/main.jsApp.vue 动手。


在这里插入图片描述

十六、从变化结果看:鸿蒙适配版具体比原始桌面版多了什么能力

如果用“能力清单”的方式总结,适配后的项目相比原始版多了这些能力:

16.1 工程能力

  • 能构建鸿蒙资源包
  • 能同步资源到 HAP 工程目录
  • 能参与 HAP 构建

16.2 平台识别能力

  • 能识别 HarmonyOS 平台
  • 能按平台启用或禁用不同逻辑

16.3 降级容错能力

  • keytar 不可用时继续运行
  • native-keymap 不可用时继续运行
  • ced 不可用时继续运行
  • fontmanager-redux 不可用时继续运行
  • @electron/remote 不完整时继续运行
  • vscode-ripgrep 不可用时首屏继续运行

16.4 启动诊断能力

  • HTML 壳层能捕获首屏资源与脚本错误
  • 主进程能桥接渲染层 console
  • 主进程能感知加载失败和 render process 崩溃

16.5 启动链拆分能力

  • 首屏重组件异步化
  • renderer 入口分阶段加载
  • 非核心依赖不再阻塞根实例挂载

十七、为什么原始桌面版不能直接跑,而鸿蒙适配版可以

这部分是全文最值得拿出来做结论的一段。

并不是说 marktext-develop 有“致命 bug”,而是它默认建立在“标准桌面 Electron 环境”的假设上。

marktext-develop 能跑起来,是因为它系统性地改变了这些前提:

  1. 平台前提变了
    HarmonyOS 被正式识别为允许平台。

  2. 能力前提变了
    桌面端默认存在的很多能力,在鸿蒙环境下改成了“可选而非必选”。

  3. 首屏前提变了
    首屏不再要求整个重组件树和所有原生依赖同步成功。

  4. 错误处理前提变了
    错误不再沉没在黑盒里,而是会反馈到 UI 或主进程日志。

  5. 工程前提变了
    项目不再只是 Electron 源码项目,而是具备了鸿蒙打包和承载能力。

从这个角度看:

这个版本不是单纯的“修复版”,而是 marktext-develop 的鸿蒙迁移版本。


在这里插入图片描述

十八、结语

这次 marktext-develop 的鸿蒙适配,本质上不是一次简单修 bug,而是一次典型的桌面 Electron 项目鸿蒙迁移:

  • 工程层补齐承载壳
  • 平台层补齐识别与降级
  • 启动层补齐容错与诊断

原始项目的问题并不是“不够好”,而是它默认站在成熟桌面平台的地板上。
而适配后的项目之所以能跑起来,是因为它为鸿蒙环境重新补上了那块地板。

如果后续还需要把更多 Electron 项目迁到鸿蒙环境,那么这次 marktext-develop 从原始桌面版到鸿蒙适配版的演进过程,本身就已经是一份很有参考价值的实践样本。
Electron 环境”的假设上。

marktext-develop 能跑起来,是因为它系统性地改变了这些前提:

  1. 平台前提变了
    HarmonyOS 被正式识别为允许平台。

  2. 能力前提变了
    桌面端默认存在的很多能力,在鸿蒙环境下改成了“可选而非必选”。

  3. 首屏前提变了
    首屏不再要求整个重组件树和所有原生依赖同步成功。

  4. 错误处理前提变了
    错误不再沉没在黑盒里,而是会反馈到 UI 或主进程日志。

  5. 工程前提变了
    项目不再只是 Electron 源码项目,而是具备了鸿蒙打包和承载能力。

从这个角度看:

这个版本不是单纯的“修复版”,而是 marktext-develop 的鸿蒙迁移版本。

[外链图片转存中…(img-Y7aR7XwZ-1779347511634)]


十八、结语

这次 marktext-develop 的鸿蒙适配,本质上不是一次简单修 bug,而是一次典型的桌面 Electron 项目鸿蒙迁移:

  • 工程层补齐承载壳
  • 平台层补齐识别与降级
  • 启动层补齐容错与诊断

原始项目的问题并不是“不够好”,而是它默认站在成熟桌面平台的地板上。
而适配后的项目之所以能跑起来,是因为它为鸿蒙环境重新补上了那块地板。

如果后续还需要把更多 Electron 项目迁到鸿蒙环境,那么这次 marktext-develop 从原始桌面版到鸿蒙适配版的演进过程,本身就已经是一份很有参考价值的实践样本。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐