React Native for OpenHarmony 实战:三方库 react-native-css-transformer 的鸿蒙化适配指南

本文记录把 react-native-css-transformer 适配到 HarmonyOS 的完整过程。

这个库和前 18 个都不一样:它不产出任何 UI,而是 Metro 的 babel 转换器(构建期工具) ------在打包时把 .css 文件转成 module.exports = {...样式对象...} 的 JS 模块。

这带来两个后果:

  1. "截图看效果"这条常规验证路子不适用,验证得拆成"转换语义 / 构建期接线 / 运行时落地"三层;
  2. 它的安装方式与运行时库相反 ------用本系列惯用的 file: 方式根本加载不了它,必须装成真目录。

一、先说结论

项 结果
上游最新版 2.0.0(MIT)。⚠️ npm 元数据里没有 gitHead 字段(其它库都有),所以无法从 npm 取到基线 commit
库类型 构建期工具 :导出 transform 钩子,把 .css 转成样式对象模块
是否需要原生适配 不需要(纯 JS,无原生模块)
与 npm 上游的差异 4 / 5 个文件去掉 CR 后逐字符一致 (含 index.js 实现);唯一差异是 package.json
安装方式 ⚠️ 必须装成真目录(tarball 或 git+);file: junction 装法加载不了
集成方式 metro.config.js 的 transformer.babelTransformerPath + resolver.sourceExts 加 'css'
编译 assembleHap 4 分 05 秒;HAP 81,774,729 → 81,795,208 字节(+20,479 ≈ 20 KB)
设备侧断言 ✅ 30 / 30 全部通过
构建期证据 ✅ bundle 里出现 module.exports = {"box120x60":{"width":120,...}},模块路径为 cssTestStyles.css
运行时证据 ✅ CSS 的 120px×60px → 布局实测 360×180 物理像素 ;80×40 → 240×120 ;100×50 + padding 10 → 300×150
必须知道的坑 ⚠️ ① file: 装法根本加载不了 ;② 应用代码不能 require 这个构建期工具;③ RN 的 width 是 border-box ,与 CSS 默认语义不同;④ 媒体查询只产出元数据、不会自动应用

一句话结论 :能用,CSS 数值精确落到布局上。但接线有三处硬要求,而且第一条就与直觉相反 ------它不能被当作普通运行时库那样用 file: 装。


二、判定过程:这不是运行时组件,而是构建期工具

判断一个 RN 库要不要做原生适配,固定走三步:

步 做法 这个库的结果
① package.json 里有没有 harmony.autolinking 没有
② 仓库里有没有 harmony/ 没有
③ 代码里有没有 NativeModules / requireNativeComponent 没有

但这一轮还要多问一个问题:它什么时候运行? 看 index.js 全文就能确定:

js 复制代码
const css2rn = require("css-to-react-native-transform").default;

const upstreamTransformer = (() => {
  try { return require("@expo/metro-config/babel-transformer"); }
  catch (error) {
    try { return require("@react-native/metro-babel-transformer"); }
    catch (error) { return require("metro-react-native-babel-transformer"); }
  }
})();

module.exports.transform = ({ src, filename, ...rest }) => {
  if (filename.endsWith(".css")) {
    var cssObject = css2rn(src, { parseMediaQueries: true });
    return upstreamTransformer.transform({
      src: "module.exports = " + JSON.stringify(cssObject),
      filename, ...rest
    });
  }
  return upstreamTransformer.transform({ src, filename, ...rest });
};

这个判断决定了后面所有事情:

问题 答案
它在应用里被 import 吗? 不。它的产物(样式对象)才被 import
谁加载它? metro.config.js,由 Node 加载
它什么时候执行? 打包期间,不是在设备上
它有哪些依赖? css-to-react-native-transform(构建期),以及三个候选上游 transformer

"由 Node 加载"这一条,就是第 4 节那个"file: 装法不可用"的根因。

公开 API 只有一个 :module.exports.transform。上游 README 面向的是"要在 metro.config.js 里怎么配",而不是"要在组件里怎么调"。


三、这个交付包长什么样:实现零改动

与 npm 上游 2.0.0 的 tarball 逐文件比对(先去 CR 再逐字符比较):

4 / 5 个文件完全一致 (index.js、README.md、CHANGELOG.md、LICENSE),唯一不同的是 package.json:

