第一次把 React Native 工程跑到 OpenHarmony 设备上的时候,我反复看了三遍日志才确定 bundle 真的加载成功了。过去几年我一直在做跨平台业务,Android 和 iOS 两端用 RN 确实顺手,但“OpenHarmony”这个新目标一开始我是持怀疑态度的——毕竟这不是换个 SDK 版本的事,而是整套原生基础设施都要重新适配。真正把工程搭起来、把构建链路走通之后,我才发现这里面的架构思路其实相当清晰:JS 业务代码几乎原封不动,原生侧换成了 ArkTS/ArkUI 体系,中间靠 Metro 打包和 C++ 桥接层把两端缝合。这篇文章就围绕我维护的一套演示工程(下面统一叫 projectX)展开,把项目目录结构和跨平台构建流程从头到尾拆开讲,重点说清每个模块是干什么的、构建时数据怎么流动、坑点在哪。如果你是第一次接触这类跨 OS 工程,或者正准备把现有 RN 业务横向扩展到新系统,这篇应该能让你少走不少弯路。
1. 项目整体设计思路与目录结构逐层拆解
1.1 适配层设计的核心思路:JS 复用、原生替换
先说清楚一个底层问题:React Native 为什么能跨平台?它真正跨平台的并不是那套 UI 控件,而是顶层的 JS 业务逻辑、组件树模型、事件分发和状态管理。到了 Android 上,RN 通过 Java 适配层把组件映射成原生 View;到了 iOS 上则是 Objective-C 适配层把组件映射成 UIView。OpenHarmony 的 UI 体系是 ArkUI,原生语言是 ArkTS 加一部分 C++,所以要让 RN 跑起来,核心工作就是把“原生适配层”整个换掉。
这条路并不是从零发明一个新框架。RNOH 的做法是:保留 RN 的 C++ 核心调度逻辑,编译到 OpenHarmony 环境,然后在上层用 ArkTS 重新实现组件、事件和原生模块封装。JS 侧业务代码不需要感知底层变化,你写的 View、Text、Button 在三个平台上最终会落成不同的原生控件,但使用姿势几乎一致。
这种设计的直接结果就是工程结构会变:传统 RN 工程是单仓库、单业务模块,目录里除了 src 就是 android/ios 两个原生壳;而为了适配 OpenHarmony,工程往往演化成 monorepo 多包形态。因为你不再只是维护业务代码,还要同时维护平台适配层、原生宿主工程、构建脚本和示例应用。第一次打开这种工程的人会懵,目录怎么这么多?其实每个目录都有非常明确的分工。
1.2 根目录与核心包目录逐项说明
为了有代入感,直接给一个典型的 RNOH 工程目录,类似我实际在 projectX 里维护的样子:
projectX/ ├── apps/ │ └── demo/ # 业务演示工程 │ ├── src/ # JS/TS 业务源码 │ ├── index.js # RN 入口注册文件 │ ├── package.json │ └── metro.config.js # Metro 打包配置 ├── ohos/ │ ├── entry/ # OpenHarmony 原生宿主模块 │ │ ├── src/main/ets/ # ArkTS 入口与页面 │ │ ├── src/main/resources/ # 图标、字符串、rawfile 资源 │ │ ├── src/main/module.json5 # 模块与能力声明 │ │ ├── build-profile.json5 # 构建配置 │ │ ├── hvigorfile.ts # 模块级构建脚本 │ │ └── oh-package.json5 # 模块依赖声明 │ ├── hvigorfile.ts # 工程级构建脚本 │ └── oh-package.json5 # 工程级依赖声明 ├── packages/ │ ├── react-native-harmony/ # 平台适配层 │ │ ├── src/main/cpp/ # C++ 桥接与渲染适配 │ │ ├── src/main/ets/ # ArkTS 原生组件/模块封装 │ │ └── src/main/interface/ # 对外类型与接口声明 │ └── rn-components/ # 选装原生组件库 ├── node_modules/ ├── package.json # JS 侧统一依赖 ├── tsconfig.json └── build-flags.json5 # 全工程编译开关这里最显眼的就是ohos/目录,它是 OpenHarmony 原生部分,作用相当于传统工程里的android/和ios/。它不是一个普通 Node 项目,而是标准的 OpenHarmony 应用工程,包含entry模块、hvigorfile.ts构建脚本、oh-package.json5依赖声明。理解这一点很关键:你在ohos/目录下做的事,本质上和写一个纯 OpenHarmony 应用一模一样,只是这个应用的内核换成了 RN 运行时。
packages/目录则是平台适配层的所在地。react-native-harmony包是核心,src/main/cpp下面编译的是 RN C++ 内核与 JSI 基础设施,src/main/ets下面则是 ArkTS 封装的原生组件和模块。平时如果只是开发业务,你基本不用动这个目录,但要排查原生问题、扩展新原生能力时,绝大部分时间都耗在这里。
apps/demo/才是开发者日常写业务代码的地方。它和传统 RN 项目几乎没有差别,入口文件、组件文件、样式文件、路由配置都是一样的写法。真正的区别在配置层面,比如metro.config.js要能识别 monorepo 结构,依赖安装时会多出一堆react-native-harmony相关系列包。
1.3 业务代码和 OpenHarmony 原生配置的映射关系
第一次把业务代码接到 OpenHarmony 时,最容易被忽略的是几处“对应关系”。我整理了一个常用映射表,排查问题能用得上:
| 业务侧 | OpenHarmony 原生侧 | 说明 |
|---|---|---|
index.js注册入口 | entry/src/main/ets里的页面加载入口 | 注册的 appName 必须和原生加载时传入的 appName 一致 |
metro.config.js的projectRoot | bundle 加载路径 | 原生侧会按配置路径读取 JS bundle |
package.json里的 JS 依赖 | oh-package.json5里的原生依赖 | 两者都会用到,缺一不可 |
| JS 原生模块调用 | react-native-harmony里的 ArkTS/C++ 实现 | 需要在原生侧注册并导出 |
| release bundle 输出 | entry/src/main/resources/rawfile/ | 直接打进 HAP 包 |
用一个实际场景来说:业务侧AppRegistry.registerComponent('projectXDemo', () => App)注册了入口,那么原生侧在启动一个 ArkUI 页面时需要调用 RN 容器加载接口,传入的组件名也必须是'projectXDemo'。这个名字是纯字符串约定,拼错一个字母,运行时就只会白屏加一串 “Unable to load script” 日志,非常迷惑。所以我的习惯是:把 appName 抽成常量,业务侧和原生侧通过一份共享配置统一引用,避免各写各的字符串。
另一个常见问题是rawfile路径。release 模式下,打包好的 JS bundle 通常会放到entry/src/main/resources/rawfile/目录里,IDE 构建时会原封不动打包进 HAP。如果你把 bundle 输出到了别的自定义目录,构建产物里找不到文件,应用启动后就会一直卡在加载界面。这个路径没有统一标准,完全取决于你的工程怎么组织,但建议固定下来放进 README。
2. 跨平台构建流程与核心环节实现
2.1 “双链构建”的基本链路:JS 业务链与原生链并行
RNOH 的构建流程和传统 RN 有一个核心差异:它同时跑两条构建链路,一条处理 JS 业务,一条处理 OpenHarmony 原生代码。两条链路互不阻塞,但最终会在运行时汇聚到一个应用里。
第一条链路是 JS 业务链。业务源码经过 Metro 打包,输出一份 JS bundle。开发时这份 bundle 通常放在 Metro dev server 上,真机运行通过 http 拉取;发布时则会把 bundle 写入 rawfile 资源目录,随应用一起打包。第二条链路是原生链。ohos/目录里的 ArkTS/C++ 源码经过 hvigor 编译,加上各种元数据、资源和依赖库,最终产出 OpenHarmony 应用安装包(HAP)。
运行时汇聚的过程是这样的:
HAP 启动 -> 加载 JS 运行时 -> 通过 RN 容器读取 bundle -> JS 执行组件渲染逻辑 -> RNOH C++ 层处理组件树 -> ArkTS 层映射为 ArkUI 控件 -> 页面出现在屏幕上整个过程里,业务代码开发者只需要关心第一条链路,原生能力开发则需要同时关注两条链路的边界。很多团队刚迁移到这类工程时,会把大量时间花在调试“JS 代码看起来没问题但页面不显示”的问题上,其实就是没理解这两条链路的时序关系——原生容器还没就绪,JS 已经执行完了,注册的组件自然找不到宿主。
2.2 Metro 打包器配置与 JS Bundle 生成细节
Metro 本身不认识 OpenHarmony,它只负责把 JS/TS 打包成 bundle。RNOH 工程里 Metro 唯一需要特殊照顾的是 monorepo 结构。如果业务代码不在 node_modules 里,而是存在于apps/demo/之外的其他包中,Metro 默认只会监视projectRoot目录,这就导致你改了共享包代码,热更新半天不生效。解决方式是把所有需要监听的目录都写进watchFolders。
我常用的 metro.config.js 长这样:
const path = require('path'); const { getDefaultConfig } = require('@react-native/metro-config'); const projectRoot = __dirname; module.exports = { ...getDefaultConfig(projectRoot), projectRoot, watchFolders: [ projectRoot, path.resolve(projectRoot, 'node_modules'), path.resolve(projectRoot, 'packages'), path.resolve(projectRoot, 'apps'), ], transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: true, }, }), }, resolver: { sourceExts: ['tsx', 'ts', 'jsx', 'js', 'json', 'cjs', 'mjs'], nodeModulesPaths: [ path.resolve(projectRoot, 'node_modules'), path.resolve(projectRoot, 'packages'), ], }, };有几个点值得展开。experimentalImportSupport保持 false 是稳妥做法,因为 Metro 的 import 支持本质上还是转成 require,开启后容易触发边界问题。inlineRequires: true是 Metro 的一个优化开关,它会把模块引用内联到调用处,减少启动时的模块初始化数量,对 OpenHarmony 这种新适配的平台尤其推荐,因为端上运行时对新架构的兼容层做的工作更多。
打包命令方面,RNOH 支持为 harmony 平台指定打包目标:
npx react-native bundle \ --platform harmony \ --dev false \ --entry-file index.js \ --bundle-output ./ohos/entry/src/main/resources/rawfile/bundle.js \ --assets-dest ./ohos/entry/src/main/resources/rawfile注意--platform harmony是这一套适配工具链扩展出来的产物名。打包完成后,检查一下 rawfile 目录里是否同时存在 bundle.js 和 assets 目录,缺少任何一个都可能导致运行期找不到图片资源或脚本文件。我碰到过好几次:bundle 成功打了,但--assets-dest配错位置,结果图标和启动图全部加载失败,界面显示出来了但丑得没法看。
2.3 hvigor 原生构建与依赖安装流程
hvigor 是 OpenHarmony 侧的构建工具,职责上等同于 Android 的 Gradle。它的配置分布在工程根目录和每个模块目录下:根目录的hvigorfile.ts定义整个工程的任务集合,模块目录下的hvigorfile.ts定义该模块的编译行为。
一个最简工程级hvigorfile.ts大概是这样的:
import { appTasks } from '@ohos/hvigor-ohos-plugin'; export default { system: appTasks, plugins: [] };模块级类似,区别在于引用的是hapTasks或featureTasks。这里的抽象层级比 Gradle 简单,普通项目基本不用改动模板,真正需要配置的是oh-package.json5里的依赖。文件里可以声明对本地适配层包的依赖,写法非常接近 npm 的 file: 依赖:
{ "name": "projectX", "version": "1.0.0", "description": "RNOH demo entry", "main": "", "dependencies": { "react-native-harmony": "file:../packages/react-native-harmony", "rn-components": "file:../packages/rn-components" } }依赖安装命令是ohpm install,这个管理器会生成oh-package-lock.json5锁定版本。在实际项目中,团队最容易因为“JS 依赖装了,但原生依赖没装”而出问题。JS 侧npm install只能把react-native-harmony的 npm 包拉到 node_modules,原生侧还必须在ohos/目录下执行一次ohpm install,让 hvigor 能解析到适配层源码。这两步是分开的,新接手项目的同学经常只跑其中一个,然后就开始怀疑人生。
原生构建命令通常在 IDE 里点执行,也可以在命令行跑:
hvigorw assembleHap --mode module -p product=default整个编译过程会先把 adapt 层 C++ 源码与本地的 SDK 头文件编译链接,再编译 ArkTS 侧的封装,最后打包成 HAP。C++ 编译比较耗时,首次全量构建可能要几分钟到十几分钟不等,看机器性能。如果没加过原生代码,之后构建基本走增量路径,速度会快很多。
2.4 ArkTS 原生容器与组件映射的关键点
RNOH 能把 React 组件树搬到 OpenHarmony 上,靠的是 ArkTS 侧封装的原生容器。RN 里的<View>在 Android 上对应 ViewGroup,在 iOS 上对应 UIView,在 OpenHarmony 上则对应 Column、Stack、Row 这一系 ArkUI 布局容器。RNOH 的工作就是在 C++ 层面计算出 Flexbox 布局树,再翻译成 ArkUI 布局参数,最终生成真正的原生控件。
这个过程有几个容易踩的差异点。overflow属性在部分端上表现不一致,OpenHarmony 侧早期版本对裁剪边界的处理跟 iOS 更像,如果你的业务里大量使用了带 borderRadius 的图片裁剪,需要单独验证。还有zIndex、绝对定位等布局场景,不同原生端解释差异比想象中大,出现问题先看是不是“同一份 UI 在不同端表现不一样”,而不是急着改业务代码。
原生模块注册方面,RNOH 的流程比 Android 传统 RN 工程显式很多。新增一个原生方法时,我不建议只改 JS 侧然后直接调用,因为自动链接在这个平台上并不完全可靠。正确的姿势是确认三层都改到位:
- ArkTS 侧实现模块方法,并继承 TurboModule 或对应基类。
- 在容器初始化时把模块实例注册进 TurboModuleProvider,并保持注册名和 JS 侧
TurboModuleRegistry.getEnforcing的名字一致。 - JS 侧定义调用入口和类型声明,确保方法名、参数类型对齐。
把这三层理解成“服务端接口、网关路由、前端 SDK”的关系,排查问题时就会很清楚:JS 侧报找不到模块,大概率是第二层的注册名写错了;返回 undefined,则多是第一层返回值类型和第三层声明不一致。
3. 实操复现:从环境准备到三端运行
3.1 环境准备与版本对齐
RNOH 工程对环境的敏感程度比传统 RN 高得多。我的建议是先固定一套组合,整条链路跑通之后再去升级。Node 选 18 或 20 的长稳定版本,JDK 选 17,OpenHarmony SDK 用适配包文档中明确验证过的 API版本,不要直接装到最新——新系统版本对 API 的定义和裁剪会更激进,反而容易编译报错。
IDE 方面,OpenHarmony 原生开发通常使用官方配套的开发环境,它同时负责打开ohos/工程、管理 SDK、配置真机签名和调试。理论上命令行也能完成其他部分,但签名和设备管理还是 IDE 里操作最省事。首次创建项目时,记得在 IDE 里检查 SDK 路径和构建工具的版本是否和工程build-profile.json5里声明的版本一致。不一致时,IDE 通常会在构建前报错,这时候不要硬改 build-profile 去匹配本地 SDK,而是考虑下载工程声明的 SDK 版本,因为适配层包的 C++ 代码是明确按某个 API 版本编译的,换成其他版本很容易出现链接错误。
3.2 脚手架工程初始化与目录检验
OpenHarmony 侧工程可以用 IDE 自带向导生成,也可以从已有模板复制。初始化完成后,一个干净工程只有ohos/和必要配置文件,还没有业务代码。这时候我会按以下顺序检查工程完整性:
# 1. 确认 Node 环境与包管理器版本 node -v npm -v ohpm -v # 2. 安装 JS 侧依赖 npm install # 3. 在 ohos 目录安装原生侧依赖 cd ohos && ohpm install && cd .. # 4. 启动 Metro,确认能正常监听文件变化 npm run start这三步都通过后,目录里应该已经能看到node_modules、oh_modules和各类锁文件。注意oh_modules是原生依赖安装的产物,和node_modules平级。如果用了本地依赖方式安装适配层包,oh_modules下会生成对应的包连接符号,这里没有内容的话,IDE 打开后 C++ 编译阶段必然报找不到头文件。
3.3 示例页面构建、启动与调试
业务侧写一个常见计数器页面,用来验证整条链路:
import React, { useState } from 'react'; import { View, Text, Button, StyleSheet } from 'react-native'; function App() { const [count, setCount] = useState(0); return ( <View style={styles.container}> <Text style={styles.text}>count: {count}</Text> <Button title="加一" onPress={() => setCount((c) => c + 1)} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, text: { fontSize: 24 }, }); export default App;入口文件:
import { AppRegistry } from 'react-native'; import App from './src/App'; AppRegistry.registerComponent('projectXDemo', () => App);开发模式下,启动 Metro 之后不要急着构建 release,先通过 IDE 跑一个 debug 包。真机运行时,如果页面白屏且日志显示 “Unable to load script”,优先检查设备能不能访问到电脑的 Metro 端口。Android 时代一条adb reverse就能解决,OpenHarmony 工具链对应的命令行为在不同版本上有些差异,最稳妥的方式是让手机和电脑连同一个局域网,在 Metro 的配置里绑定局域网 IP。环境变量或者启动参数中设置监听地址,避免默认只监听 localhost。
Release 构建前,先把 bundle 手动生成并放进 rawfile,再执行 hvigor 打包。检查产物是否包含 bundle.js 的关键还是看 rawfile 路径,这一步在 2.2 已经展开过。整个流程跑通后,再回头看目录结构和构建过程,你会发现每层其实只做了一件很具体的事,串起来的逻辑并不复杂。
3.4 三端共享代码时的平台差异适配
跨平台不是“一份代码到处跑”这么简单,更多时候是“一份逻辑,多端微调”。RNOH 工程里最常遇到的是平台样式差异。我们可以在样式或配置层面对齐三端:
import { Platform, StyleSheet } from 'react-native'; const styles = StyleSheet.create({ button: { ...Platform.select({ android: { elevation: 2 }, ios: { shadowOpacity: 0.2 }, default: { shadowOpacity: 0.2 }, }), }, });Platform.select的default分支就是对未识别的平台生效,OpenHarmony 场景下会走这里。这个方法高度建议在团队内统一使用,尽量不要散落各种if (Platform.OS === 'harmony')分支。三端差异还包括键盘弹起行为、状态栏高度、图片加载格式等,这些细节不在构建范围,但会影响最终体验。我的原则是业务逻辑尽量共享,视觉细节以小配置文件维护,而不是在代码里到处写平台分支。
文件级差异处理也类似。Metro 在解析模块时可以优先尝试带平台后缀的文件,类似 Android 的 sourceSets。如果工程里已经存在xxx.android.ts、xxx.ios.ts,再增加xxx.harmony.ts时,需要确保 Metro 的resolver.sourceExts里包含它的解析规则,或者利用默认匹配逻辑让静态资源加载器能找到对应文件。这一个点最容易被忽略,因为三个平台的命名习惯不一样,写错一个后缀,编译期完全不报错,运行时就报 module not found。
4. 常见问题与排查技巧实录
4.1 版本不匹配导致 JS 运行时报错
RN 的版本迭代很快,适配层包的发布节奏不可能跟得一模一样。如果你发现 JS 层引入组件时提示某个原生模块不存在,或者渲染出来是空白,先别怀疑业务代码。检查package.json里的react-native版本和react-native-harmony适配包要求的版本是否匹配。版本组合一旦错位,C++ 桥接层的行为和 JS 侧 JSI 接口无法对齐,最简单的表现就是所有原生组件全部失效。
处理方式是:先在适配层包的文档里找“supported versions”表,锁定一个经过验证的版本组合,比如某适配包对 RN 0.72.x 系列最稳,那就不要用 0.73 或更新版本。升级时至少要同时升级react-native与适配层包,并且按 release note 里的步骤重新生成模板工程做比对。硬扛升级很容易把半天时间搭进去。
4.2 依赖下载失败或包解析异常
这个问题有两种典型形态。一种是ohpm install时网络原因导致部分依赖一直拉不全,表现为构建到最后突然说某个头文件或容器类找不到。另一种是团队里有人把 node_modules 或 oh_modules 删过,之后 IDE 缓存还停留在旧状态,构建日志里到处是 “Cannot find module”。
我的处理路径是:
- 删除
node_modules、oh_modules和锁文件,重新 install。 - 如果还是拉不动某些依赖,先检查三方仓库配置,确认团队内部是否应该走私有镜像或本地镜像源。
- 如果网络条件确实差,就提前把适配层包用
npm pack打到本地 tar 包,然后在 package.json 里用file:或直接传绝对路径引用。
这里有个团队协作技巧:不要把整个node_modules或者oh_modules提交进 git,但要确保锁文件完整。每次安装完,确认oh-package-lock.json5有变更就提交,它记录了实际可用的依赖树,是复现环境的关键。
4.3 Metro 连不上设备的排查思路
开发模式常见的现象是:IDE 里应用启动正常,但页面白屏,日志里反复出现网络请求失败。这时候按顺序排查:
第一,从设备浏览器或者开发者工具里直接访问电脑的 Metro 地址,确认网络通不通。第二,检查 Metro 是否只监听了 localhost,如果是,需要让它在局域网地址上监听。第三,确认项目的 dev server 入口没有写死 localhost,很多老项目在index.js或原生初始化代码里硬编码了地址,设备通过局域网访问时就会被拒掉。
现象:真机一直白屏,Metro 日志无输出 原因:设备无法访问电脑 8081 端口 验证:同一局域网下直接访问 http://电脑IP:8081/status 修复:Metro 监听 0.0.0.0,并将 dev server 地址改为电脑局域网 IP在生产发布模式下不存在这个问题,因为 bundle 已经打进包体,不需要网络。所以如果时间紧,先打一个 release 包验证业务本身是否正常,再回来慢慢排查开发模式是更稳妥的思路。
4.4 原生模块桥接不生效
如果你新增了一个原生模块,JS 侧调用时却一直拿到 undefined 或者报 “TurboModule is not defined”,问题通常出在注册环节。先在原生侧确认模块实例是否真的被 fill 进了容器,再核对注册的字符串 key 和 JS 侧调用的模块名是否完全一致。这类问题有一个特点:错误日志不一定在第一屏,经常在调用某个方法时才爆出来,非常折磨人。
另外要注意返回类型。RNOH 的 TurboModule 在跨语言调用时对类型比较敏感,JS 侧声明返回number,ArkTS 侧如果返回的是Number包装对象,也可能解析失败。规范的做法是:原生模块方法返回值尽量用基础类型,复杂对象用普通 map 或 JSON 字符串传递。这个方法在 Android/iOS 上也适用,只是 OpenHarmony 适配层更严格,按最严格的标准写就不会出错。
4.5 高频问题速查表
| 症状 | 可能原因 | 首选排查方式 |
|---|---|---|
| 启动白屏,日志报 Unable to load script | bundle 没找到或 dev server 不可达 | 检查 rawfile 路径 / 设备网络 |
| 打包报缺 C++ 头文件 | 适配层原生依赖未安装 | 在 ohos 目录执行 ohpm install |
| JS 调用原生模块返回 undefined | TurboModule 未注册或类型不匹配 | 核对注册名与返回类型 |
| 构建报 SDK 版本不一致 | 本地 SDK 与工程 build-profile 不匹配 | 下载与工程匹配的 SDK 版本 |
| Metro 热更新不生效 | watchFolders 没包含共享包目录 | 把 packages 目录加进 watchFolders |
| 页面能显示但图片资源缺失 | assets 资源输出路径不对 | 检查 --assets-dest 与 rawfile |
这张表是项目组内部长期排查后沉淀的总结,基本覆盖了第一周接触 RNOH 工程会碰到的绝大部分障碍。拿着它对照处理,通常半小时内能定位到根因。
5. 构建提速、版本治理与团队协作建议
5.1 Metro 缓存与 hvigor 增量编译的实际用法
构建慢在 RNOH 工程里更容易发生,因为除了 JS 打包,还有原生 C++ 编译。Metro 本身有缓存,但 monorepo 结构调整频繁,缓存经常失效。我的做法是不要在开发阶段频繁 reset cache,而是让 Metro 长驻运行,同时把 watchFolders 配准。只有当你改了 Metro 配置、升级了依赖,确定出现诡异问题时,再执行一次npx react-native start --reset-cache。每次改完配置就 reset,反而会把缓存命中率打下来,热更新变慢。
hvigor 的增量编译和 Gradle 类似,但细节差异不小。如果你发现只改了一行 ArkTS,构建却把 C++ 全量重新编了一遍,很大概率是构建脚本里某个全局参数或资源目录被标记成强制重编。这种情况下检查build-profile.json5里的工程级配置,看是否开启了不必要的依赖清理。真正频繁改动 C++ 时,把适配层包当作独立工程单独编译验证后再集成,比直接在主工程里全量构建省时间得多。
5.2 锁定工具链、规范依赖提交与 CI 配置
多人协作时,第一优先级是锁版本。README 里必须写清 Node、JDK、SDK、Metro 与适配层包之间的关系。可以准备一个环境检查脚本,每次 clone 后自动比对本地版本和工程声明版本,不一致就直接给出提示,省去人工看文档的时间。除此之外,把package-lock.json和oh-package-lock.json5纳入必须提交清单,这是复现工程环境的基础。
CI 配置要区分两件事:业务 CI 和原生 CI。业务 CI 可以按照传统 RN 一样跑 lint、单测、构建 bundle,速度也快。原生 CI 则需要一台安装了完整 IDE 和 SDK 的构建机,模拟完整 hvigor 构建。如果团队预算有限,也至少要确保发布包构建在干净环境里跑一次,避免“本地能过,打包机必炸”的经典惨案。
我强烈建议在 CI 里增加一个“三端冒烟用例”,也就是同一份核心业务代码在 Android、iOS、OpenHarmony 三个平台都编译并启动,再跑几个基础交互。这个用例不需要覆盖全部功能,只为了在早期发现平台适配层出现的回归问题。RNOH 适配层升级后,跑一次冒烟测试基本能判断是否值得继续深入验证。
治理层面还有一件事容易被忽视:bundle 产物到底要不要提交到仓库。如果团队需要频繁复用同一个 JS 包发布不同版本,把 bundle 纳入产物管理是有意义的;如果是常规迭代,我更推荐提交源代码和锁文件,bundle 统一由 CI 生成,避免本地手工生成的 bundle 混入版本历史。这个问题没有绝对正确,但要形成团队共识,否则每次构建后都会有“你的包和我的包行为不一样”的争议。
最后再分享一个个人体会:这类跨 OS 工程真正需要长期维护的,不是业务代码,而是那套“平台差异清单”。我每踩到一个坑,都会记录下现象、根因、解决方案和验证方式。过一个月再回头看,这份清单比任何文档都有用。如果你正准备接手类似工程,建议从搭建环境开始就同步维护一份这样的记录,后面做版本升级和新功能扩展时,能省下的时间远超你写文档的时间。