一、写在前面

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

原项目地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_Trilium

欢迎在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

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

Trilium Notes 是一个偏重个人知识库和本地笔记管理的开源软件。它不是简单的文档编辑器,里面有笔记树、富文本编辑、代码笔记、附件、日历、关系图、Mermaid、Excalidraw、PDF 预览、SQLite 数据库等能力。也正因为这样,迁移到鸿蒙时不能只看页面能不能打开,还要看本地数据库和应用初始化链路能不能真正跑通。

这次适配选择的路线是:

使用 Trilium 的 apps/standalone 版本生成静态资源,再通过 OpenHarmony Electron runtime 在 HAP 中承载。

这个选择不是为了省事,而是为了避开第一阶段最重的 native addon 问题。Trilium 桌面端依赖 better-sqlite3,如果直接迁桌面 Electron 版本,需要处理鸿蒙侧 Node 原生模块编译、加载、ABI、数据目录和权限等问题。Standalone 版本使用 @sqlite.org/sqlite-wasm,更适合先把核心笔记体验跑进鸿蒙窗口。

本次适配重点处理了几条主线:

  • HAP 工程能够独立构建和安装
  • Trilium standalone 产物能够稳定同步到鸿蒙资源目录
  • Electron runtime 能够加载本地 Trilium 页面
  • SQLite WASM、Service Worker、本地 bridge 能够完成初始化
  • 白屏问题能够通过日志快速定位
  • 鸿蒙窗口标题栏的最小化、最大化、关闭按钮能够正常显示

先说结论:这次适配已经让 Trilium 的核心笔记界面在鸿蒙 Electron HAP 中跑起来。真正难的地方不在“把 HTML 放进去”,而在 WebAssembly、SQLite WASM、运行时 header、Service Worker、本地静态服务和鸿蒙窗口外壳之间的协作。

在这里插入图片描述


二、项目背景

Trilium Notes 当前适配版本为:

0.103.0