字段 变化
description 追加 " for React Native for OpenHarmony"
repository 由字符串改为对象(指向 AtomGit)
scripts.test 由 eslint . 换成 node --test __tests__/harmony-contract.test.cjs
homepage 新增 AtomGit 地址
files 删除了上游的白名单 (上游为 ["index.js","README.md"])

实现文件零改动 ,所以下面所有结论都只涉及使用方式与集成要求,与适配质量无关。


四、接入宿主:三处必须同时满足

4.1 安装:必须装成"真目录"

bash 复制代码
# ✅ 方式一:tarball(交付包 README 推荐的方式)
cd /path/to/react-native-css-transformer && npm pack
npm install /path/to/react-native-css-transformer-2.0.0.tgz

# ✅ 方式二:从适配仓库的 TAG 装(git+ 会 clone 成真目录)
npm install "git+https://atomgit.com/oh-react-native/react-native-css-transformer.git#2.0.0-ohos-1.0.0"

# ❌ 方式三:file: 本地路径 ------ 本系列惯用,但**这个包加载不了**(见第八节)

4.2 metro.config.js:两处改动

js 复制代码
const config = {
  transformer: {
    // ① 让 css-transformer 接管 babel 转换
    //    非 .css 文件它会原样委派给上游 transformer,所以对既有代码是透传的
    babelTransformerPath: require.resolve('react-native-css-transformer'),
    getTransformOptions: async () => ({ /* 原有配置保持不变 */ }),
  },
  resolver: {
    // ② 默认 sourceExts 里**没有** css,不加上就 `import s from './x.css'` 解析失败
    //    (已核实 RN 0.84 的 assetExts 里也没有 css,所以不存在"同时在两边"的冲突)
    sourceExts: ['js', 'jsx', 'json', 'ts', 'tsx', 'cjs', 'mjs', 'css'],
  },
};

4.3 应用代码:只使用它的产物

tsx 复制代码
import css from './x.css';          // ← 只有这一行是应用侧要写的

<View style={css.box120x60} />;

千万不要 在应用代码里 require('react-native-css-transformer') ------ 后果见第八节第 2 条。

三处缺一不可 :装成真目录(否则加载失败)、babelTransformerPath(否则 .css 不被转换)、sourceExts 加 css(否则文件解析不到)。而这三条交付包的 README 一条都没写。


五、验证设计:一个"不产出 UI"的库怎么验

它没有 UI、没有返回值、没有设备侧行为,所以验证必须拆成三层,而且每层都要有独立的证据:

层 要回答的问题 手段
① 转换语义 CSS 到底被转成什么? 在 PC 上用 Node 直接调底层 css-to-react-native-transform,把结果打印出来当真值
② 构建期接线 .css 真的被转换并进入产物了吗? 在打包产物里定位该模块的 module.exports
③ 运行时落地 由 CSS 生成的样式真的作用到渲染上了吗? 布局 dump 量 View 的矩形(density 3,CSS 数值取整数 ⇒ 期望值可精确计算)

5.1 先取真值,再写断言

这一点在这轮尤其重要:CSS→RN 的映射有很多"想当然会错"的地方(px 保不保留?简写展不展开?颜色转不转 hex?)。凭记忆写断言必错。

powershell 复制代码
cd E:\rnoh-work\RNOH084Demo
node _cssprobe\probe.cjs      # 打印转换后的完整结构

实测真值:

CSS 写法 转换结果
width: 120px width: 120(px 被剥成数字)
background-color: #ff0000 backgroundColor: "#ff0000"(键驼峰化,值不动)
background-color: rgb(0, 128, 255) "rgb(0, 128, 255)"(原样保留,不转 hex)
border-radius: 8px borderRadius: 8
padding: 10px 展开成 paddingTop/Right/Bottom/Left: 10
font-size: 20px fontSize: 20
flex-direction: row flexDirection: "row"

5.2 内容尺寸取整数,让期望值可精确计算

测试页里三个盒子的 CSS 数值都取整数(120/60、80/40、100/50),设备 density = 3 ⇒ 期望物理像素 360×180 / 240×120 / 300×150 。这样"精确到像素"才有判据。

5.3 分组口径

组 进通过率 内容
A 转换结果(构建期) ✅ 26 导入对象的键、px 剥离、驼峰化、简写展开、flex 透传、媒体查询元数据、样式值里无 px 残留
B 渲染几何(运行时) ✅ 4 三个盒子的实测尺寸
外部协议 信息 bundle 里的模块本体 + 布局 dump 的矩形

