
我在 HarmonyOS 上写了一个轻量级小程序运行时从 Web 容器到 JSBridge项目地址https://github.com/lichenyang5/MiniAppRuntime-Harmony项目定位一个受小程序运行时架构启发的 HarmonyOS Web 容器与 JSBridge 框架。说明本文只讨论个人开源项目的设计思路全部基于公开 HarmonyOS / ArkTS / ArkWeb 能力实现不包含任何公司内部源码、内部接口或非公开文档内容。1. 为什么要做这个项目很多前端同学第一次接触 HarmonyOS会有一个很自然的问题能不能像小程序一样在原生应用里面承载一套 H5 页面然后让 H5 调用原生能力这件事听起来简单其实背后有一条很完整的运行时链路。H5 页面只是入口真正的问题是H5 怎么把请求发给 ArkTSArkTS 怎么知道这个请求要调用哪个能力不同能力怎么注册、分发和维护H5 调完之后结果怎么回到 Promise超时、未知 action、参数错误、回调丢失这些问题怎么处理所以我做了这个项目MiniAppRuntime-Harmony。它不是为了做一个大而全的小程序平台而是先做一条最小、清晰、可解释、可扩展的链路。第一阶段的目标非常明确ArkWeb 加载本地 H5 → H5 调用 window.myascf.send(action, params) → ArkTS 接收请求 → Dispatcher 分发 action → Registry 找到 handler → Biz 做参数校验 → Imp 调用系统能力 → ArkTS 通过 runJavaScript 回调 H5 → H5 Promise resolve / reject2. 最小闭环长什么样现在 Demo 里已经跑通了一个能力window.myascf.send(ui.showToast,{message:hello from h5})H5 调用这行代码后最终会在 HarmonyOS 侧触发真实的promptAction.showToast。这背后的链路是H5 Button → myascf.js → window.MyASCFNative.postMessage(...) → JavaScriptProxy.postMessage(...) → BridgeController → BridgeDispatcher → HandlerRegistry → ToastBiz → ToastImp → promptAction.showToast → BridgeCallbackExecutor → runJavaScript → H5 callback map → Promise resolve可以看到这不是简单写一个if (action xxx)。我希望这个项目从第一天开始就具备框架感Web 容器只负责加载页面BridgeController 只负责通信边界Dispatcher 只负责分发Registry 只负责注册和查询Biz 只负责业务语义和参数校验Imp 只负责调用 HarmonyOS 系统能力CallbackExecutor 只负责把结果安全回调给 H5。这样写的好处是后面新增 API 时不会把所有逻辑堆在一个页面里面。3. 项目目录目前核心目录大致如下entry/src/main/ets/runtime/ bridge/ JavaScriptProxy.ets BridgeController.ets BridgeCallbackExecutor.ets dispatcher/ BridgeDispatcher.ets registry/ HandlerRegistry.ets RuntimeBootstrap.ets api/ ActionNames.ets BridgeTypes.ets biz/ ToastBiz.ets imp/ ToastImp.ets error/ BridgeErrorCode.ets BridgeError.ets logger/ RuntimeLogger.etsH5 Demo 放在entry/src/main/resources/rawfile/web/ index.html js/ myascf.js demo.js css/ demo.css文档放在docs/ overview/ architecture/ stages/ api/ debug/这也是我比较推荐的做法代码和文档同步推进。很多个人项目的问题是代码写得越来越多但别人看不懂。这个项目每做一步都会对应一篇 stage 文档解释“为什么这么做”。4. 第一步先让 ArkWeb 加载本地 H5这个项目没有一上来就写 JSBridge。第一步只做了一件事让 HarmonyOS 应用通过 ArkWeb 加载本地 HTML。Web({src:$rawfile(web/index.html),controller:this.controller})这一点很重要。因为如果 Web 容器都没跑稳后面调 JSBridge、Dispatcher、Registry只会把问题混在一起。所以第一阶段的节奏是先跑通页面 再跑通 H5 → ArkTS 再引入 action 分发 再接真实系统能力 最后处理 timeout 和 callback lost每一步都可以单独验证。5. 第二步H5 如何调用 ArkTSH5 侧暴露了一个统一入口window.myascf.send(action,params)这个方法做了几件事生成requestId组装请求对象把 Promise 的resolve / reject存到 callback map通过 ArkWeb 注入的代理对象调用 ArkTS等 ArkTS 回调后根据requestId找到对应 callback。请求协议很简单{requestId:req_xxx,action:ui.showToast,params:{message:hello from h5}}响应协议也很简单{requestId:req_xxx,code:0,message:showToast success,data:{echoAction:ui.showToast}}这里的关键点是requestId。没有requestIdH5 同时发出多个请求时就不知道哪个响应对应哪个 Promise。6. 第三步不要在 Controller 里写业务一开始为了验证通信BridgeController 可以直接返回 mock response。但是很快会遇到问题如果所有 action 都写在 BridgeController 里它就会变成一个巨大的上帝类。所以项目引入了两个东西BridgeDispatcher HandlerRegistryDispatcher 做分发Registry 做注册。伪代码是这样的consthandlerregistry.get(request.action)if(!handler){returnunknownActionResponse}returnawaithandler(request)这样一来BridgeController 的职责就变轻了。它只负责接收原始消息解析请求调用 Dispatcher把响应回给 H5。它不关心ui.showToast到底怎么实现。7. 第四步Biz / Imp 分层接下来ui.showToast从 mock handler 变成真实能力。但是我没有在 handler 里面直接写promptAction.showToast(...)而是拆成两层ToastBiz ToastImpToastBiz负责参数校验message 是否存在 message 是否是字符串 message 是否为空ToastImp负责调用系统能力promptAction.showToast({message})这样拆的好处是参数校验和系统调用分离以后测试 Biz 时可以 mock Imp以后 Toast 能力变复杂时不会污染 Dispatcher以后新增 API可以复制这套模式。这也是框架项目和普通 Demo 的区别。8. 第五步JSBridge 不是发消息就完了能发消息只是第一步。真正写 JSBridge 时还要处理H5 调用超时ArkTS 回调时 callback 已经不存在ArkTS 回调 JS 字符串转义未知 action参数错误内部异常。所以项目又抽出了一层BridgeCallbackExecutor它专门负责把 BridgeResponse 序列化安全拼接 JS调用runJavaScript记录回调日志处理回调失败风险。H5 侧则补充了 timeoutwindow.myascf.send(ui.showToast,params,{timeout:5000})如果超过时间还没收到响应Promise 会 reject 一个 TIMEOUT。如果响应回来时 callback 已经被删除就记录 CALLBACK_LOST而不是让页面崩掉。9. 目前项目完成度目前已经完成ArkWeb 本地 H5 加载H5 Promise 形式调用JavaScriptProxy 通信边界BridgeControllerBridgeDispatcherHandlerRegistryRuntimeBootstrap 注册ToastBiz 参数校验ToastImp 调用promptAction.showToastBridgeCallbackExecutorTIMEOUTCALLBACK_LOSTUNKNOWN_ACTIONPARAM_ERRORINTERNAL_ERROR分阶段 docs 文档。这已经是一个比较完整的第一阶段运行时骨架。它还不是一个完整小程序框架但已经能说明一件事一个 HarmonyOS Web 容器框架应该如何从通信边界、能力注册、业务分层和回调机制一步步搭起来。10. 后续计划下一步我准备继续做两件事。第一扩展第二个 API例如system.clipboard.writeText system.clipboard.readText这样可以验证 Registry / Biz / Imp 模式是否可复用。第二做一个轻量调试面板展示requestIdactionparamsresponsedurationcodeerror message。这会让整个运行时更适合调试也更像一个真实框架。项目地址https://github.com/lichenyang5/MiniAppRuntime-Harmony如果你也在学习 HarmonyOS / ArkTS / ArkWeb或者对 JSBridge、小程序运行时、Web 容器架构感兴趣可以一起交流。