本文记录把 react-native-css-transformer 适配到 HarmonyOS 的完整过程。
这个库和前 18 个都不一样:它不产出任何 UI,而是 Metro 的 babel 转换器(构建期工具) ------在打包时把 .css 文件转成 module.exports = {...样式对象...} 的 JS 模块。
这带来两个后果:
- "截图看效果"这条常规验证路子不适用,验证得拆成"转换语义 / 构建期接线 / 运行时落地"三层;
- 它的安装方式与运行时库相反 ------用本系列惯用的
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 条断言全部通过,覆盖了上一节表格里的每一条映射。其中值得一提的三条判据:
width的类型是number而不是字符串 ("120px"会被 RN 直接忽略,是最隐蔽的失败方式);padding简写被展开成四边 ,且原始padding键不再存在(留着简写是无效的);- 样式值里不含
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 库与转换规则本身的
-
width/height是 border-box 语义,与网页 CSS 的 content-box 不同(见 7.3)。 -
padding/margin简写会被展开成四边;保留简写是无效的,断言时要按展开后的键写。 -
颜色值原样保留 (
#ff0000还是#ff0000,rgb(...)还是rgb(...)),不会归一化成同一种写法。 -
单位只处理
px:其它单位(%、em、rem、vw等)本次未测,CHANGELOG 提到视口单位需要额外的 babel 插件配合。 -
媒体查询只产出元数据 (
__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那一类)去读它并在运行时选分支。 -
它只能处理
.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 本次验证的边界
- 只在一台模拟器上验证 (
Pura X View,density 3),没有真机。 .css语法覆盖有限 :只测了类选择器 + 单个媒体查询 + 基础属性;嵌套、@import、CSS 变量、伪类、%/em/rem/vw单位均未测。- 媒体查询的"实际应用"未验证:只验证了元数据产出,没有接入配套插件真正按查询切样式。
- 视觉属性未做像素验证 :
border-radius/font-size/color只验证了转换值与布局尺寸,没做像素取色或字形比对。 - 未与
StyleSheet.create对比 :直接传 css 对象能工作,但没比较用StyleSheet.create包装后的差异(校验、性能)。 - 样式合并/覆盖未测 :数组形式
[css.a, css.b]的优先级行为未验证。 - 没有跨平台对照(未在 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 轮遇不到的:
-
构建期工具的安装方式与运行时库相反。 运行时库由 Metro 解析(
nodeModulesPaths能兜底),file:软链完全没问题;构建期工具由 Node 解析(metro.config.js先加载它),软链目标不在宿主node_modules树里 ⇒ 内部所有require全部失败 ,补传递依赖也没用。必须装成真目录(tarball / git+)。 这一条与直觉相反,是本轮最该记住的。 -
构建期工具绝不能被应用代码
require。 Metro 会静态解析应用代码的require,把整棵依赖树打进 bundle,撞上fs/path这类 Node 内置模块。它只能由metro.config.js引用。 -
"把可疑配置设成等于默认值"是很强的隔离手段。 我把
babelTransformerPath设成与 Metro 默认完全同一个文件,看到同样的错误 ------ 立刻排除了"这个库有问题",把怀疑收敛到自己写的代码上。 -
加转换器往往还要同时加扩展名。
sourceExts不加css,文件根本解析不到,而错误信息不会提示这一点。顺带要确认它不在assetExts里(否则 Metro 会在两处冲突)。 -
CSS 的盒模型语义与 RN 不一致。 RN 的
width是 border-box,网页 CSS 默认是 content-box ⇒ 同时写width与padding的盒子实际尺寸会偏小。搬 CSS 时要专门核对这种组合。 -
"产出元数据"不等于"功能生效"。 媒体查询只被解析成
__mediaQueries结构,不会自动切样式;必须看到下游消费者真的用上它,才能说这条能力可用。 -
没有 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