六、实测一:CSS → 样式的逐键真值(设备侧复核)

设备上跑 A. 转换结果,从 hilog 读回:

复制代码
· .css 转换后的顶层键 = box120x60, box80x40, padded100x50, label, row, __mediaQueries, @media (min-width: 100px)
· @media 下的内容 = {"box120x60":{"width":200}}
· __mediaQueries 内容 = {"@media (min-width: 100px)":[{"inverse":false,"type":"all",
                          "expressions":[{"modifier":"min","feature":"width","value":"100px"}]}]}
· 参与 px 检查的样式类 = box120x60, box80x40, padded100x50, label, row
· 转换结果总长度(字符) = 649

26 条断言全部通过,覆盖了上一节表格里的每一条映射。其中值得一提的三条判据:

  1. width 的类型是 number 而不是字符串 ("120px" 会被 RN 直接忽略,是最隐蔽的失败方式);
  2. padding 简写被展开成四边 ,且原始 padding 键不再存在(留着简写是无效的);
  3. 样式值里不含 px ------ 但注意这条只能对样式类做,不能对整个对象做,原因见第七节第 3 条。

七、实测二:构建期产物 + 运行时落地

7.1 构建期:在 bundle 里找到那个模块

打包后,在产物里能直接看到 .css 变成了标准 JS 模块:

js 复制代码
__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
  module.exports = {
    "box120x60": { "width": 120, "height": 60, "backgroundColor": "#ff0000", "borderRadius": 8 },
    "box80x40": { "width": 80, "height": 40, "backgroundColor": "rgb(0, 128, 255)" },
    "padded100x50": { "width": 100, "height": 50, "paddingTop": 10, "paddingRight": 10,
                      "paddingBottom": 10, "paddingLeft": 10, "backgroundColor": "#00aa00" },
    "label": { "fontSize": 20, "color": "#0000ff", "textAlign": "center" },
    "row": { "flexDirection": "row", "justifyContent": "space-between", "alignItems": "center" },
    "__mediaQueries": { ... },
    "@media (min-width: 100px)": { ... }
  };
}, 738, [], "cssTestStyles.css");

模块路径就是 cssTestStyles.css,内容是标准 JS ------ 这就是"转换真的生效了"的硬证据。同时可核对:bundle 里不含原始 CSS 文本 width: 120px。

7.2 运行时:CSS 数值 → 布局尺寸

在应用外读布局:

盒子 CSS 声明 期望 实测矩形
.box120x60 width:120px; height:60px 360×180 360×180 @ (60,1340) ✓
.box80x40 width:80px; height:40px 240×120 240×120 @ (60,1676) ✓
.padded100x50 width:100px; height:50px; padding:10px 300×150 300×150 @ (60,1952) ✓

三个盒子逐个精确。

7.3 一个必须注意的语义差异:RN 的 width 是 border-box

看第三个盒子:声明 width: 100px; padding: 10px。

  • 若按网页 CSS 的默认 content-box 语义,外框应是 100 + 2×10 = 120vp = 360px
  • 实测外框 300px = 100vp ⇒ RN/Yoga 的 width 是 border-box(padding 计在 100 之内)
  • 而内部文字 PADDED 的起点 x = 90 = 60 + 10vp×3 ⇒ padding 确实生效,只是被算在宽度之内

⇒ 把网页 CSS 直接搬到 RN 时,凡是同时写了 width 与 padding 的盒子,实际尺寸会比网页小一圈。 这是用这个 transformer 时最容易踩的语义坑。


八、两个必须知道的坑

8.1 file: 装法根本加载不了这个包

本系列前 18 个库都用 file: 装(离线、改完直接生效),所以这一轮我也先按惯例来。结果:

第一步 :npm install --ignore-scripts 报 added 1 package、软链建好,但 css-to-react-native-transform 没被装上 (这是 file: 的已知行为)。显式补装后,用 Node 直接 require 仍然失败:

复制代码
Error: Cannot find module 'css-to-react-native-transform'
Require stack:
- E:\rnoh-work\react-native-css-transformer\index.js      ← junction 的**目标目录**

原因 :node_modules/react-native-css-transformer 是指向真实目录的 junction。Node 解析 index.js 里的 require(...) 时,从该文件所在的真实目录往上找:

