本页介绍 Metro bundler 的 Resolution → Transformation → Serialization 三阶段流水线,说明各阶段对应的 metro.config.js 配置字段,以及缓存机制和 Fast Refresh 的工作原理。
Metro 是什么
Metro 是 React Native 官方的 JavaScript bundler,由 Meta 开发,专为移动端设计。它的核心职责和 webpack/Vite 类似——把分散的 JS/TS 模块打成一个(或多个)bundle——但针对 RN 开发体验做了优化:超快的增量构建、HMR、对 require 的动态分析。
三阶段流水线
1. Resolution(依赖解析)
从入口文件出发,递归查找所有 import/require,建立依赖图(dependency graph)。
关键行为:
- 平台扩展名优先:
Button.ios.tsx比Button.tsx优先被 iOS 构建选中 resolver.assetExts决定哪些文件扩展名当资源(图片、字体)而不是代码resolver.sourceExts决定哪些扩展名是可解析的源文件(默认含ts,tsx,js,jsx)- NativeWind 在这阶段通过
resolver.unstable_conditionNames插入自己的模块映射
2. Transformation(模块转换)
对依赖图里每个文件独立做转换,输出 Metro 可以序列化的模块格式。
关键行为:
- 每个文件通过 Babel(
babel.config.js)转换:JSX → JS、TS → JS、装饰器等语法降级 - 转换结果被缓存(见下文缓存机制),下次如果文件未变则直接复用
transformer.babelTransformerPath可以替换默认 transformer,NativeWind v4 用此接入 CSS-in-JS 转换
3. Serialization(序列化打包)
把转换后的模块拼装成最终产物。
关键行为:
- 开发模式:每个模块保持独立 segment,支持 HMR 替换单个模块
- 生产模式(
expo export):所有模块拼成一个 bundle 文件,Hermes 再编译成字节码 serializer.customSerializer可以接管这个阶段,做代码分割(React Native RAM Bundles / lazy requires)
metro.config.js 字段对应阶段
const { getDefaultConfig } = require('expo/metro-config')
const config = getDefaultConfig(__dirname)
// ——— Resolution 阶段 ———
config.resolver.sourceExts.push('svg') // 新增可解析扩展名
config.resolver.assetExts.push('glb') // 3D 资产
config.resolver.extraNodeModules = { ... } // 模块别名
// ——— Transformation 阶段 ———
config.transformer.babelTransformerPath = '...' // NativeWind 等库在这里注入
config.transformer.minifierPath = '...' // 替换压缩器(默认 terser)
// ——— Serialization 阶段 ———
config.serializer.customSerializer = (...) // 自定义 bundle 输出格式
module.exports = config
配置文件调用链
pnpm start
└─ Expo CLI
读取 app.json(plugins、platforms、sdkVersion)
注入 Expo 特有 resolver/transformer(如 expo-modules-core)
└─ Metro(metro.config.js)
每个模块调用 Transformation 时
└─ Babel(babel.config.js)
NativeWind preset 激活时
└─ NativeWind(读取 tailwind.config.js 生成工具类 token)
重要细节:Expo SDK 54 默认不生成 metro.config.js 和 babel.config.js。Expo CLI 内置了对应的默认配置(通过 expo/metro-config 和 babel-preset-expo 提供)。只有当你需要覆盖默认行为时才创建这两个文件,否则不需要。
缓存机制
Metro 对 Transformation 阶段的结果做文件级缓存。
- 缓存位置:
$TMPDIR/metro-cache/或node_modules/.cache/metro/(取决于版本) - 缓存 key 由以下因素决定:文件内容哈希 + 依赖的
babel.config.js内容哈希 + Metro 版本 - 需要
--reset-cache的场景:- 修改了
babel.config.js或metro.config.js但没看到效果 - 安装了新的 Babel plugin/preset
- NativeWind 样式不生效(NativeWind transformer 的输出被旧缓存覆盖)
- 升级了 Expo SDK / Metro 版本后出现奇怪错误
- 修改了
pnpm start --reset-cache
# 或
pnpm expo start --clear
HMR / Fast Refresh 原理
Fast Refresh 是 Metro + React 共同实现的功能,不是单独的工具。
工作流程:
- Metro 监听文件系统变化(FSWatcher)
- 文件变更时,只对变更文件及其受影响的依赖重新跑 Transformation
- 通过 WebSocket 把变更的模块 patch 推送到 Expo Dev Client
- React 的 Fast Refresh runtime 在 JS 线程接收 patch,用新模块替换旧模块
- 如果变更的文件只包含 React 组件(没改 hook/class 结构),组件状态被保留;如果改了 hook 顺序或 class 结构,状态被重置
Fast Refresh 的”保留状态”能力依赖 React 内部的 reconciler——它知道哪些状态可以安全保留。这是 webpack HMR 做不到的(webpack HMR 的状态保留需要手写 module.hot.accept 回调)。
常见排查
| 现象 | 原因 | 解决 |
|---|---|---|
| 改了配置文件但不生效 | Metro 缓存旧的 transformer 结果 | --reset-cache |
| NativeWind class 不生效 | tailwind.config.js content 路径没覆盖到文件 | 检查 glob 路径 |
Cannot find module | resolver.sourceExts 缺少扩展名 | 在 metro.config.js 添加 |
| bundle 很慢 | 依赖图太大,maxWorkers 不够 | METRO_MAX_WORKERS=8 pnpm start |
参见
- NativeWind v4 Expo 配置指南
- Hermes 字节码引擎
参考
版本说明
- 本页基于 2026-04-13 调研结果整理,适用范围以当前工具版本为准。