项目根目录使用 pnpm workspace 管理,主要结构包括:

  • apps/client:核心前端界面
  • apps/desktop:桌面 Electron 版本
  • apps/standalone:浏览器 standalone 版本
  • packages/trilium-core:核心业务逻辑
  • packages/*:编辑器、组件、共享能力
  • scripts/:构建、版本、迁移等脚本
  • ohos_hap/:本次新增的鸿蒙 HAP 工程

这个项目有一个很关键的特点:

数据库不是边缘功能,而是 Trilium 启动和使用的核心。

桌面端使用 native SQLite 路线,standalone 版本则使用 SQLite WASM。对鸿蒙适配来说,这个差异非常重要。页面能加载,只说明资源路径和窗口入口大致正确;SQLite 初始化成功,才说明 Trilium 真正进入了可用状态。

Standalone 版本依赖:

@sqlite.org/sqlite-wasm

并且内部已经具备 OPFS / SAHPool 相关逻辑。SAHPool VFS 可以让 SQLite WASM 在浏览器类环境中获得更稳定的持久化能力,也避免了第一版就去处理 better-sqlite3 的鸿蒙 native addon 编译。

所以这次的适配切入点不是桌面端 apps/desktop,而是:

apps/standalone/dist

用 standalone 产物建立完整的鸿蒙运行入口,再由 Electron HAP 提供窗口、进程和资源承载。


三、总体变化概览

3.1 新增鸿蒙工程壳

适配后的项目新增:

ohos_hap/
├── AppScope/
├── electron/
├── web_engine/
└── build-profile.json5

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

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

这个目录里会放入:

resources/app/
├── main.js
├── package.json
└── standalone/
    ├── index.html
    ├── sw.js
    └── assets/

3.2 新增构建脚本

新增脚本:

scripts/build-ohos-package.cjs
scripts/build-ohos-hap.cjs

package.json 中新增:

{
  "ohos:package": "node scripts/build-ohos-package.cjs",
  "ohos:sync": "OHOS_TRILIUM_OUT=ohos_hap/web_engine/src/main/resources/resfile/resources/app pnpm ohos:package",
  "ohos:build": "node scripts/build-ohos-hap.cjs"
}

这几条命令分别对应:

  • 构建 Trilium standalone 产物
  • 同步运行资源到 HAP 工程
  • 修补鸿蒙 Electron adapter 的 V8 参数
  • 调用 Hvigor 构建 HAP
  • 校验最终 HAP 里的关键资源和运行时参数

3.3 新增 Electron 启动引导

构建脚本会写入:

resources/app/main.js

这个文件负责:

  • 设置 TRILIUM_OPENHARMONY=1
  • 默认关闭硬件加速
  • 启动本地静态服务器
  • 给资源响应补充 COOP / COEP header
  • 创建 BrowserWindow
  • 加载 http://127.0.0.1:37880/?openHarmony=1
  • 记录 renderer console、加载事件和 DOM snapshot

在这里插入图片描述


四、为什么 Trilium 选择 standalone 版优先

Trilium 桌面端完整能力更多,但鸿蒙适配第一版优先选择 standalone,核心原因有三个。

第一,standalone 版已经把核心笔记体验放在 Web 运行环境里。前端、编辑器、笔记树、本地 bridge 和数据库初始化都有现成入口,不需要重写 ArkTS 页面。

第二,standalone 使用 SQLite WASM,避免了 better-sqlite3 在鸿蒙侧的 native addon 问题。桌面端 native SQLite 路线后续可以继续研究,但不适合作为第一版启动路径。

第三,Trilium 的核心价值在笔记数据和编辑体验,不在托盘、系统菜单、全局快捷键这些桌面集成能力。第一版先保证数据层和页面稳定,后面再逐个补齐文件选择、导入导出、下载、打印等能力,会更稳。

所以这次路线可以概括为:

apps/standalone/dist 静态产物 + 本地 HTTP 服务 + 鸿蒙 Electron 窗口 + HAP 工程承载。

这里特别需要注意,本次没有选择直接 loadFile('standalone/index.html')。Trilium standalone 运行链路里有 Service Worker、SQLite WASM、OPFS / SAHPool、本地 bridge 等能力,本地 HTTP 服务能更好地处理 scope、MIME、header 和资源访问问题。


五、鸿蒙适配核心路径

5.1 第一步:构建 Trilium standalone 产物

scripts/build-ohos-package.cjs 会先确认 standalone 产物是否存在:

apps/standalone/dist/index.html
apps/standalone/dist/sw.js

如果没有可复用产物,就执行:

pnpm standalone:build

构建完成后,脚本会把 apps/standalone/dist 复制到鸿蒙运行资源目录:

resources/app/standalone

这样 Trilium 的 Web 入口、静态资源、Service Worker、WASM 文件和前端 bundle 都能进入 HAP。

5.2 第二步:写入 OpenHarmony runtime package

同步资源后,脚本会写入:

resources/app/package.json

核心字段包括:

{
  "name": "trilium-openharmony",
  "main": "main.js",
  "productName": "Trilium Notes",
  "triliumRuntime": {
    "mode": "standalone"
  }
}

这样 OpenHarmony Electron runtime 就知道从 main.js 启动,而不是去寻找 Trilium 原始桌面端入口。

5.3 第三步:启动本地静态服务器

生成的 main.js 会在 Electron 主进程中启动一个本地服务器:

http://127.0.0.1:37880/

它会为常见资源设置正确 MIME,例如:

.js    application/javascript
.wasm  application/wasm
.css   text/css
.json  application/json

同时给响应补充:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin

这些 header 对 SQLite WASM、worker 和浏览器隔离能力非常关键。Trilium 不是只加载一个普通页面,它要让 WASM 数据库和本地 worker 一起工作。

5.4 第四步:创建 BrowserWindow 并加载本地服务

窗口创建保持轻量,但显式打开系统窗口能力:

mainWindow = new BrowserWindow({
  width: 1200,
  height: 800,
  minWidth: 500,
  minHeight: 400,
  title: 'Trilium Notes',
  frame: true,
  minimizable: true,
  maximizable: true,
  closable: true,
  autoHideMenuBar: true,
  webPreferences: {
    contextIsolation: false,
    nodeIntegration: false,
    webviewTag: true
  }
});

页面加载方式是:

mainWindow.loadURL('http://127.0.0.1:' + port + '/?openHarmony=1');

这里的 openHarmony=1 会让 standalone 侧识别当前环境,进入鸿蒙适配分支。

5.5 第五步:修补 adapter 的 V8 参数

这是这次适配中最关键的一步。

鸿蒙 Electron adapter 默认带有:

--js-flags=--jitless

在当前环境下,这会导致:

typeof WebAssembly === 'undefined'

Trilium standalone 依赖 SQLite WASM,WebAssembly 被关掉后,数据库初始化无法继续,页面就会停在白屏或半初始化状态。

构建 HAP 前,脚本会把 adapter so 中的参数替换成:

--js-flags=--no-lazy

替换前后字符串长度一致,可以做 byte-for-byte 替换。脚本还会做校验:

  • 只允许找到唯一一处 --js-flags=--jitless
  • 如果已经替换过,直接复用
  • 如果找不到预期参数,停止构建
  • HAP 生成后再次检查最终包里的 libadapter.so

这一步把一个设备侧白屏问题,变成了构建阶段就能明确控制的检查项。

5.6 第六步:把运行诊断放进启动入口

Trilium 白屏时,仅靠窗口是否出现很难判断问题在哪里。适配中给 main.js 增加了运行诊断:

  • console-message
  • did-start-loading
  • dom-ready
  • did-finish-load
  • did-fail-load
  • render-process-gone
  • unresponsive
  • DOM snapshot at dom-ready
  • DOM snapshot after 5s

DOM snapshot 会检查:

openHarmony
hasLocalFetch
hasWebAssembly
webAssemblyType
glob.dbInitialized
glob.isStandalone
glob.baseApiUrl

这些信息能把“白屏”拆成多个可观察状态。

5.7 第七步:同步鸿蒙窗口外壳和 Electron 窗口语义

页面跑起来以后,还遇到过一个很容易被误判的问题:窗口右上角没有最小化、最大化、关闭按钮。

问题不在 Trilium 前端,而在鸿蒙外壳的窗口装饰层。WebBaseAbility 默认隐藏标题栏,同时三键默认也是 false。Electron JS 层创建 BrowserWindow 时认为自己是 frame: true,但 ArkTS 外壳层没有同步打开三键。

适配中做了两处处理:

  1. EntryAbility 对桌面直接启动的初始主窗口启用 native frame
  2. AppWindowAdapter.setUseNativeFrame(true) 时同步 hideTitleBar=false,并调用 setWindowTitleButtonVisible(true, true, true)

这样 Electron 的 frame/minimizable/maximizable/closable 语义才能真正落到鸿蒙窗口上。

在这里插入图片描述


六、问题困难解决

这次 Trilium 适配中最大的困难是:

页面资源能加载,不代表 Trilium 已经可用;必须确认 SQLite WASM、本地 bridge 和数据库初始化全部跑通。

具体问题主要集中在下面几类。

6.1 困难一:白屏不是普通资源路径问题

最早看到的是白屏。常规思路会先查 JS / CSS 路径,但 Trilium 这里更深一层:资源请求可以进来,页面生命周期也能走到 dom-ready,真正失败的是 SQLite WASM 初始化。

解决方式是把 renderer 状态打出来:

hasWebAssembly
hasLocalFetch
dbInitialized
isStandalone

当日志里出现:

hasWebAssembly: false

问题就从“页面为什么空白”变成了“运行时为什么没有 WebAssembly”。

6.2 困难二:--jitless 会让 SQLite WASM 没有运行环境

鸿蒙 Electron adapter 的默认 V8 参数包含:

--js-flags=--jitless

它会让当前 runtime 中的 WebAssembly 不可用。对 Trilium 来说,这会直接影响数据库。

最终处理方式是在 scripts/build-ohos-hap.cjs 中做受控二进制替换:

--js-flags=--jitless -> --js-flags=--no-lazy

并在最终 HAP 中再次确认:

HAP 不再包含 --jitless
HAP 包含 --js-flags=--no-lazy

修复后的启动日志会出现:

hasWebAssembly: true
[BrowserSqlProvider] SQLite WASM initialized
[Worker] Database loaded
[Worker] Router configured
[LocalBridge] Local response <- GET /bootstrap?openHarmony=1 200

在这里插入图片描述

6.3 困难三:Service Worker / WASM / worker 需要更接近 Web Server 的环境

Trilium standalone 不是一个单纯的静态页面。它会用到:

  • Service Worker
  • Local bridge
  • SQLite WASM
  • Worker
  • OPFS / SAHPool

如果直接 loadFile,很容易遇到 scope、MIME、header、WASM 加载方式上的差异。适配中使用本地 HTTP 服务承载 standalone 产物,并补充 COOP / COEP header,让运行环境更接近普通 Web server。

这也是为什么入口加载的是:

http://127.0.0.1:37880/?openHarmony=1

而不是:

file:///.../standalone/index.html

6.4 困难四:窗口三键属于鸿蒙外壳层问题

页面能显示之后,发现主窗口没有最小化、最大化、关闭按钮。

排查后确认,默认 WebBaseAbility 会隐藏标题栏:

protected hideTitleBar: boolean = true;
protected minimizable: boolean = !this.hideTitleBar;
protected maximizable: boolean = !this.hideTitleBar;
protected closable: boolean = !this.hideTitleBar;

Electron 后续调用 setUseNativeFrame(true) 时,旧实现只显示 decor,没有同步三键 visible。解决方式是让 setUseNativeFrame(true) 同步 preference,并调用:

windowClass?.setWindowTitleButtonVisible(true, true, true);

这样窗口才真正符合桌面应用预期。

6.5 运行链路确认顺序

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

  1. apps/standalone/dist/index.html 是否生成
  2. resources/app/standalone/index.html 是否同步成功
  3. resources/app/package.jsonmain 是否指向 main.js
  4. 本地服务是否监听 127.0.0.1:37880
  5. Electron 是否加载 /?openHarmony=1
  6. hasWebAssembly 是否为 true
  7. SQLite WASM 是否初始化成功
  8. worker 是否完成 Database loaded
  9. /bootstrap 是否返回 200
  10. DOM snapshot after 5sdbInitialized 是否为 true
  11. 主窗口三键是否可见

这个顺序可以确认 HAP、Electron 主进程、standalone 页面、SQLite WASM、本地 bridge 和鸿蒙窗口外壳都进入了正确状态。


七、构建与运行方式

同步资源:

cd /Trilium-main-ohso
pnpm ohos:sync

构建 HAP:

cd /Trilium-main-ohso
pnpm ohos:build

HAP 输出目录:

ohos_hap/electron/build/default/outputs/default/

当前生成的未签名 HAP:

electron-default-unsigned.hap

安装到设备或模拟器:

hdc install -r ohos_hap/electron/build/default/outputs/default/electron-default-unsigned.hap

启动应用:

hdc shell aa start -a EntryAbility -b org.triliumnotes.trilium.ohos -m electron

八、适配成果

trilium-ohos 当前已经完成:

  • OpenHarmony Electron HAP 工程接入
  • Trilium standalone 静态资源构建与同步
  • Electron runtime package 写入
  • 本地 HTTP 服务启动
  • WASM / Worker / Service Worker MIME 处理
  • COOP / COEP header 设置
  • SQLite WASM 初始化链路
  • SAHPool / OPFS 数据库加载链路
  • Local bridge /bootstrap 请求链路
  • Renderer console 和 DOM snapshot 诊断
  • --jitless 导致 WebAssembly 不可用的问题修复
  • 主窗口标题栏三键显示修复
  • 命令行构建 HAP
  • 模拟器安装和启动验证

九、功能验证结果

trilium-ohos 按实际启动和笔记使用链路完成了基础验证。

第一组是启动链路:

  • HAP 能安装到鸿蒙模拟器
  • EntryAbility 能正常启动
  • Electron 主进程能执行 resources/app/main.js
  • 本地静态服务器能监听 127.0.0.1:37880
  • BrowserWindow 能加载 /?openHarmony=1

第二组是页面和数据层:

  • Trilium 页面不再白屏
  • WebAssembly 可用
  • SQLite WASM 初始化成功
  • worker 完成数据库加载
  • /bootstrap 返回 200
  • DOM snapshot after 5sdbInitialized=true

第三组是窗口体验:

  • 主窗口标题正确显示为 Trilium Notes
  • 系统标题栏可见
  • 最小化、最大化/还原、关闭按钮可见
  • 窗口缩放和基本鼠标操作正常

第四组是后续需要继续验证的能力:

  • 文件选择、导入导出、下载路径
  • 剪贴板和图片粘贴
  • 打印、PDF、附件预览
  • 大型笔记库下 SAHPool 持久化表现
  • 触控、窗口缩放、多窗口行为
  • 桌面端菜单和快捷键能力是否需要补齐

这些验证说明,Trilium 的核心笔记页面、数据库初始化和鸿蒙窗口承载已经形成闭环。后续重点会从“能不能启动”转向“复杂笔记库和系统能力是否稳定”。


十、结语

trilium-ohos 的适配本质上是一次“本地知识库软件的 WebAssembly 数据库运行时迁移”。

它和普通 Web 项目迁鸿蒙不太一样。Trilium 的关键不只是页面资源路径,而是 SQLite WASM、Service Worker、本地 bridge、OPFS / SAHPool 和 Electron 窗口外壳能否一起工作。

这次适配的经验是:

  • 第一版优先选择 standalone,比直接迁桌面端更稳
  • 白屏问题要尽快拆成可观察的运行状态
  • WebAssembly 对 Trilium 是主路径能力,不是性能优化项
  • HAP 构建脚本必须校验最终包里的运行时参数
  • Electron JS 层的窗口配置,需要和 ArkTS 外壳层同步

最终结果是,Trilium 的核心笔记体验已经通过 standalone + SQLite WASM 进入鸿蒙 Electron HAP。后续如果继续补齐桌面端能力,也应该沿着已经跑通的启动、数据库和窗口链路逐步扩展,而不是一开始就把所有 Electron 桌面能力一次性搬进来。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