复制代码
E:\rnoh-work\react-native-css-transformer\node_modules   ← 不存在
E:\rnoh-work\node_modules                                ← 不在宿主树里
E:\node_modules                                          ← 不存在

⇒ 宿主的 node_modules 根本不在它的祖先链上。 Metro 的 resolver.nodeModulesPaths 救不了它 ------因为这里的消费者是 Node 本身 (metro.config.js 由 Node 加载),不是 Metro 的解析器。

第二步 :把传递依赖补进库目录,下一个错误立刻出现:

复制代码
Error: Cannot find module 'metro-react-native-babel-transformer'
Require stack:
- E:\rnoh-work\react-native-css-transformer\index.js:18

而 @react-native/metro-babel-transformer 明明在宿主的 node_modules 里也存在,却同样解析不到。库里是三级 fallback:

js 复制代码
try { return require("@expo/metro-config/babel-transformer"); }         // 宿主没有 → 抛,被捕获
catch { try { return require("@react-native/metro-babel-transformer"); } // 宿主要有,但**从这里解析不到** → 抛,被捕获
        catch { return require("metro-react-native-babel-transformer"); } } // 哪都没有 → 抛,**未被捕获** ⇒ 模块加载失败

⇒ 不是"缺一个依赖",而是库内部所有 require 都从 junction 目标解析。

正确做法:装成真目录。

项 file: junction tarball / git+
包本体 ✅ 软链 ✅ 真目录
css-to-react-native-transform ❌ 不装 ✅ 自动装上
metro.config.js 里能否 require 它 ❌ 失败 ✅ 成功
结论 不可用 可用

顺带记录一个反面教材:为排查这个问题,我在库目录里跑了一次 npm install <pkg>,结果把它 devDependencies 也一并拉齐 ------ 855 个包。所以"往库目录补依赖"这条路线本身也很脏。

另外,运行时库和构建期库的结论是相反的 :运行时库由 Metro 解析,file: 完全没问题;构建期库由 Node 解析,file: 直接不可用。

8.2 应用代码不能 require 这个构建期工具

接线完成后打包失败:

复制代码
error Unable to resolve module metro-react-native-babel-transformer from
      E:\rnoh-work\RNOH084Demo\node_modules\react-native-css-transformer\index.js

补上那个包后,错误变成:

复制代码
error Unable to resolve module fs from
      E:\rnoh-work\RNOH084Demo\node_modules\@react-native\metro-babel-transformer\src\index.js

fs 是 Node 内置模块 ,Metro 当然解析不了 ⇒ 说明 Metro 在把 transformer 的整棵依赖树当成应用代码来打包。

定位过程 :我做了一个关键对照------把 babelTransformerPath 显式设成与 Metro 默认完全同一个文件:

js 复制代码
babelTransformerPath: require.resolve('@react-native/metro-babel-transformer'),

结果报的还是同一个 fs 错误 ⇒ 与 css-transformer 无关。

真正的原因在我的测试页里:

ts 复制代码
const t = require('react-native-css-transformer');   // ← 我为了断言"导出里有 transform"而写的

Metro 会静态解析应用代码里的 require ,于是把构建期工具(→ @react-native/metro-babel-transformer → fs)拉进了 bundle。

修正与验证 :从测试页删掉那个 require,并把之前为它补装的 metro-react-native-babel-transformer 卸掉再打包:

复制代码
npm uninstall metro-react-native-babel-transformer   → removed 12 packages
npm run dev                                          → exit 0  ✅

⇒ 那个包从来就不需要 ,只是被错误的 require 拖进来的。

规律 :babelTransformerPath 指向的模块由 Node 在构建期加载,绝不能出现在应用代码的依赖图里。


九、语义差异与已知限制

