鸿蒙 PC 上用 RNOH 开发开源鸿蒙应用
一次真实、完整、可复现的环境搭建记录:在一台 HarmonyOS PC(HUAWEI MateBook Pro / HAD-W32)上,从零把 React Native for OpenHarmony(RNOH)的开发环境跑通,并让一个 RN 应用真正在本机安装、启动、渲染出界面。
本文不是官方文档的复述,而是踩过的每一个坑 + 定位过程 + 最终解法。所有命令、版本号、报错信息都来自真实终端。

0. 先交代结论
如果你只想看结果:最终形态是
- React Native 0.84.1 + RNOH 0.84.4(对应官方 0.84-stable 分支)
- 工具链由 HNP 包 hmos-clt 提供:ohpm 6.0.1 / hvigorw 7.26.8 / hdc 3.2.0f / OpenHarmony SDK API 26
- Node 用 harmonybrew 的 v26.12.0,hvigor 构建不需要 JDK
- 工程结构:AwesomeProject/(RN 侧)+ AwesomeProject/harmony/(鸿蒙侧)
- 三个必须知道的反直觉坑:CLI 在 Node 22+ 崩、本机文件系统偶发 EINTR、构建期间改源码会让 ninja 状态脱节
应用最终在这台鸿蒙 PC 上跑起来的样子:
Welcome to React Native
Version: 0.84.1
JS Engine: Hermes (250829098.0.9)
1. 为什么要在一台鸿蒙 PC 上折腾 RNOH
React Native for OpenHarmony(RNOH)是 OpenHarmony 社区里把 React Native 框架适配到鸿蒙的工程,它让你用 RN 的写法(JS/TS + React 组件)去开发鸿蒙原生应用。
官方文档 CPF-RN/ohos_react_native[1] 的 《环境搭建》[2] 章节,默认假设你在一台 Windows / macOS 上用 DevEco Studio 图形界面做开发:
- 装 DevEco Studio[3]
- 用 IDE 新建 Empty Ability 工程
- 在 IDE 里点 File > Project Structure > Signing Configs,登录华为账号自动签名
- 点 Run 运行
但这次的情况不太一样:开发机本身就是一台鸿蒙 PC。于是问题变成——在没有把 DevEco Studio 的 SDK 目录单独铺开的情况下,能不能纯命令行把这条路走通?
答案是能。而且走通之后发现,命令行链路其实比“必须开 IDE”清爽得多。
2. 环境侦察:这台机器上到底有什么
动手之前先摸清楚家底。这里有一个很容易踩的认知陷阱:官方的 CLI 工具链并不在常规的“安装目录”里,而是被打包成了一个 HNP 包。
2.1 基础系统信息
$ uname -a
HarmonyOS localhost HongMeng Kernel 1.13.0 #1 SMP aarch64 Toybox
$ hdc -v
Ver: 3.2.0f
一台 aarch64 的鸿蒙机器,hdc(鸿蒙的设备调试工具)已经有了。
2.2 关键发现:HNP 包里的完整工具链
先看有哪些工具:
$ for c in git node npm java cmake ninja python3 hdc ohpm hvigorw; do
printf “%-8s ” “$c”; command -v $c || echo “MISSING”
done
git MISSING
node /storage/Users/currentUser/.harmonybrew/bin/node
npm /storage/Users/currentUser/.harmonybrew/bin/npm
java MISSING
cmake MISSING
ninja MISSING
python3 MISSING
hdc /storage/Users/currentUser/.local/bin/hdc
ohpm MISSING
hvigorw MISSING
git、java、python3、cmake 全没有,ohpm/hvigorw 也不在 PATH 上。乍一看条件很差。
但顺着 hdc 的软链找过去,会发现一个 HNP(HarmonyOS Native Package)包:
$ ls /data/service/hnp/
hmos-clt.org
$ ls /data/service/hnp/hmos-clt.org/hmos-clt_1.0.0/
ace-server clangd codelinter hvigor node ohpm sdk
hmos-clt(HarmonyOS Command Line Tools)就是一个完整的命令行工具链,里面包含:
| 目录 | 内容 |
|---|---|
| ohpm/ | 鸿蒙包管理器 ohpm 6.0.1 |
| hvigor/ | 鸿蒙构建系统 hvigorw 7.26.8 |
| node/ | 自带 Node v24.18.1 |
| sdk/ | 完整 OpenHarmony SDK
(ets / js / native / previewer / toolchains) |
| codelinter/ | 代码检查 |
SDK 版本:
$ cat …/sdk/default/openharmony/ets/oh-uni-package.json
{
“apiVersion”: “26”,
“displayName”: “Ets”,
“platformVersion”: “26.0.0”,
“releaseType”: “Release”,
“version”: “26.0.0.105”
}
API 26,正好和本机 HarmonyOS 7.0 匹配。
还有一个意外之喜——SDK 里自带了 cmake 和 ninja:
$ …/sdk/default/openharmony/native/build-tools/cmake/bin/cmake –version
cmake version 3.28.2
$ …/sdk/default/openharmony/native/build-tools/cmake/bin/ninja –version
1.13.2
也就是说,前面看到的 cmake MISSING / ninja MISSING 根本不是问题——hvigor 会自己从 SDK 里找,不需要我们手工装。
2.3 hvigor 到底需不需要 JDK?
这是官方文档没写清楚、但会直接决定你要不要下一整个 JDK 的问题。
DevEco Studio 会捆绑一个 JBR(JetBrains Runtime),所以大家习惯性认为“鸿蒙构建要 Java”。但在纯命令行环境下,得验证。
先看代码:
$ grep -rn “JAVA_HOME\|javac\|java -jar” …/hvigor –include=*.js
(无输出)
再看实际调用的工具:
$ file …/sdk/default/openharmony/toolchains/lib/hap-sign-tool
hap-sign-tool: ELF shared object, 64-bit LSB arm64 …
$ file …/sdk/default/openharmony/toolchains/lib/ohos_packing_tool
ohos_packing_tool: ELF shared object, 64-bit LSB arm64 …
签名工具和打包工具都是原生 ELF 可执行文件,不是 .jar 包。
结论:hvigor 构建不需要 JDK。 后来整条链路(编译 → 打包 → 签名)也验证了这一点。
省掉一整个 JDK 安装,这在鸿蒙 PC 上(apt/brew 生态都不完整)意义很大。
3. 版本选型:为什么是 0.84 而不是最新的 0.86
RNOH 是多版本线并行的项目,同一个仓库里有 0.72-stable、0.77-stable、0.82-stable、0.84-stable 等多个分支。
选版本有三个约束:
- RN 和 RNOH 的版本必须对齐(前端包与原生包版本号对齐)
- OHOS SDK 要支持(本机只有 API 26)
- ohpm 没有 dist-tag,原生包必须按具体版本号安装
3.1 先看有哪些版本
先查 npm 上 `@react-native-oh/react-native-harmony`[4] 的 dist-tag:
$ npm view @react-native-oh/react-native-harmony dist-tags –json
{
“beta”: “0.82.7”,
“0.72-stable”: “0.72.140”,
“0.77-stable”: “0.77.71”,
“0.82-stable”: “0.82.30”,
“0.84-rc”: “0.84.2”, <– 注意:0.84 线上还没有 stable tag
“latest”: “0.72.145”
}
有个细节值得注意:**0.84 线当时只有 0.84-rc tag,没有 0.84-stable**。但实际版本列表里已经有更新的补丁号:
$ npm view @react-native-oh/react-native-harmony versions –json | grep 0.84
“0.84.1” “0.84.2” “0.84.3” “0.84.4”
所以文档里说的”用 0.84-stable 拿最新稳定版”在当时是不成立的——这个 tag 还没发。得直接指定具体版本号。
3.2 确定配套的 RN 版本
不要猜,直接读包的 peerDependencies:
$ npm view @react-native-oh/react-native-harmony@0.84.4 peerDependencies –json
{ “react-native”: “0.84.1” }
于是配套关系确定:
| 包 | 版本 |
|---|---|
| react-native | 0.84.1 |
| @react-native-oh/react-native-harmony | 0.84.4 |
| @react-native-oh/react-native-harmony-cli | 0.84.4 |
3.3 顺手验证镜像源
后面要配华为云 npm 镜像[5]加速,先确认它不会漏包——RC 版本的包在镜像上容易滞后:
$ npm view @react-native-oh/react-native-harmony@0.84.4 version \
–registry=https://repo.huaweicloud.com/repository/npm/
0.84.4
$ npm view react-native versions –json \
–registry=https://repo.huaweicloud.com/repository/npm/ | grep 0.84
“0.84.0” “0.84.1”
镜像覆盖完整,可以放心配。
4. 补齐工具链
盘点下来只缺一样东西:git。
ohpm、hvigorw、hdc、cmake、ninja、SDK 全都在 hmos-clt 里,node 有 harmonybrew。但 git 没有——而 RN 开发(初始化工程、拉依赖、管理版本)没它很难受。
4.1 第一个坑:harmonybrew 装不上 git
鸿蒙 PC 上有 harmonybrew[6](Homebrew[7] 的鸿蒙移植版),理论上 brew install git 就行:
$ brew install git
==> Installing git dependency: bzip2 (1.0.8_2)
==> Pouring bzip2-1.0.8_2.arm64_ohos.bottle.tar.gz
Error: git: Interrupted system call @ rb_file_s_stat –
…/vendor/bundle/ruby/4.0.0/extensions/aarch64-linux-ohos/…/ruby-prof-2.0.5
重试一次,报错换了个文件,但病因一样:
Error: Interrupted system call – bs_fetch:open_current_file:open:
…/Homebrew/retryable_download.rb
注意这个报错——Interrupted system call,也就是 EINTR。它后面还会反复出现,是这台机器上的一条主线(见第 9 章)。而且关键在于:它在读 Homebrew 自己的 Ruby 源码时就失败了,说明不是 git 这个包的问题,是 Homebrew 在这台机器上的文件访问不稳定。
与其跟 Homebrew 缠斗,不如找现成的。
4.2 曲线救国:Homebrew 内置的 portable-git
Homebrew 自己需要一个 git 来更新 formula,所以它内置了一份 portable-git:
$ ls ~/.harmonybrew/Homebrew/Library/Homebrew/vendor/portable-git/current/bin/
git git-cvsserver git-receive-pack git-shell git-upload-archive git-upload-pack scalar
$ …/portable-git/current/bin/git –version
git version 2.55.0
版本 2.55.0,可用。但有一个必须确认的点:它有没有 https 支持。git 的远程 helper 在 libexec/git-core/ 下,早期版本的目录列表里看不到 git-remote-https,如果缺了就没法 git clone https://…:
$ ls …/libexec/git-core/git-remote-https
…/libexec/git-core/git-remote-https # 存在,放心
链接到 PATH 里(~/.local/bin 已经在前面的 .zshrc 里):
P=~/.harmonybrew/Homebrew/Library/Homebrew/vendor/portable-git/current
ln -sf $P/bin/git ~/.local/bin/git
ln -sf $P/bin/scalar ~/.local/bin/scalar
验证不能只看 –version,要真拉一次仓库:
$ git –version
git version 2.55.0
$ git clone –depth 1 https://atomgit.com/CPF-RN/ohos_react_native.git _test
Cloning into ‘_test’…
Updating files: 100% (4561/4561), done. # https 克隆正常
4.3 顺便排掉一个”幽灵 node”
在调试 PATH 顺序时撞见一个诡异现象:把 ~/.local/bin 排到最前面后,node 直接报
sh: /storage/Users/currentUser/.local/bin/node: Permission denied
查下去发现 ~/.local/bin/ 里躺着三个指向另一个 Node 安装的软链:
$ ls -la ~/.local/bin/node ~/.local/bin/npm
node -> /storage/Users/currentUser/.local/opt/node/bin/node
npm -> /storage/Users/currentUser/.local/opt/node/bin/npm
也就是这台机器上其实有三份 Node:
| Node | 版本 | npm | 状态 |
|---|---|---|---|
| harmonybrew | v26.12.0 | 11.20.0 | ✅ 实际生效 |
| ~/.local/opt/node | — | 10.9.3 | ⚠️ 软链不可执行 |
| hmos-clt/node | v24.18.1 | — | 备用 |
之所以日常没出问题,是因为 .zshrc 里 brew shellenv 把 harmonybrew 的路径排在了 ~/.local/bin前面,那份坏软链被遮住了。
这提醒了一件事:验证环境时要用“真实 shell 加载顺序”去测,而不是自己拼一个 PATH 去测,否则测出来的问题可能根本不存在,或者反过来漏掉真实问题。用 zsh 复现真实环境:
$ zsh -c ‘source ~/.zshrc >/dev/null 2>&1; command -v node; node -v’
/storage/Users/currentUser/.harmonybrew/bin/node
v26.12.0
4.4 写入环境变量
现在把 RNOH 需要的环境变量固化到 ~/.zshrc:
# ===== HarmonyOS / React Native for OpenHarmony (RNOH) 开发环境 =====
# hmos-clt (HarmonyOS Command Line Tools) 提供 hdc / ohpm / hvigorw / OpenHarmony SDK
export HMOS_CLT_HOME=”/data/service/hnp/hmos-clt.org/hmos-clt_1.0.0″
export DEVECO_SDK_HOME=”$HMOS_CLT_HOME/sdk”
export OHPM_HOME=”$HMOS_CLT_HOME/ohpm”
# hdc 在 SDK 的 toolchains 下;ohpm / hvigorw 在工具链根目录
export PATH=”$HMOS_CLT_HOME/ohpm/bin:$HMOS_CLT_HOME/hvigor/bin:$HMOS_CLT_HOME/sdk/default/openharmony/toolchains:$PATH”
# RNOH 工程默认走 CAPI 架构,必须为 1
export RNOH_C_API_ARCH=1
# hdc 服务端口,任意未被占用的端口即可
export HDC_SERVER_PORT=7035
两个变量是 RNOH 的硬要求,漏了会出各种怪问题:
- **RNOH_C_API_ARCH=1**:RNOH 的 Demo 工程默认是 CAPI 版本,文档里专门有一节讲它。少了它,C++ 侧的桥接会走另一套逻辑。
- **HDC_SERVER_PORT=7035**:hdc 服务端口。真机调试(安装、日志、推送 bundle)都要用。
4.5 配置 npm 镜像源
~/.npmrc(保留原有的 prefix,加上华为云镜像):
prefix=/storage/Users/currentUser/npm
registry=https://repo.huaweicloud.com/repository/npm/
strict-ssl=false
改完 registry 必须清缓存,否则旧的 registry 结果还会命中:
$ npm cache clean –force
官方文档里还写了 sslVerify=false,但 npm 并没有这个配置项(那是别的工具的参数),加了只会产生 unknown config 警告,所以这里只保留有效的 strict-ssl。
4.6 验收
$ zsh -c ‘source ~/.zshrc >/dev/null 2>&1
for c in node npm ohpm hvigorw hdc git; do
printf “%-8s %s\n” “$c” “$(command -v $c)”
done’
node /storage/Users/currentUser/.harmonybrew/bin/node # v26.12.0
npm /storage/Users/currentUser/.harmonybrew/bin/npm # 11.20.0
ohpm …/hmos-clt_1.0.0/ohpm/bin/ohpm # 6.0.1
hvigorw …/hmos-clt_1.0.0/hvigor/bin/hvigorw # 7.26.8
hdc …/sdk/default/openharmony/toolchains/hdc # 3.2.0f
git /storage/Users/currentUser/.local/bin/git # 2.55.0
顺手确认一件事:**ohpm / hvigorw 只从 PATH 里取 node**(脚本里是 NODE_CMD=node,或读 NODE_HOME),并不强制用自带的 Node。实测系统 node 26 与它们兼容:
$ ohpm -v # 6.0.1
$ hvigorw –version # 7.26.8
所以不需要把 hmos-clt/node 塞进 PATH 去替换系统 Node,保持一份 Node 更干净。
5. 创建 React Native 工程
5.1 初始化
文档明确说了 npx react-native init 已废弃,要用新的 `@react-native-community/cli`[8](RN 0.84.1 的 Release Notes[9]):
cd /storage/Users/currentUser/Desktop/rn
npx –yes @react-native-community/cli@latest init AwesomeProject \
–version 0.84.1 \
–pm npm
几个细节:
- –version 0.84.1:必须显式指定,否则会拉到最新的 RN,和 RNOH 0.84.4 对不上(前面靠 peerDependencies 验过就是 0.84.1)。
- –pm npm:这个参数建议加上。CLI 初始化时会问用哪个包管理器(yarn/npm/pnpm/bun),在非交互场景下就会卡住。显式指定后全程无提示。
- –skip-git-init 这类参数没加:机器上已经有 git 了,让它正常初始化仓库即可。
结果:
✔ Downloading template
✔ Copying template
✔ Processing template
✔ Installing dependencies
产物是一个标准的 RN 工程(App.tsx / metro.config.js / android/ / ios/ / package.json …),依赖装完约 442 MB。
5.2 安装 RNOH 鸿蒙化依赖
cd AwesomeProject
npm install @react-native-oh/react-native-harmony@0.84.4 \
@react-native-oh/react-native-harmony-cli@0.84.4
装完 package.json 里多了两个依赖:
“dependencies”: {
“@react-native-oh/react-native-harmony”: “^0.84.4”,
“@react-native-oh/react-native-harmony-cli”: “^0.84.4”,
“react-native”: “0.84.1”,
…
}
这一步会在 node_modules/@react-native-oh/react-native-harmony/ 下带出三个 .har(鸿蒙原生包):
$ ls -la node_modules/@react-native-oh/react-native-harmony/*.har
react_native_openharmony.har 80138612 # 80 MB,debug 用
react_native_openharmony_release.har 13567179 # release:cpp 编成 .so,ts/ets 保留源码
react_native_openharmony_release2.har 13588935 # release2:ts/ets 也编成 .abc 字节码
80 MB 的 har,下载和后续 ohpm install 都会比较久,心里有数就行。
注意:ohpm 没有 dist-tag,原生包只能按具体版本号装。所以拖依赖时前端包 0.84.4 和原生包 0.84.4 要同时写死,别一边写 tag 一边写版本号。
5.3 配置 metro
RN 的 JS 打包靠 Metro[10]。要让它认识鸿蒙平台,得挂上 RNOH 的 metro 配置。原本的 metro.config.js 只有一行 mergeConfig,改成:
const {mergeConfig, getDefaultConfig} = require(‘@react-native/metro-config’);
const {createHarmonyMetroConfig} = require(‘@react-native-oh/react-native-harmony/metro.config’);
/** @type {import(‘@react-native/metro-config’).MetroConfig} */
const config = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
};
module.exports = mergeConfig(
getDefaultConfig(__dirname),
createHarmonyMetroConfig({
reactNativeHarmonyPackageName: ‘@react-native-oh/react-native-harmony’,
}),
config,
);
关键就是 createHarmonyMetroConfig——它注册了 harmony 这个平台,Metro 才知道该为鸿蒙产出什么样的 bundle。
5.4 加一条 bundle 脚本
在 package.json 的 scripts 里加:
“dev”: “react-native bundle-harmony –dev”
这条命令由 react-native-harmony-cli 提供(还记得它注册了 bundle-harmony 命令吗),作用是生成鸿蒙用的 JS bundle。
5.5 一个会白屏的坑:App.tsx 的 safe-area
RN 0.84 的模板 App.tsx 长这样:
import { SafeAreaProvider, useSafeAreaInsets } from ‘react-native-safe-area-context’;
function App() {
return (
<SafeAreaProvider>
<StatusBar … />
<AppContent />
</SafeAreaProvider>
);
}
它依赖 react-native-safe-area-context。官方文档专门提示:这个依赖在鸿蒙上会导致白屏,必须移除。替换成把安全区直接写死:
import { NewAppScreen } from ‘@react-native/new-app-screen’;
import { StatusBar, StyleSheet, useColorScheme, View } from ‘react-native’;
function App() {
const isDarkMode = useColorScheme() === ‘dark’;
return (
<View style={styles.container}>
<StatusBar barStyle={isDarkMode ? ‘light-content’ : ‘dark-content’} />
<NewAppScreen
templateFileName=”App.tsx”
safeAreaInsets={{ top: 0, bottom: 0, left: 0, right: 0 }}
/>
</View>
);
}
const styles = StyleSheet.create({ container: { flex: 1 } });
export default App;
这是一个很容易被忽略但一定会踩的点:环境全配对了、HAP 也装上了,结果打开就是白屏,最后发现是模板自带的 safe-area 依赖。
6. 生成鸿蒙 bundle
$ npm run dev
Welcome to Metro v0.83.8
[INFO] No harmony-specific third-party packages have been detected
info Created harmony/entry/src/main/resources/rawfile/bundle.harmony.js
info Copied 7 assets
产物落在鸿蒙工程的 rawfile 目录:
$ ls harmony/entry/src/main/resources/rawfile/
assets bundle.harmony.js
- bundle.harmony.js:打包后的 JS(本项目约 5.4 MB)
- assets/:bundle 里引用到的图片资源(这里是 RN 的 LogBox 图标和 new-app-screen 的插图)
这里有个顺序上的隐含依赖:bundle 要产出到 harmony/ 目录下,所以得先有鸿蒙工程。下一章先讲怎么把鸿蒙工程建出来,如果按本文顺序实操,把第 6、7 章调换一下即可。
7. 创建鸿蒙工程:别手搓,CLI 自带模板
官方文档在这一步是图形界面操作:打开 DevEco Studio → File > New > Create Project → 选 Empty Ability → 下一步 → 选 Compile SDK → 建一个叫 MyApplication 的工程。
问题来了:纯命令行怎么做?没有 IDE 的“新建工程”按钮。
7.1 一个意外的发现
本来打算手搓工程结构(AppScope/app.json5、module.json5、build-profile.json5、resources/… 一堆文件,写错一个就构建失败)。先去翻参考实现,结果在 react-native-harmony-cli 的源码里看到了这个:
$ find packages/react-native-harmony-cli/src -maxdepth 2 -type d
src/autolinking
src/codegen
src/commands <– 命令实现
src/init <– 工程模板!
src/init/templates
src/io
src/simulator
$ ls src/commands
bundle-harmony.ts clean-harmony.ts codegen-harmony.ts
codegen-lib-harmony.ts init-harmony.ts init.ts
link-harmony.ts run-harmony.ts verify-package-harmony.ts
CLI 自带一个 init-harmony 命令(源码:src/commands/init-harmony.ts[11]),而且 src/init/templates/harmony/[12] 下是一套完整的鸿蒙工程模板:
$ find src/init/templates/harmony -type f | head -20
harmony/AppScope/app.json5
harmony/AppScope/resources/base/element/string.json
harmony/AppScope/resources/base/media/app_icon.png
harmony/build-profile.json5
harmony/codelinter.json
harmony/entry/build-profile.json5
harmony/entry/hvigorfile.ts
harmony/entry/oh-package.json5
harmony/entry/src/main/cpp/CMakeLists.txt
harmony/entry/src/main/cpp/PackageProvider.cpp
harmony/entry/src/main/ets/entryability/EntryAbility.ets
harmony/entry/src/main/module.json5
harmony/hvigor/hvigor-config.json5
harmony/hvigorfile.ts
harmony/oh-package.json5
…
这比手搓靠谱得多——它是官方维护的、和当前 RNOH 版本配套的工程骨架。
命令的参数也很清楚:
$ npx react-native init-harmony –help
–bundle-name <string> # 必填,三段式 bundle name,如 com.org.project
–project-root-path [path] # 默认 .
–app-name [string] # 默认取 package.json 的 name
–rnoh-npm-package-name [string]
–rnoh-cli-npm-package-name [string]
bundle-name 这里值得停一下。它决定签名能不能过。本项目里之所以选 com.nutpi.myapplication,是因为机器上已经有一份 DevEco 生成过的 debug 签名材料,其 profile 绑定的正是这个 bundle name(详见第 8 章)。如果随便起个名字,签名就得重来。
7.2 第二个坑:命令直接崩了
$ npx react-native init-harmony –bundle-name com.nutpi.myapplication
error The “paths[0]” argument must be of type string. Received undefined.
TypeError [ERR_INVALID_ARG_TYPE]: The “paths[0]” argument must be of type string.
at Object.resolve (node:path:1257:7)
at new AbsolutePath (…/react-native-harmony-cli/dist/core/AbsolutePath.js:20:42)
at get path (…/react-native-harmony-cli/dist/io/RealFS.js:40:16)
at findRNOHHvigorPluginPath (…/dist/commands/init-harmony.js:203:23)
at …/dist/commands/init-harmony.js:102:42
AbsolutePath 收到了 undefined。顺着调用栈往回看,findRNOHHvigorPluginPath 在遍历目录项:
// dist/commands/init-harmony.js
function findRNOHHvigorPluginPath(fs, dirPath) {
for (let dirent of fs.readDirentsSync(dirPath)) {
if (dirent.isDirectory() || !dirent.name.startsWith(‘rnoh-hvigor-plugin’)) {
continue;
}
return dirent.path; // <– 这里
}
return null;
}
而 dirent.path 是由 RealFS 里的 getter 拼出来的:
// dist/io/RealFS.js
get path() {
return new core_1.AbsolutePath(this.nodeFSDirent.path)
.copyWithNewSegment(this.nodeFSDirent.name);
}
它读的是 Node.js fs.Dirent 的 .path 属性。
7.3 根因:Node 24+ 移除了Dirent.path
Node 的 `fs.Dirent`[13] 原本有 path(父目录路径),但它在 v20.12 起被废弃,改名为 `parentPath`[14],并在 v24.0.0 被彻底移除(DEP0178[15] | 中文版[16])。
实测本机两个 Node:
$ node -e “const d=require(‘fs’).readdirSync(‘.’,{withFileTypes:true})[0];
console.log(‘path:’, d.path, ‘| parentPath:’, d.parentPath)”
# node v26.12.0 (harmonybrew)
path: undefined | parentPath: .
$ /data/…/hmos-clt_1.0.0/node/bin/node -e “同上”
# node v24.18.1 (hmos-clt)
path: undefined | parentPath: .
两个版本都是 undefined。
这里就出现了一个尴尬的版本死锁:
- RN 0.84 要求 Node >= 22.11.0(package.json 的 engines)
- 而 CLI 需要 Dirent.path,它在 22.x 之后被移除了
- 本机只有 Node 24 和 26,没有 22
所以没有一个可用版本能同时满足两者,只能改 CLI。
7.4 三种解法与选择
| 方案 | 评价 |
|---|---|
| 降级到 Node 22 | 本机没有,且和 RN 0.84 的 engines 打架,还会牵连 harmonybrew 的全局环境 |
| 不用 CLI,手工复刻模板 | 要手改十几个文件 + 模板变量渲染(appName/bundleName/metro 配置都要替换),易错且后续 CLI 升级会脱节 |
| 给 CLI 打个兼容补丁 | 改 2 行,语义完全等价,风险最小 ✅ |
补丁就是把取值改成”优先 parentPath,回退 path”:
get path() {
– return new core_1.AbsolutePath(this.nodeFSDirent.path).copyWithNewSegment(this.nodeFSDirent.name);
+ // Node 22+ removed `dirent.path` (replaced by `parentPath`); fall back for older runtimes.
+ const parentPath = this.nodeFSDirent.parentPath ?? this.nodeFSDirent.path;
+ return new core_1.AbsolutePath(parentPath).copyWithNewSegment(this.nodeFSDirent.name);
}
改的文件是:
node_modules/@react-native-oh/react-native-harmony-cli/dist/io/RealFS.js
⚠️ 这个补丁在 node_modules 里,npm install 之后会丢,必须重新打。
工程化一点的建议是用 patch-package 把它固化下来;本次为了保持环境最小改动,只做了手工补丁,并在环境说明里记了一笔。
7.5 重跑,工程生成成功
$ npx react-native init-harmony –bundle-name com.nutpi.myapplication
warn Overwritten “./metro.config.js”
success Created “./harmony” directory
info Run on a physical device:
1. Run `npm start` to start the Metro server.
2. Open `./harmony` project in DevEco Studio and wait for background jobs to finish.
3. …
注意那行 warn Overwritten “./metro.config.js”——CLI 自己会写 metro 配置。所以第 5.3 节手工写 metro.config.js 其实是可选的,init-harmony 会生成一份等价的。反过来也要注意:如果你已经手工改过 metro.config.js,跑 init-harmony 会被覆盖。
生成的工程结构:
$ find harmony -type f -not -path “*/oh_modules/*” | sort
harmony/.gitignore
harmony/AppScope/app.json5
harmony/AppScope/resources/base/element/string.json
harmony/AppScope/resources/base/media/app_icon.png
harmony/build-profile.json5
harmony/build-profile.template.json5
harmony/codelinter.json
harmony/entry/.gitignore
harmony/entry/build-profile.json5
harmony/entry/hvigorfile.ts
harmony/entry/oh-package.json5
harmony/entry/src/main/cpp/CMakeLists.txt
harmony/entry/src/main/cpp/PackageProvider.cpp
harmony/entry/src/main/ets/PackageProvider.ets
harmony/entry/src/main/ets/codegen/.gitignore
harmony/entry/src/main/ets/entryability/EntryAbility.ets
harmony/entry/src/main/ets/pages/Index.ets
harmony/entry/src/main/ets/workers/RNOHWorker.ets
harmony/entry/src/main/module.json5
harmony/entry/src/main/resources/base/element/color.json
harmony/entry/src/main/resources/base/element/string.json
harmony/entry/src/main/resources/base/media/*.png
harmony/entry/src/main/resources/base/profile/main_pages.json
harmony/entry/src/main/resources/rawfile/ # bundle 落这里
harmony/hvigor/hvigor-config.json5
harmony/hvigorfile.ts
harmony/oh-package.json5
几个关键文件值得单独看一眼:
harmony/hvigor/hvigor-config.json5 —— 它把 RNOH 的 hvigor 插件作为依赖引进来:
{
“modelVersion”: “5.0.0”,
“dependencies”: {
“@rnoh/hvigor-plugin”: “../../node_modules/@react-native-oh/react-native-harmony-cli/harmony/rnoh-hvigor-plugin-0.84.4.tgz”
},
…
}
这就是前面 findRNOHHvigorPluginPath 要找的那个 rnoh-hvigor-plugin-*.tgz——它是 CLI 包自带的一个本地 hvigor 插件包,负责在构建时把 RNOH 接进来。
**harmony/hvigorfile.ts**:
import { appTasks } from ‘@ohos/hvigor-ohos-plugin’;
import { createRNOHProjectPlugin } from ‘@rnoh/hvigor-plugin’;
export default {
system: appTasks,
plugins: [createRNOHProjectPlugin()],
};
**harmony/entry/src/main/cpp/CMakeLists.txt**:C++ 侧的入口,编译出 librnoh_app.so。注意它引用了 oh_modules 里的 RNOH 源码:
set(RNOH_CPP_DIR “${CMAKE_CURRENT_SOURCE_DIR}/../../../../oh_modules/@rnoh/react-native-openharmony/src/main/cpp”)
…
add_subdirectory(“${RNOH_CPP_DIR}” ./rn)
include(“${CMAKE_CURRENT_SOURCE_DIR}/autolinking.cmake”)
add_library(rnoh_app SHARED
${GENERATED_CPP_FILES}
“./PackageProvider.cpp”
“${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp”
)
target_link_libraries(rnoh_app PUBLIC rnoh)
autolink_libraries(rnoh_app)
这就是为什么后面必须 ohpm install——C++ 编译需要 oh_modules 里的 RNOH 原生源码。
7.6 安装鸿蒙依赖
cd harmony
ohpm install –all
install completed in 68s 630ms。这一步会把 80 MB 的 react_native_openharmony.har 解开到 oh_modules(本项目最终 331 MB),C++ 编译要用。依赖来自 ohpm 官方仓库[17](本机 ~/.ohpm/.ohpmrc 里已配好)。
7.7 顺带修掉的一个配置冲突
init-harmony 生成的 build-profile.json5 里,SDK 版本是:
compatibleSdkVersion: ‘5.0.0(12)’,
targetSdkVersion: ‘6.0.0(20)’,
但本机 SDK 只有 API 26。这两个值是按老版本 SDK 写的,必须对齐成本机实际的:
compatibleSdkVersion: ‘26.0.0’,
targetSdkVersion: ‘26.0.0’,
另外当时还试过把 hvigor/hvigor-config.json5 的 modelVersion 从 5.0.0 改成 26.0.0(对齐机器上旧工程),结果直接报错:
> hvigor ERROR: 00303027 Configuration Error
Error Message: The modelVersion in hvigor-config.json5 is 26.0.0,
and the modelVersion in oh-package.json5 is 5.0.0
hvigor-config.json5 和 oh-package.json5 的 modelVersion 必须一致,单独改一个必挂。要么都改,要么都别改——这里选择**回退到模板自带的 5.0.0**,保持和官方模板一致,最少偏离。
官方说明:`hvigor-config.json5` 配置项[18] | `oh-package.json5`[19]
8. 签名:纯命令行下最大的那道坎
8.1 为什么绕不过去
鸿蒙设备不装未签名或签名不被信任的 HAP。而签名需要三样东西凑齐:
| 材料 | 作用 | 来源 |
|---|---|---|
| .p12 | 存着私钥的密钥库 | 本地生成 |
| .cer | 开发证书(公钥 + 华为 CA 签发链) | 华为签发 |
| .p7b | Profile
(描述“哪个应用、能装在哪些设备、debug 还是 release”) |
华为签发 |
关键在 .p7b:它必须由华为签发,并且把目标设备的 UDID 列进去(debug 包)。这不是本地能凭空造出来的——官方流程就是“在 DevEco Studio 里点 Sign In,登录华为账号,自动生成签名”(参考使用真机进行调试[20])。顺带一提,鸿蒙的签名工具链本身是开源的:OpenHarmony/developtools_hapsigner[21]——SDK 里的 hap-sign-tool 就出自它。
纯命令行环境下没有这个按钮。所以这一步要么想办法复用已有材料,要么去走华为的账号体系。
8.2 先看看机器上有什么
翻用户目录,发现 ~/.ohos/config/ 下躺着一整套签名材料:
$ ls -la ~/.ohos/config/
default_MyApplicationI2N-ov7ZD3FZxit__mTtQuOR7WC0uSY36PWyeShdmRI.p12 1003 B
default_MyApplicationI2N-ov7ZD3FZxit__mTtQuOR7WC0uSY36PWyeShdmRI.csr
default_MyApplicationI2N-ov7ZD3FZxit__mTtQuOR7WC0uSY36PWyeShdmRI.cer 2914 B
default_MyApplicationI2N-ov7ZD3FZxit__mTtQuOR7WC0uSY36PWyeShdmRI.p7b 8907 B
文件名里的 MyApplicationI2N-… 说明这是之前某次 DevEco 自动签名为 MyApplication 工程生成的。
先验证这份 profile 能不能用在自己身上。.p7b 是个签名过的信封,拆开看内容:
$ openssl smime -verify -in default_MyApplication*.p7b -inform DER -noverify \
| node -e “let s=”;process.stdin.on(‘data’,d=>s+=d).on(‘end’,()=>{
const j=JSON.parse(s.slice(s.indexOf(‘{‘)));
console.log(‘bundle-name :’, j[‘bundle-info’]?.[‘bundle-name’]);
console.log(‘type :’, j.type);
console.log(‘device-ids :’, (j[‘debug-info’]?.[‘device-ids’]||[]).length, ‘个’);
console.log(‘validity :’, new Date(j.validity[‘not-before’]*1000).toISOString().slice(0,10),
‘~’, new Date(j.validity[‘not-after’]*1000).toISOString().slice(0,10));
})”
输出:
bundle-name : com.nutpi.myapplication
type : debug
device-ids : 73 个
validity : 2026-10-15 ~ 2027-10-15
profile 还有效(有效期一年)。而且它绑定的 bundle name 是 com.nutpi.myapplication —— 这就是第 7.1 节里 init-harmony 必须用这个名字的原因。
那设备在不在那 73 个里面?查本机 UDID:
$ hdc shell bm get –udid
udid of current device is :
<本机设备的 UDID,此处已脱敏>
和 profile 里 device-ids[0] 完全一致 ✅ —— 因为目标设备就是本机自己。
source 材料齐了,但还有一个致命问题。
8.3 第三道坎:p12 的密码是加密的
签名要用私钥,私钥在 .p12 里,打开 .p12 要密码。而 DevEco 生成的这个密码是随机且加密存储的,明文不在任何配置文件里。
试着撞几个常见口令:
$ for pw in “” 123456 000000 123456789 huawei password ohos; do
printf “pass=[%s] -> ” “$pw”
openssl pkcs12 -in default_MyApplication*.p12 -noout -passin “pass:$pw” 2>&1 | head -1
done
pass=[] -> Mac verify error: invalid password?
pass=[123456] -> Mac verify error: invalid password?
pass=[000000] -> Mac verify error: invalid password?
pass=[123456789]-> Mac verify error: invalid password?
pass=[huawei] -> Mac verify error: invalid password?
pass=[password] -> Mac verify error: invalid password?
pass=[ohos] -> Mac verify error: invalid password?
全军覆没。密码被 DevEco 用机器级密钥加密后存着:
$ find ~/.ohos/config/material -type f
material/ac/a6f01a4dd39f3974d7e8a94728888c4b 16 bytes
material/ce/0ee5b1f24f314ba95f7abce8b904e8bb 48 bytes # 像 AES-GCM 的密文
material/fd/0/d08f07bc410c579b12c59bea21038ae7 16 bytes
material/fd/1/d27248cb3a773121855ae389aee0c615 16 bytes
material/fd/2/a27683fd88fe7c8936fbfdfbfb13f429 16 bytes
这些是 DevEco 的加密凭据库,解密要 DevEco 自己的密钥。此路不通。
8.4 转机:翻出之前那套可用的签名配置
按理说该放弃了。但有一个线索:机器上有 ~/.hvigor/project_caches/ 和一个 ~/deveco 目录,说明之前有人在这台机器上跑过 DevEco。
于是全盘搜带签名的工程配置:
$ find ~ -name “build-profile.json5” -not -path “*/node_modules/*”
/storage/Users/currentUser/Documents/DevEcoStudioProjects/MyApplication/build-profile.json5
…
找到了! 那个最初生成签名材料的工程本体还在。打开它的 build-profile.json5:
{
“app”: {
“signingConfigs”: [
{
“name”: “default”,
“material”: {
“storePassword”: “<DevEco 生成的加密串,此处已脱敏>”,
“certpath”: “/storage/Users/currentUser/.ohos/config/default_MyApplication…cer”,
“keyAlias”: “debugKey”,
“keyPassword”: “<DevEco 生成的加密串,此处已脱敏>”,
“profile”: “/storage/Users/currentUser/.ohos/config/default_MyApplication…p7b”,
“signAlg”: “SHA256withECDSA”,
“storeFile”: “/storage/Users/currentUser/.ohos/config/default_MyApplication…p12”
},
“type”: “HarmonyOS”
}
],
“products”: [
{
“name”: “default”,
“signingConfig”: “default”,
“targetSdkVersion”: “26.0.0”,
“compatibleSdkVersion”: “26.0.0”,
“runtimeOS”: “HarmonyOS”
}
]
}
}
这就是答案。 那串形如 0000xxxxxxxx… 的密文是加密后的密码——我们不需要解开它,因为机器上的 hvigor 本来就会解它(凭据库就在 ~/.ohos/config/material/,是机器级的)。把这段配置原样搬进我们自己的工程,hvigor 就能自己完成解密和签名。
这个工程还顺便给了两个宝贵信息:
- 它的 compatibleSdkVersion / targetSdkVersion 都是 26.0.0 —— 印证了第 7.7 节的判断,本机就该用 26。
- 它的 entry/build/default/ 目录存在 —— 说明它是成功构建过的,那套配置在这台机器上验证可用。
8.5 最终写入我们工程的签名配置
harmony/build-profile.json5:
{
app: {
signingConfigs: [
{
name: ‘default’,
material: {
storePassword: ‘<DevEco 生成的加密串,原样搬>’,
certpath: ‘/storage/Users/currentUser/.ohos/config/default_MyApplication….cer’,
keyAlias: ‘debugKey’,
keyPassword: ‘<同上>’,
profile: ‘/storage/Users/currentUser/.ohos/config/default_MyApplication….p7b’,
signAlg: ‘SHA256withECDSA’,
storeFile: ‘/storage/Users/currentUser/.ohos/config/default_MyApplication….p12’,
},
type: ‘HarmonyOS’,
},
],
products: [
{
name: ‘default’,
signingConfig: ‘default’, // 引用上面的签名配置
compatibleSdkVersion: ‘26.0.0’,
targetSdkVersion: ‘26.0.0’,
runtimeOS: ‘HarmonyOS’,
buildOption: { nativeCompiler: ‘BiSheng’ },
},
],
buildModeSet: [{ name: ‘debug’ }, { name: ‘release’ }],
},
modules: [
{ name: ‘entry’, srcPath: ‘./entry’,
targets: [{ name: ‘default’, applyToProducts: [‘default’] }] },
],
}
提醒:安装同 bundle name 的 HAP 会覆盖设备上已安装的同名应用。这次就把那个旧的 DevEco MyApplication 覆盖掉了。如果不想覆盖,得换 bundle name,但同时也就得重新走华为账号签名。
9. 构建 HAP,以及这条路上最耗时的两个坑
9.1 命令
cd AwesomeProject/harmony
export DEVECO_SDK_HOME=”$HMOS_CLT_HOME/sdk”
export RNOH_C_API_ARCH=1
hvigorw assembleHap –no-daemon
–no-daemon 建议加上:hvigor 默认会起常驻 daemon,在非交互、需要反复排查的场景下,不用 daemon 反而更容易看清每次构建的完整过程。
产物:
entry/build/default/outputs/default/
├── entry-default-signed.hap # 44 MB,签名版
├── entry-default-unsigned.hap
├── mapping
└── pack.info
9.2 第三个坑:Interrupted system call(EINTR)
第一次构建,跑到 C++ 编译阶段,扑了一屏这种错误:
In file included from …/libcxx-ohos/include/c++/v1/string:536:
/…/include/c++/v1/string:536:10: fatal error:
cannot open file ‘…/third-party/boost/libs/type_traits/include/__string/char_traits.h’:
Interrupted system call
#include <__string/char_traits.h>
FAILED: rn/third-party/rn/react/renderer/componentregistry/…/ComponentDescriptorRegistry.cpp.o
ninja: build stopped: subcommand failed.
注意两点:
- 报错是 Interrupted system call(EINTR),不是”文件不存在”,也不是用户代码的编译错误。
- 报错路径看着很怪 —— 它在 boost 的 include 目录里找一个 __string/char_traits.h。这其实是 C++ 头文件搜索的正常现象:编译器按 -I 顺序逐个试,真正要打开的是 SDK 里的 libc++ 头文件,试到中途某个候选路径时被打断了。
先确认文件本身没问题:
$ S=…/BiSheng/bin/../include/libcxx-ohos/include/c++/v1
$ for f in __string/char_traits.h __variant/monostate.h __compare/common_comparison_category.h; do
[ -r “$S/$f” ] && echo “$f 可读” || echo “$f 缺失”
done
__string/char_traits.h 可读
__variant/monostate.h 可读
__compare/common_comparison_category.h 可读
# 连续读 300 次,看是否稳定
$ fail=0; for i in $(seq 1 300); do cat “$S/__string/char_traits.h” >/dev/null 2>&1 || fail=$((fail+1)); done
$ echo “失败次数: $fail / 300”
失败次数: 0 / 300
文件完全正常,单线程读 300 次零失败。
再看资源水位:
$ free -m
total used free
Mem: 31914 29203 2710 # 只剩 2.7 GB
SwapTotal: 52428800 # 但有 50 GB swap
SwapFree: 45937940
$ cat /proc/loadavg
23.183 22.933 28.318 # 20 核机器,负载 23
$ grep -c ^processor /proc/cpuinfo
20
结论:20 核并行编译、内存接近打满、负载 23。在这种高压并发下,文件系统偶发返回 EINTR —— 这和第 4.1 节 Homebrew 装 git 失败、以及后面 git checkout 报 Interrupted system call 是同一个病因。
这就解释了为什么它表现得像“随机故障”:同一份源码,重跑一次可能就过了。
9.3 应对:重试 + 增量推进
关键在于 **ninja 会保留已编译的 .o**。所以重试不是从零开始,而是接着上次的进度往下推:
for i in 1 2 3 4 5 6; do
echo “########## 尝试 $i ##########” >> build.log
hvigorw assembleHap –no-daemon >> build.log 2>&1
if ls entry/build/default/outputs/default/*.hap >/dev/null 2>&1; then
echo “BUILD_OK after attempt $i” >> build.log; break
fi
echo “attempt $i failed” >> build.log
done
实测每次重试都在推进(.o 文件数 239 → 283 → 376 → 522……),最终一次干净的重建会跑通。
完整的 C++ 全量编译(RN + RNOH + folly + hermes + boost + glog + fmt)大约需要 25~30 分钟,官方文档也专门提示了“全量编译 C++ 代码耗时较长,请耐心等待”。这一步没有任何捷径,第一次构建就是要等。
一个可选优化:如果机器本身内存充裕,可以在 hvigor/hvigor-config.json5 的 nodeOptions.maxOldSpaceSize 上调;但编译期的内存压力主要来自 clang 而不是 hvigor 的 Node 进程,调这个收益有限。
9.4 第四个坑:构建期间改源码,会让 ninja “记忆错乱”
这是最隐蔽的一个。
曾经有一次,构建跑到一半,我顺手改了 harmony/entry/src/main/ets/pages/Index.ets。结果那次构建最后失败在链接阶段,报的是一长串:
FAILED: …/libhermestooling.so
clang++: error: no such file or directory: ‘rn/RNOH/JSEngine/hermes/…/HermesInstance.cpp.o’
clang++: error: no such file or directory: ‘rn/third-party/libfolly.a’
clang++: error: no such file or directory: ‘rn/third-party/glog/libglog.a’
clang++: error: no such file or directory: ‘rn/third-party/boost/libs/context/libboost_context.a’
…
ninja: build stopped: subcommand failed.
去看构建目录,发现了问题:
$ find entry/.cxx -name “*.o” | wc -l
30 # 之前明明有 522 个
$ find entry/.cxx -name “libfolly.a”
(空)
一大堆 .o 和静态库凭空消失了,但 ninja 的构建日志还认为它们是“最新的”,于是跳过重新编译、直接进入链接 → 链接器找不到文件。
这类症状的经典成因是:ninja 的 .ninja_log 与实际文件系统脱节。而在这里,触发它的很大可能是“构建期间源文件被修改”——hvigor 检测到输入变化后重跑了任务链,而 native 构建目录被重置,两边状态没对齐。
实践建议很朴素:构建期间不要动源码。 改完代码,重新构建。
9.5 成功:签名版 HAP 与签名校验
干净重跑后,产物出来了,而且可以独立验证签名有效性:
$ …/toolchains/lib/hap-sign-tool verify-app \
-inFile entry/build/default/outputs/default/entry-default-signed.hap \
-outCertChain /tmp/out.cer -outProfile /tmp/out.p7b
10-18 13:42:04.287 INFO – hap verify successed!
10-18 13:42:04.287 INFO – verify-app success
顺便确认 HAP 里到底装了什么:
$ unzip -l entry-default-signed.hap | grep -E “rawfile|\.so$”
resources/rawfile/bundle.harmony.js 5456027
resources/rawfile/rnoh.profdata 4834640
libs/arm64-v8a/libreactnative.so 13433656
libs/arm64-v8a/librnoh_app.so 3182128
libs/arm64-v8a/librnoh_core.so 5115120
libs/arm64-v8a/librnoh_core_package.so 2982520
libs/arm64-v8a/libhermesvm.so 3414896
libs/arm64-v8a/libjsi.so 874168
…
- librnoh_app.so 就是我们自己的 CMakeLists.txt 编出来的适配层
- librnoh_core.so / librnoh_core_package.so 来自 RNOH 的 har
- bundle.harmony.js 就是第 6 章生成的业务 JS
——三段拼起来,一个完整的 RN 鸿蒙应用。
10. 安装到本机并运行
10.1 安装
目标设备就是本机(HAD-W32,API 26),通过 hdc 直接装:
$ hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
[Info]App install path:/storage/…/entry-default-signed.hap
msg:install bundle successfully.
AppMod finish
install bundle successfully —— 说明前面第 8 章那套“复用签名材料”的思路是对的,加密的密码不需要解开,hvigor 自己会解。
顺带确认一下同名应用的情况(避免覆盖掉不想覆盖的东西):
$ hdc shell bm dump -a | grep nutpi
com.nutpi.hongjie
com.nutpi.myapplication # 旧的那个 DevEco 工程,会被覆盖
10.2 启动
$ hdc shell aa start -a EntryAbility -b com.nutpi.myapplication
start ability successfully.
$ hdc shell “ps -ef | grep nutpi”
20020174 33244 670 0 08:55:34 ? 00:00:01 com.nutpi.myapplication # 进程活着
进程起来了,start ability successfully。看起来一切顺利。
10.3 第五个坑:装上了、启动了、但是白屏
进程活着,界面却不出来。这种“进程在、无界面”的情况,在 RN 里八成是 bundle 没加载成功。
回头看一件之前在打包时的观察:
$ unzip -l entry-default-signed.hap | grep -iE “rawfile/(bundle|hermes)”
resources/rawfile/bundle.harmony.js 5456027
# 没有 hermes_bundle.hbc
HAP 里装的是 **bundle.harmony.js**。
再看 init-harmony 生成的 Index.ets 是怎么加载 bundle 的:
jsBundleProvider: this.rnohCoreContext?.isDebugModeEnabled
? new AnyJSBundleProvider([
new MetroJSBundleProvider(), // 开发者机器上的 Metro 服务
new ResourceJSBundleProvider(…, ‘hermes_bundle.hbc’), // 只找 .hbc
])
: new ResourceJSBundleProvider(…, ‘hermes_bundle.hbc’), // 生产也只找 .hbc
它只找 hermes_bundle.hbc,而 HAP 里根本没有这个文件。
具体来说,debug 模式下它会:
- 先试 MetroJSBundleProvider → 没起 Metro 服务,失败
- 再试 hermes_bundle.hbc → 资源不存在,失败
- 两个都失败 → 白屏
而官方文档里那段 Index.ets 示例其实是带回落的,它列了四个 provider:
new AnyJSBundleProvider([
new MetroJSBundleProvider(),
new FileJSBundleProvider(‘/data/storage/el2/base/files/bundle.harmony.js’),
new ResourceJSBundleProvider(rm, ‘hermes_bundle.hbc’),
new ResourceJSBundleProvider(rm, ‘bundle.harmony.js’), // <– 这行是关键
])
init-harmony 生成的版本少了最后那条 bundle.harmony.js 回落。
10.4 修复
把回落补上(顺便用 AnyJSBundleProvider 统一 debug / release 两条路径,逻辑更简单):
import {
AnyJSBundleProvider,
MetroJSBundleProvider,
RNApp,
RNOHErrorDialog,
ResourceJSBundleProvider,
RNOHCoreContext
} from ‘@rnoh/react-native-openharmony’;
import { getRNOHPackages } from ‘../PackageProvider’;
@Entry
@Component
struct Index {
@StorageLink(‘RNOHCoreContext’) private rnohCoreContext: RNOHCoreContext | undefined = undefined
build() {
Column() {
if (this.rnohCoreContext) {
if (this.rnohCoreContext?.isDebugModeEnabled) {
RNOHErrorDialog({ ctx: this.rnohCoreContext })
}
RNApp({
rnInstanceConfig: {
name: “AwesomeProject”,
createRNPackages: getRNOHPackages,
fontResourceByFontFamily: {},
enableDebugger: this.rnohCoreContext?.isDebugModeEnabled,
},
appKey: “AwesomeProject”, // 必须与 AppRegistry.registerComponent 的 appName 一致
/**
* 按顺序尝试:
* 1. Metro 服务(npm start,支持热加载)
* 2. hermes_bundle.hbc(Hermes 字节码,生产推荐)
* 3. bundle.harmony.js(npm run dev 产出的本地 bundle)
*/
jsBundleProvider: new AnyJSBundleProvider([
new MetroJSBundleProvider(),
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, ‘hermes_bundle.hbc’),
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, ‘bundle.harmony.js’),
]),
})
}
}
.height(‘100%’)
.width(‘100%’)
}
}
改完重新构建(此时 native 已经编译好,主要是 ArkTS 重新编译 + 打包签名,很快)。
别忘了第 9.4 节的教训:改完源码再构建,不要在构建过程中改。
10.5 验证:不看日志,直接截屏
hilog 里不一定有有用的输出,最直接的验证就是把设备屏幕截下来看:
$ hdc shell snapshot_display -f /data/local/tmp/rnoh.jpeg
snapshot: convert rgba8888 to rgb888 successfully.
success: snapshot display 0 , write to /data/local/tmp/rnoh.jpeg as jpeg,
width: 3120, height: 2080
$ hdc file recv /data/local/tmp/rnoh.jpeg ./screen.jpeg
FileTransfer finish, Size:362605, File count = 1, time:15ms rate:24173.67kB/s
打开截图:
╭──────────────────────────╮
│ ⚛ React │
│ │
│ Welcome to React Native │
│ │
│ Version: 0.84.1 │
│ JS Engine: Hermes │
│ │
│ ┌────────────────────┐ │
│ │ Open App.tsx to │ │
│ │ get started │ │
│ └────────────────────┘ │
│ │
│ Learn & Explore │
│ ┌────────┐ ┌────────┐ │
│ │Hello │ │Fast │ │
│ │World │ │Refresh │ │
│ └────────┘ └────────┘ │
╰──────────────────────────╯
“Welcome to React Native / Version: 0.84.1 / JS Engine: Hermes” —— 一个真正的 React Native 应用,跑在鸿蒙 PC 上。
界面正常渲染、不是白屏,整条链路彻底打通:
JS/TS 源码 ──bundle-harmony──▶ bundle.harmony.js
│
▼
CMake + ninja ──▶ librnoh_app.so ──┐
│ │
▼ ▼
HAP ──hap-sign-tool──▶ 签名 HAP ──hdc──▶ 鸿蒙设备
11. 一次跑通之后:日常开发命令
环境搭好之后,开发循环其实很轻量。
11.1 改 JS 代码(最常用)
cd AwesomeProject
npm run dev # 重新生成 bundle
cd harmony
hvigorw assembleHap –no-daemon # ArkTS + 打包,秒级
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.nutpi.myapplication
因为 C++ 已经编译过,只有第一次是全量编译(25~30 分钟),之后只要不动 cpp/ 和 CMakeLists.txt,都是增量,很快。
11.2 改 C++ 代码
改了 entry/src/main/cpp/ 下的东西,hvigorw assembleHap 会自动触发 native 增量编译。如果改动比较大导致状态异常,清掉重来:
rm -rf entry/.cxx entry/build
hvigorw assembleHap –no-daemon # 又要等全量编译
11.3 Metro 热加载(体验最好)
Index.ets 里 MetroJSBundleProvider 排在第一位,所以只要起着 Metro:
cd AwesomeProject
npm start # 监听 8081
应用启动时会优先从 Metro 拉 bundle,改 JS 后在设备上摇一摇 / 触发 reload 即可生效,不用重新打包安装。这是日常调 UI 最快的方式。
11.4 看日志
hdc shell hilog -x | grep -iE “RNOH|AwesomeProject”
11.5 备份产物 / 截图
hdc shell snapshot_display -f /data/local/tmp/shot.jpeg
hdc file recv /data/local/tmp/shot.jpeg ./shot.jpeg
12. 踩坑全记录
把这次踩到的所有坑汇总成一张表,按“遇到顺序”排列。如果你正准备在鸿蒙 PC 上搭 RNOH 环境,建议先扫一遍这张表。
| # | 现象 | 根因 | 解法 |
|---|---|---|---|
| 1 | brew install git
反复失败,报 Interrupted system call @ rb_file_s_stat |
本机文件系统偶发 EINTR,且失败点在 Homebrew 自己的 Ruby 源码上,与 git 包无关 | 放弃 brew,直接用 Homebrew 内置的 portable-git(2.55.0)软链到 ~/.local/bin |
| 2 | init-harmony
直接崩:The “paths[0]” argument must be of type string. Received undefined |
CLI 用了 **fs.Dirent.path**,该属性在 Node 22+ 已被移除(改名 parentPath);RN 0.84 又要求 Node ≥ 22.11,版本死锁 | 给 react-native-harmony-cli/dist/io/RealFS.js 打补丁,改用 parentPath ?? path。每次 npm install 后需重打 |
| 3 | hvigor ERROR: 00303027 Configuration Error: modelVersion … 26.0.0 / 5.0.0 | hvigor-config.json5
与 oh-package.json5 的 modelVersion 被改成不一致 |
两个文件保持一致(本项目统一用模板自带的 5.0.0) |
| 4 | 模板的 compatibleSdkVersion: ‘5.0.0(12)’ 与本机只有 API 26 不匹配 | init-harmony
模板按老 SDK 写的 |
改成 compatibleSdkVersion: ‘26.0.0’ / targetSdkVersion: ‘26.0.0’ |
| 5 | C++ 编译报 fatal error: cannot open file ‘…’: Interrupted system call | 20 核并行编译 + 内存接近打满(31.9 GB 只剩 2.7 GB,负载 23)下,文件系统偶发 EINTR。文件本身没问题(单线程连读 300 次零失败) | 重跑 hvigorw assembleHap;ninja 保留 .o,每次重试都是增量推进 |
| 6 | 链接阶段报一堆 clang++: error: no such file or directory: ‘rn/third-party/libfolly.a’ 等 | 构建期间修改了源码
,hvigor 重跑任务链、native 构建目录被重置,而 ninja 的 .ninja_log 仍认为 .o 是最新的 → 直接跳到链接(.o 从 522 个掉到 30 个) |
构建期间不要动源码;必要时 rm -rf entry/.cxx entry/build 后干净重建 |
| 7 | 装了、启动了、但是白屏 | init-harmony
生成的 Index.ets 只找 **hermes_bundle.hbc**,而 npm run dev 产出的是 **bundle.harmony.js**,HAP 里没有 .hbc |
在 AnyJSBundleProvider 里补上 ResourceJSBundleProvider(…, ‘bundle.harmony.js’) 回落(官方文档示例本来就有这一条) |
| 8 | (预防性)鸿蒙上白屏 | RN 0.84 模板的 App.tsx 依赖 react-native-safe-area-context,官方文档明确说它在鸿蒙上会导致白屏 | 移除 SafeAreaProvider,改成显式传 safeAreaInsets={{top:0,bottom:0,left:0,right:0}} |
三个”反直觉”的点,单独强调:
- hvigor 不需要 JDK。 官方文档依赖 DevEco Studio,而 DevEco 捆绑了 JBR,容易让人误以为鸿蒙构建必须装 Java。实际 SDK 的 hap-sign-tool / ohos_packing_tool 都是原生 ELF,不碰 Java。
- 签名不需要知道 p12 密码。 DevEco 把密码加密存在机器级凭据库(~/.ohos/config/material/)里,配置文件里只有加密串。照抄那个加密串即可——解密是 hvigor 在构建时做的,不需要人工参与。
- Interrupted system call 不是你的代码有问题。 在这台机器上它出现在 Homebrew、git、clang 三个完全不同的程序里。看到它就一条策略:重试。
13. 从零到跑通:完整命令清单
按实际执行顺序整理,可直接照抄(路径按需替换)。
阶段一:工具链与环境
# 1) 接入 git(brew 装不上时的兜底方案)
P=~/.harmonybrew/Homebrew/Library/Homebrew/vendor/portable-git/current
mkdir -p ~/.local/bin
ln -sf $P/bin/git ~/.local/bin/git
ln -sf $P/bin/scalar ~/.local/bin/scalar
git –version # 2.55.0
# 2) ~/.zshrc 追加(见 4.4 节)
export HMOS_CLT_HOME=”/data/service/hnp/hmos-clt.org/hmos-clt_1.0.0″
export DEVECO_SDK_HOME=”$HMOS_CLT_HOME/sdk”
export PATH=”$HMOS_CLT_HOME/ohpm/bin:$HMOS_CLT_HOME/hvigor/bin:$HMOS_CLT_HOME/sdk/default/openharmony/toolchains:$PATH”
export RNOH_C_API_ARCH=1
export HDC_SERVER_PORT=7035
# 3) ~/.npmrc 追加
# registry=https://repo.huaweicloud.com/repository/npm/
# strict-ssl=false
npm cache clean –force
source ~/.zshrc
阶段二:React Native 工程
cd <你的工作目录>
# 4) 初始化 RN 工程(版本必须与 RNOH 对齐)
npx –yes @react-native-community/cli@latest init AwesomeProject \
–version 0.84.1 –pm npm
cd AwesomeProject
# 5) 安装 RNOH 鸿蒙化依赖
npm install @react-native-oh/react-native-harmony@0.84.4 \
@react-native-oh/react-native-harmony-cli@0.84.4
# 6) 打 CLI 兼容补丁(Node 22+ 必须;见 7.4 节)
# 编辑 node_modules/@react-native-oh/react-native-harmony-cli/dist/io/RealFS.js
# 把 this.nodeFSDirent.path 改为 (this.nodeFSDirent.parentPath ?? this.nodeFSDirent.path)
# 7) package.json 加脚本
# “dev”: “react-native bundle-harmony –dev”
# 8) 替换 App.tsx,移除 safe-area 依赖(见 5.5 节)
# 9) 生成鸿蒙工程(bundle-name 要和你可用的签名 profile 一致)
npx react-native init-harmony –bundle-name com.nutpi.myapplication
阶段三:鸿蒙工程配置与构建
cd harmony
# 10) 改 build-profile.json5:SDK 版本 + signingConfigs(见 8.5 节)
# 11) 安装鸿蒙依赖(C++ 编译需要 oh_modules 里的 RNOH 源码)
ohpm install –all
# 12) 修 Index.ets,补 bundle.harmony.js 回落(见 10.4 节)
cd .. && npm run dev && cd harmony # 生成 bundle 到 entry/src/main/resources/rawfile/
# 13) 构建(首次全量 C++ 编译约 25~30 分钟)
hvigorw assembleHap –no-daemon
# 14) 验证签名
$HMOS_CLT_HOME/sdk/default/openharmony/toolchains/lib/hap-sign-tool verify-app \
-inFile entry/build/default/outputs/default/entry-default-signed.hap \
-outCertChain /tmp/out.cer -outProfile /tmp/out.p7b
阶段四:安装与运行
# 15) 确认设备在线
hdc list targets
# 16) 安装 + 启动
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.nutpi.myapplication
# 17) 截屏验证(最直接的确认手段)
hdc shell snapshot_display -f /data/local/tmp/shot.jpeg
hdc file recv /data/local/tmp/shot.jpeg ./shot.jpeg
14. 环境速查
工具链版本
| 组件 | 版本 | 路径 |
|---|---|---|
| React Native | 0.84.1 | — |
| RNOH / RNOH CLI | 0.84.4 | node_modules/@react-native-oh/ |
| Node / npm | v26.12.0 / 11.20.0 | harmonybrew |
| ohpm | 6.0.1 | $HMOS_CLT_HOME/ohpm/bin |
| hvigorw | 7.26.8 | $HMOS_CLT_HOME/hvigor/bin |
| hdc | 3.2.0f | $HMOS_CLT_HOME/sdk/default/openharmony/toolchains |
| OpenHarmony SDK | API 26 (26.0.0.105) | $HMOS_CLT_HOME/sdk |
| cmake / ninja | 3.28.2 / 1.13.2 | SDK 内置,hvigor 自动调用 |
| git | 2.55.0 | ~/.local/bin/git
(portable-git) |
关键路径
AwesomeProject/ # RN 工程
├── App.tsx # 已移除 safe-area 依赖
├── metro.config.js # 已挂 createHarmonyMetroConfig
├── package.json # 含 “dev”: “react-native bundle-harmony –dev”
└── harmony/ # 鸿蒙工程(init-harmony 生成)
├── build-profile.json5 # 签名配置 + API 26
├── hvigor/hvigor-config.json5 # 引入 @rnoh/hvigor-plugin
├── entry/src/main/cpp/CMakeLists.txt # 编译 librnoh_app.so
├── entry/src/main/ets/pages/Index.ets # 已补 bundle.harmony.js 回落
└── entry/src/main/resources/rawfile/ # bundle.harmony.js 落在这里
15. 结语
回到最初的问题:能不能不装 DevEco Studio、纯命令行在鸿蒙 PC 上把 RNOH 跑起来?
能。而且整条链路里真正“脏”的地方只有三处,都很容易处理:
- 一个 2 行的 CLI 补丁(Dirent.path → parentPath)—— 这是 RNOH CLI 与新版 Node 的兼容性缺口,跟鸿蒙本身无关;
- 一次签名配置搬运 —— 纯命令行下没法走”登录华为账号自动签名”,但如果机器上已有可用材料,照抄加密串就行;
- 一次 Index.ets 回落补齐 —— 官方文档示例里有,init-harmony 模板里漏了。
剩下的全是“知道就好”的工程常识:全量 C++ 编译要等半小时、内存不够就用增量重试、构建期间别改代码。
几个让我印象比较深的点:
- hmos-clt 这个 HNP 包被严重低估了。 很多人以为鸿蒙开发必须有 DevEco Studio,其实 ohpm / hvigorw / 完整 SDK / hdc / cmake / ninja 全都在里面,而且完全可以在终端里驱动。
- init-harmony 是被文档忽略的命令。 官方环境搭建文档全篇是 IDE 操作,但你不需要真的去点”新建工程”——CLI 里就有官方模板。
- 别和环境对抗,要找现成的。 Homebrew 装不上 git,答案不是去修 Homebrew,而是它自己就带了一份 portable-git;p12 密码解不开,答案不是去爆破,而是机器上本来就有能解开它的 hvigor。
- Interrupted system call 会连着骗你三次。 它出现在三个毫不相干的程序里,每次都会让人怀疑”是不是我哪里配错了”。认识到它是环境特性而不是配置错误,能省掉大量无用的排查。
最后,一个顺手发现:跑通之后截屏时,发现这台机器上其实开着 DevEco Studio,而且已经打开了我们生成的 harmony 工程。所以如果你更习惯图形界面,hvigorw assembleHap 换成 IDE 里点一下 Run 也是完全等价的——两条路通向同一个 HAP。
命令行这条路的真正价值不在“取代 IDE”,而在于:它让整条构建链路变得可脚本化、可 CI、可复现。CI 机器上不会有人给你分配一个 DevEco Studio 的窗口。
本文基于 2026-10 的一次真实搭建过程记录,版本配套为 RN 0.84.1 + RNOH 0.84.4 + OpenHarmony SDK API 26,运行设备为 HUAWEI MateBook Pro(HAD-W32,HarmonyOS 7.0)。RNOH 迭代较快,版本号、tag 与 CLI 行为可能已变化,请以你所用版本的官方文档为准。
附录:参考链接
以下是全文涉及的全部外部资源。本文实作在 0.84-stable 分支上,版本相关的内容请优先看该分支(main 分支已到 0.86,与本文版本不一致)。
RNOH 官方资源
- 官方仓库(AtomGit)————CPF-RN/ohos_react_native[22]
- 0.84 版本分支————0.84-stable[23]
- 《环境搭建》(本文依据)————docs/zh-cn/02-开发/01-环境与工具/环境搭建.md[24]
- 《环境搭建》main 分支版(0.86,仅作对照)————环境搭建.md[25]
- 《版本说明》(dist-tag 规则与安装方式)————docs/zh-cn/05-运维/版本说明.md[26]
- 版本升级指导(本地包 / 远程依赖互转)————docs/zh-cn/05-运维/版本升级指导.md[27]
- 调试调测(Metro 热加载)————docs/zh-cn/03-调测/调试调测.md[28]
- init-harmony 命令源码————src/commands/init-harmony.ts[29]
- 鸿蒙工程模板源码————packages/react-native-harmony-cli/src/init/templates/harmony[30]
- 整包替换模板:AwesomeProject————docs/templates/AwesomeProjectReplace[31]
- 整包替换模板:MyApplication————docs/templates/MyApplicationReplace[32]
npm / ohpm 包
- `@react-native-oh/react-native-harmony`[33]————前端包(含 metro.config、三个 .har、RNAbility 等)
- `@react-native-oh/react-native-harmony-cli`[34]————提供 init-harmony / bundle-harmony / codegen-harmony 等命令
- `@react-native-community/cli`[35]————RN 工程初始化
- `react-native`[36]————版本列表页
- `patch-package`[37]————可选,用于把本文的 CLI 补丁固化下来,避免 npm install 后丢失
- ohpm 官方仓库————https://ohpm.openharmony.cn/ohpm/
React Native 官方
- RN v0.84.1 Release Notes————react-native/releases/tag/v0.84.1[38]
- RN 官网————reactnative.dev[39]
- RN 中文网「环境搭建」————set-up-your-environment[40]
- Metro 配置文档————reactnative.net.cn/docs/metro[41]
Node.js 官方(dirent.path相关)
- DEP0178:dirent.path 已移除————nodejs.org/api/deprecations.html#dep0178-direntpath[42]
- DEP0178 中文版————nodejs.cn/api/deprecations/dep0178_dirent_path.html[43]
- fs.Dirent.parentPath————nodejs.org/api/fs.html#direntparentpath[44]
- Node 官方迁移 codemod(Recipe 源码)————nodejs/userland-migrations · dirent-path-to-parent-path[45]
- Node.js 下载————nodejs.org/en/download[46]
华为开发者文档
- DevEco Studio 下载————developer.huawei.com/consumer/cn/download/deveco-studio[47]
- 配置开发环境(IDE 工具总览)————ide-tools-overview[48]
- 使用真机进行调试————ide-run-device[49]
- 使用模拟器进行调试————ide-previewer-debug[50]
- hvigor 配置项(hvigor-config.json5,含 modelVersion 说明)————ide-hvigor-set-options[51]
- oh-package.json5 说明————ide-oh-package-json5[52]
- 模块级 build-profile.json5(含 abiFilters)————ide-hvigor-build-profile[53]
- 应用沙箱目录————app-sandbox-directory[54]
- 应用沙箱目录(V5 版)————app-sandbox-directory-V5[55]
- 应用安装资源访问————resource-categories-and-access-V5[56]
OpenHarmony 社区
- OpenHarmony 官网————openharmony.cn[57]
- OpenHarmony 代码仓库(AtomGit)————atomgit.com/openharmony[58]
- 签名工具 hapsigner(hap-sign-tool 的来源)————OpenHarmony/developtools_hapsigner[59]
工具与镜像源
- harmonybrew(鸿蒙版 Homebrew)————atomgit.com/Harmonybrew/brew[60]
- Homebrew 官网————brew.sh[61]
- 华为云 npm 镜像————https://repo.huaweicloud.com/repository/npm/
关于链接可达性:以上链接在撰写时均已核对存在。其中华为开发者文档站(developer.huawei.com)是 SPA 单页应用,用命令行工具(curl/wget)抓取会拿不到正文甚至被拒(403/405),用浏览器访问即可;www.npmjs.com 同理,对自动化抓取有反爬限制。这不是链接失效。
参考资料:
[1]CPF-RN/ohos_react_native: https://atomgit.com/CPF-RN/ohos_react_native
[2]《环境搭建》: https://atomgit.com/CPF-RN/ohos_react_native/blob/0.84-stable/docs/zh-cn/02-开发/01-环境与工具/环境搭建.md
[3]DevEco Studio: https://developer.huawei.com/consumer/cn/download/deveco-studio
[4]@react-native-oh/react-native-harmony: https://www.npmjs.com/package/@react-native-oh/react-native-harmony
[5]华为云 npm 镜像: https://repo.huaweicloud.com/repository/npm/
[6]harmonybrew: https://atomgit.com/Harmonybrew/brew
[7]Homebrew: https://brew.sh
[8]@react-native-community/cli: https://www.npmjs.com/package/@react-native-community/cli
[9]Release Notes: https://github.com/facebook/react-native/releases/tag/v0.84.1
[10]Metro: https://reactnative.net.cn/docs/metro
[11]src/commands/init-harmony.ts: https://atomgit.com/CPF-RN/ohos_react_native/blob/0.84-stable/packages/react-native-harmony-cli/src/commands/init-harmony.ts
[12]src/init/templates/harmony/: https://atomgit.com/CPF-RN/ohos_react_native/tree/0.84-stable/packages/react-native-harmony-cli/src/init/templates/harmony
[13]fs.Dirent: https://nodejs.org/api/fs.html
[14]parentPath: https://nodejs.org/api/fs.html#direntparentpath
[15]DEP0178: https://nodejs.org/api/deprecations.html#dep0178-direntpath
[16]中文版: https://nodejs.cn/api/deprecations/dep0178_dirent_path.html
[17]ohpm 官方仓库: https://ohpm.openharmony.cn/ohpm/
[18]hvigor-config.json5 配置项: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-hvigor-set-options
[19]oh-package.json5: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-oh-package-json5
[20]使用真机进行调试: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-run-device
[21]OpenHarmony/developtools_hapsigner: https://atomgit.com/openharmony/developtools_hapsigner
[22]CPF-RN/ohos_react_native: https://atomgit.com/CPF-RN/ohos_react_native
[23]0.84-stable: https://atomgit.com/CPF-RN/ohos_react_native/tree/0.84-stable
[24]docs/zh-cn/02-开发/01-环境与工具/环境搭建.md: https://atomgit.com/CPF-RN/ohos_react_native/blob/0.84-stable/docs/zh-cn/02-开发/01-环境与工具/环境搭建.md
[25]环境搭建.md: https://atomgit.com/CPF-RN/ohos_react_native/blob/main/docs/zh-cn/02-开发/01-环境与工具/环境搭建.md
[26]docs/zh-cn/05-运维/版本说明.md: https://atomgit.com/CPF-RN/ohos_react_native/blob/0.84-stable/docs/zh-cn/05-运维/版本说明.md
[27]docs/zh-cn/05-运维/版本升级指导.md: https://atomgit.com/CPF-RN/ohos_react_native/blob/0.84-stable/docs/zh-cn/05-运维/版本升级指导.md
[28]docs/zh-cn/03-调测/调试调测.md: https://atomgit.com/CPF-RN/ohos_react_native/blob/0.84-stable/docs/zh-cn/03-调测/调试调测.md
[29]src/commands/init-harmony.ts: https://atomgit.com/CPF-RN/ohos_react_native/blob/0.84-stable/packages/react-native-harmony-cli/src/commands/init-harmony.ts
[30]packages/react-native-harmony-cli/src/init/templates/harmony: https://atomgit.com/CPF-RN/ohos_react_native/tree/0.84-stable/packages/react-native-harmony-cli/src/init/templates/harmony
[31]docs/templates/AwesomeProjectReplace: https://atomgit.com/CPF-RN/ohos_react_native/tree/0.84-stable/docs/templates/AwesomeProjectReplace
[32]docs/templates/MyApplicationReplace: https://atomgit.com/CPF-RN/ohos_react_native/tree/0.84-stable/docs/templates/MyApplicationReplace
[33]@react-native-oh/react-native-harmony: https://www.npmjs.com/package/@react-native-oh/react-native-harmony
[34]@react-native-oh/react-native-harmony-cli: https://www.npmjs.com/package/@react-native-oh/react-native-harmony-cli
[35]@react-native-community/cli: https://www.npmjs.com/package/@react-native-community/cli
[36]react-native: https://www.npmjs.com/package/react-native
[37]patch-package: https://www.npmjs.com/package/patch-package
[38]react-native/releases/tag/v0.84.1: https://github.com/facebook/react-native/releases/tag/v0.84.1
[39]reactnative.dev: https://reactnative.dev
[40]set-up-your-environment: https://www.react-native.cn/docs/set-up-your-environment
[41]reactnative.net.cn/docs/metro: https://reactnative.net.cn/docs/metro
[42]nodejs.org/api/deprecations.html#dep0178-direntpath: https://nodejs.org/api/deprecations.html#dep0178-direntpath
[43]nodejs.cn/api/deprecations/dep0178_dirent_path.html: https://nodejs.cn/api/deprecations/dep0178_dirent_path.html
[44]nodejs.org/api/fs.html#direntparentpath: https://nodejs.org/api/fs.html#direntparentpath
[45]nodejs/userland-migrations · dirent-path-to-parent-path: https://github.com/nodejs/userland-migrations/tree/main/recipes/dirent-path-to-parent-path
[46]nodejs.org/en/download: https://nodejs.org/en/download
[47]developer.huawei.com/consumer/cn/download/deveco-studio: https://developer.huawei.com/consumer/cn/download/deveco-studio
[48]ide-tools-overview: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-tools-overview
[49]ide-run-device: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-run-device
[50]ide-previewer-debug: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-previewer-debug
[51]ide-hvigor-set-options: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-hvigor-set-options
[52]ide-oh-package-json5: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-oh-package-json5
[53]ide-hvigor-build-profile: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-hvigor-build-profile-0000001778834297-V5
[54]app-sandbox-directory: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-sandbox-directory
[55]app-sandbox-directory-V5: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/app-sandbox-directory-V5
[56]resource-categories-and-access-V5: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/resource-categories-and-access-V5
[57]openharmony.cn: https://www.openharmony.cn
[58]atomgit.com/openharmony: https://atomgit.com/openharmony
[59]OpenHarmony/developtools_hapsigner: https://atomgit.com/openharmony/developtools_hapsigner
[60]atomgit.com/Harmonybrew/brew: https://atomgit.com/Harmonybrew/brew
本文由人人都是产品经理作者【nutpi】,微信公众号:【nutpi】,原创/授权 发布于人人都是产品经理,未经许可,禁止转载
题图来自Unsplash,基于 CC0 协议。
- 目前还没评论,等你发挥!

起点课堂会员权益



