鸿蒙 PC 上用 RNOH 开发开源鸿蒙应用

0 评论 211 浏览 0 收藏 94 分钟

一次真实、完整、可复现的环境搭建记录:在一台 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 图形界面做开发:

  1. 装 DevEco Studio[3]
  2. 用 IDE 新建 Empty Ability 工程
  3. 在 IDE 里点 File > Project Structure > Signing Configs,登录华为账号自动签名
  4. 点 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 等多个分支。

选版本有三个约束:

  1. RN 和 RNOH 的版本必须对齐(前端包与原生包版本号对齐)
  2. OHOS SDK 要支持(本机只有 API 26)
  3. 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 就能自己完成解密和签名。

这个工程还顺便给了两个宝贵信息:

  1. 它的 compatibleSdkVersion / targetSdkVersion 都是 26.0.0 —— 印证了第 7.7 节的判断,本机就该用 26。
  2. 它的 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.

注意两点:

  1. 报错是 Interrupted system call(EINTR),不是”文件不存在”,也不是用户代码的编译错误。
  2. 报错路径看着很怪 —— 它在 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 模式下它会:

  1. 先试 MetroJSBundleProvider → 没起 Metro 服务,失败
  2. 再试 hermes_bundle.hbc → 资源不存在,失败
  3. 两个都失败 → 白屏

而官方文档里那段 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}}

三个”反直觉”的点,单独强调:

  1. hvigor 不需要 JDK。 官方文档依赖 DevEco Studio,而 DevEco 捆绑了 JBR,容易让人误以为鸿蒙构建必须装 Java。实际 SDK 的 hap-sign-tool / ohos_packing_tool 都是原生 ELF,不碰 Java。
  2. 签名不需要知道 p12 密码。 DevEco 把密码加密存在机器级凭据库(~/.ohos/config/material/)里,配置文件里只有加密串。照抄那个加密串即可——解密是 hvigor 在构建时做的,不需要人工参与。
  3. 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 跑起来?

能。而且整条链路里真正“脏”的地方只有三处,都很容易处理:

  1. 一个 2 行的 CLI 补丁(Dirent.path → parentPath)—— 这是 RNOH CLI 与新版 Node 的兼容性缺口,跟鸿蒙本身无关;
  2. 一次签名配置搬运 —— 纯命令行下没法走”登录华为账号自动签名”,但如果机器上已有可用材料,照抄加密串就行;
  3. 一次 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 协议。

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
海报
评论
评论请登录
  1. 目前还没评论,等你发挥!