9.1 库与转换规则本身的

  1. width/height 是 border-box 语义,与网页 CSS 的 content-box 不同(见 7.3)。

  2. padding/margin 简写会被展开成四边;保留简写是无效的,断言时要按展开后的键写。

  3. 颜色值原样保留 (#ff0000 还是 #ff0000,rgb(...) 还是 rgb(...)),不会归一化成同一种写法。

  4. 单位只处理 px :其它单位(%、em、rem、vw 等)本次未测,CHANGELOG 提到视口单位需要额外的 babel 插件配合。

  5. 媒体查询只产出元数据 (__mediaQueries + "@media ..." 两个键),不会自动应用:

    json 复制代码
    "__mediaQueries": {
      "@media (min-width: 100px)": [
        {"inverse": false, "type": "all",
         "expressions": [{"modifier": "min", "feature": "width", "value": "100px"}]}
      ]
    },
    "@media (min-width: 100px)": { "box120x60": {"width": 200} }

    要真正按媒体查询切样式,需要配套插件(上游生态里是 babel-plugin-react-native-classname-to-dynamic-style 那一类)去读它并在运行时选分支。

  6. 它只能处理 .css 文件 (按 filename.endsWith('.css') 判断),文件名以外的判定方式不支持。

9.2 交付包质量

做得好的:

  • 实现零改动 (4/5 文件与上游逐字符一致,含 index.js);
  • spec.json 带 license: MIT;
  • scripts.test 换成可运行的 node --test;
  • npm pack 正常 (10 个文件、无 prepare 脚本)------对比某些交付包因 prepare: bob build 连 tgz 都打不出来。

问题:

① README 的「使用示例」会把人带进坑。 两个 README 都写:

ts 复制代码
import Library from 'react-native-css-transformer';
const Library = require('react-native-css-transformer');

而它没有任何应用侧 API (导出的 transform 是给 metro 调用的)。照着这个示例写,恰好触发 8.2 那个打包失败 (应用代码 require 构建期工具 → 把 fs 拖进 bundle)。正确用法是"写进 metro.config.js 的 babelTransformerPath,应用代码只 import .css"。

② README 没提 metro.config.js 的那两处改动 (babelTransformerPath 与 sourceExts 加 css)------ 缺任何一个功能都不生效,而 README 只说"不需要接线"。

③ README 没提传递依赖 css-to-react-native-transform 必须可达 ,也没提 tarball / git+ 是唯一可行的安装方式。

④ spec.json 无 upstreamCommit ------ 这次有个客观原因:npm 元数据里没有 gitHead (其它 18 个库都有),确实取不到基线 commit。但 validation 仍是裸字符串 "pass"。

⑤ 契约测试通过 ≠ 可用 :只断言包名、版本、主入口文件存在、中文 README 存在 ------ 没有调用一次 transform、没有验证任何一条 CSS→样式映射,更没验证它能否被 metro 加载。

9.3 本次验证的边界

  1. 只在一台模拟器上验证 (Pura X View,density 3),没有真机。
  2. .css 语法覆盖有限 :只测了类选择器 + 单个媒体查询 + 基础属性;嵌套、@import、CSS 变量、伪类、%/em/rem/vw 单位均未测。
  3. 媒体查询的"实际应用"未验证:只验证了元数据产出,没有接入配套插件真正按查询切样式。
  4. 视觉属性未做像素验证 :border-radius / font-size / color 只验证了转换值与布局尺寸,没做像素取色或字形比对。
  5. 未与 StyleSheet.create 对比 :直接传 css 对象能工作,但没比较用 StyleSheet.create 包装后的差异(校验、性能)。
  6. 样式合并/覆盖未测 :数组形式 [css.a, css.b] 的优先级行为未验证。
  7. 没有跨平台对照(未在 iOS/Android 上跑同一份用例)。

未改动库代码。


十、常见问题

Q1:这个库要在组件里怎么调?

不需要调 。它没有任何应用侧 API。你只写一行 import css from './x.css',css 就是转换后的样式对象,直接用:

tsx 复制代码
import css from './x.css';
<View style={css.box120x60} />;

Q2:怎么装?

bash 复制代码
# tarball
cd /path/to/react-native-css-transformer && npm pack
npm install /path/to/react-native-css-transformer-2.0.0.tgz

# 或从适配仓库的 TAG(会 clone 成真目录,同样可用)
npm install "git+https://atomgit.com/oh-react-native/react-native-css-transformer.git#2.0.0-ohos-1.0.0"

不要 用 file: 本地路径 ------ 见 Q3。

Q3:为什么 file: 装法不行?

因为 node_modules/react-native-css-transformer 是个 junction(软链) ,而 metro.config.js 是由 Node 加载这个包的。Node 从软链的目标目录 往上找依赖,而宿主的 node_modules 不在那条祖先链上 ⇒ 它内部所有 require 都失败(连 @react-native/metro-babel-transformer 这种宿主里明明有的也找不到)。

Metro 的 nodeModulesPaths 救不了它 ,因为这里的解析器不是 Metro。⇒ 必须装成真目录(tarball 或 git+ 都会 clone 成真目录)。

Q4:metro.config.js 到底要改哪几处?

两处,缺一不可:

js 复制代码
transformer: {
  babelTransformerPath: require.resolve('react-native-css-transformer'),
},
resolver: {
  sourceExts: ['js', 'jsx', 'json', 'ts', 'tsx', 'cjs', 'mjs', 'css'],   // ← 加上 css
},

sourceExts 不加 css,import s from './x.css' 会直接解析失败。

Q5:会不会影响我原来的代码?

不会。转换器只对 .css 结尾的文件做处理,其它文件原样委派给上游 transformer (实测:.js 分支输出与输入一致)。我也核对了既有测试页在 bundle 里仍然齐全。

Q6:padding 为什么被拆成了四个键?

这是转换器的行为:padding: 10px → paddingTop/paddingRight/paddingBottom/paddingLeft: 10,原始 padding 键不再存在 。所以你写断言时不能检查 padding。

Q7:为什么我给盒子写 width: 100px; padding: 10px,量出来还是 100 而不是 120?

因为 RN/Yoga 的 width 是 border-box 语义 (padding 算在宽度之内),而网页 CSS 默认是 content-box。要得到 120 的效果,请显式写 width: 120px,或者改用 contentContainerStyle 之类的结构。

Q8:媒体查询能用吗?

转换器会解析它,但不会自动应用。 结果里会多出 __mediaQueries(解析后的查询描述)与 "@media (min-width: 100px)"(该查询下的样式差异)两个键,需要配套插件去消费它们才能在运行时切换样式。

Q9:require.resolve(...) 在 metro.config.js 里是怎么被解析的?

由 Node 解析(metro.config.js 是 Node 加载的普通 JS),所以它走的是 Node 的解析规则 ------ 这正是 Q3 那个问题的根源。也正因为如此,不能 在 metro.config.js 里指望 Metro 的 resolver 配置生效。

Q10:.css 里的单位支持哪些?

本次只验证了 px (会被剥成数字)。%/em/rem/vw 等未测;CHANGELOG 提到视口单位需要额外的 babel 插件配合。


小结

react-native-css-transformer 是本系列第一个构建期工具 :没有 UI、没有运行时 API,只在打包时把 .css 转成样式对象。它带来的经验几乎都是前 18 轮遇不到的:

  1. 构建期工具的安装方式与运行时库相反。 运行时库由 Metro 解析(nodeModulesPaths 能兜底),file: 软链完全没问题;构建期工具由 Node 解析(metro.config.js 先加载它),软链目标不在宿主 node_modules 树里 ⇒ 内部所有 require 全部失败 ,补传递依赖也没用。必须装成真目录(tarball / git+)。 这一条与直觉相反,是本轮最该记住的。

  2. 构建期工具绝不能被应用代码 require。 Metro 会静态解析应用代码的 require,把整棵依赖树打进 bundle,撞上 fs/path 这类 Node 内置模块。它只能由 metro.config.js 引用。

  3. "把可疑配置设成等于默认值"是很强的隔离手段。 我把 babelTransformerPath 设成与 Metro 默认完全同一个文件,看到同样的错误 ------ 立刻排除了"这个库有问题",把怀疑收敛到自己写的代码上。

  4. 加转换器往往还要同时加扩展名。 sourceExts 不加 css,文件根本解析不到,而错误信息不会提示这一点。顺带要确认它不在 assetExts 里(否则 Metro 会在两处冲突)。

  5. CSS 的盒模型语义与 RN 不一致。 RN 的 width 是 border-box,网页 CSS 默认是 content-box ⇒ 同时写 width 与 padding 的盒子实际尺寸会偏小。搬 CSS 时要专门核对这种组合。

  6. "产出元数据"不等于"功能生效"。 媒体查询只被解析成 __mediaQueries 结构,不会自动切样式;必须看到下游消费者真的用上它,才能说这条能力可用。

  7. 没有 UI 的库,验证要分层且有独立证据。 转换语义(PC 真值)、构建期接线(bundle 里的模块本体)、运行时落地(布局尺寸)三层互相独立 ------ 任何一层单独看都不足以证明"能用"。


本篇用到的库

项 内容
三方库 react-native-css-transformer(上游 2.0.0 的鸿蒙适配版)
适配仓库 https://atomgit.com/oh-react-native/react-native-css-transformer
适配 TAG 2.0.0-ohos-1.0.0
需要 HAR / 权限 / ohpm 都不需要(纯 JS 构建期工具)
库类型 Metro babel 转换器 :导出 transform,把 .css 转成样式对象模块
运行时依赖 css-to-react-native-transform@^2.0.0(构建期依赖;tarball/git+ 安装时会自动带上)
上游仓库 https://github.com/kristerkari/react-native-css-transformer(MIT)。⚠️ 上游 npm 元数据没有 gitHead,无法给出基线 commit
宿主工程 RNOH084Demo(测试页 rnAppKey = CssTransformerTestApp)

接入方式(纯 JS,但必须装成真目录):

bash 复制代码
# 从适配仓库安装(git+ 会 clone 成真目录)
npm install "git+https://atomgit.com/oh-react-native/react-native-css-transformer.git#2.0.0-ohos-1.0.0"

# 或本地 tarball
cd /path/to/react-native-css-transformer && npm pack
npm install /path/to/react-native-css-transformer-2.0.0.tgz
js 复制代码
// metro.config.js ------ 两处,缺一不可
const config = {
  transformer: {
    babelTransformerPath: require.resolve('react-native-css-transformer'),
    getTransformOptions: async () => ({ /* 原有配置保持不变 */ }),
  },
  resolver: {
    sourceExts: ['js', 'jsx', 'json', 'ts', 'tsx', 'cjs', 'mjs', 'css'],
  },
};
css 复制代码
/* styles.css ------ 内容在打包时被转成样式对象 */
.box {
  width: 120px;
  height: 60px;
  background-color: #ff0000;
  border-radius: 8px;
  padding: 10px;      /* 会被展开成 paddingTop/Right/Bottom/Left */
}
tsx 复制代码
// 应用代码:只 import 产物,不要 require 这个包本身
import css from './styles.css';

<View style={css.box} />;
bash 复制代码
# 打包后核对 .css 是否真的被转换(在 bundle 里找模块本体)
npm run dev
Select-String -Path harmony\entry\src\main\resources\rawfile\bundle.harmony.js -Pattern 'module.exports = \{'

# 换页启动测试页(force-stop 不能省,换页参数只在冷启动生效)
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey CssTransformerTestApp

验证环境

项 版本
React Native 0.84.1
React 19.2.3
RNOH(npm / ohpm) @react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.84.3
Node.js / npm v24.14.0 / 11.9.0
DevEco Studio 26.0.0.621
HarmonyOS SDK API 26(26.0.0.32)
设备 HarmonyOS 7.0.0(26.0.0) Beta2 模拟器 Pura X View(ohos-x64),1320×2232,density 3
宿主 HAP 产物 entry-default-signed.hap(81.80 MB)
本次增量构建 assembleHap 4 分 05 秒,HAP +20,479 字节
验证规模 设备侧 30 / 30 断言全部通过;PC 端真值 1 次;bundle 模块核对 1 次;布局 dump 三个盒子的矩形(360×180 / 240×120 / 300×150)

欢迎加入 CPF-RN 鸿蒙社区:https://atomgit.com/CPF-RN

React Native for OpenHarmony 组织:https://atomgit.com/oh-react-native

RN 三方库鸿蒙适配清单:https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview

相关推荐
老金带你玩AI6 小时前
ChatGPT dots,到底能替你操多少心?
人工智能
AliCloudROS6 小时前
计算巢 X DeepSeek Harness — 云端智能体工作台
人工智能·阿里云
深蓝AI7 小时前
GitHub的Push一年涨4.9倍:AI Agent为什么逼它重做Git存储?
人工智能·github
stereohomology7 小时前
大模型的观点谨:StoryTold 「Crafting Apps」四件套 · 深度总览
人工智能·llm
运维行者_7 小时前
网络性能监控怎么做?从自动发现到根因分析的4个环节
运维·服务器·网络·人工智能·支持向量机
Su米苏7 小时前
Spring AI 中MCP 与普通 @Tool的区别
java·人工智能·spring
美狐美颜sdk7 小时前
直播APP源码可以直接接入视频美颜sdk吗?技术方案详解
android·人工智能·音视频·美颜sdk·直播美颜sdk
问商十三载8 小时前
AI引擎生成式优化实践:刑事辩护律师团队IP构建
大数据·人工智能