Monorepo 在 React Native 项目中的实践
本文由 AI 协助更新
高版本 React Native 已能很好地支持 Monorepo。
推荐做法:先用官方 CLI 创建主工程,再通过 Yarn Workspaces 或 pnpm Workspaces 在 packages/ 下管理多个原生库,主工程通过 workspace 依赖和路径配置引用这些库。
一、什么是 Monorepo
Monorepo 是一种代码组织方式:把多个相关子项目放在同一个 Git 仓库里,按目录划分模块,通过依赖声明控制谁可以引用谁。
- Monolith:单一大仓库,模块多、依赖杂,维护成本高。
- Multirepo:每个模块一个仓库,物理隔离好,但开发、构建、联调不便。
- Monorepo:一个仓库、多目录子项目,兼顾模块边界和统一开发体验。
在 iOS/Android 里也很常见:例如用 Cocoapods、Android Studio 多模块项目做 Monorepo。在 React Native / 前端里,通常用 Yarn Workspaces 或 pnpm Workspaces 实现。
原则:子项目(目录)之间只能通过 在 package.json 中声明的依赖 互相引用,避免随意跨目录导入,保证可维护性。
二、推荐结构
- 根目录:主 React Native 工程(官方 CLI 创建),入口与原生工程都在这里。
- packages/:多个子包(如原生模块、原生组件库、公共 JS 库),由 workspace 管理。
主工程依赖 packages/* 中的包,通过 workspace 协议 和 tsconfig/babel 路径 使用它们。
三、Monorepo 配置步骤
1. 创建主工程
使用 React Native 官方 CLI 创建主项目:
npx @react-native-community/cli init MyApp
cd MyApp
2. 启用 Workspace
在项目根目录的 package.json 中声明 workspaces,使主工程能识别 packages/ 下的包。
Yarn:
{
"name": "my-app",
"private": true,
"workspaces": ["packages/*"]
}
pnpm:
在根目录 package.json 中增加:
{
"name": "my-app",
"private": true,
"packageManager": "pnpm@9.0.0",
"pnpm": {
"overrides": {}
}
}
在项目根创建 pnpm-workspace.yaml:
packages:
- "packages/*"
然后执行:
yarn install
# 或
pnpm install
以链接 workspace 包。
3. 添加模块包
在 packages/ 下创建新的原生模块或组件库(示例使用 react-native-create-lib,--repo-name 可省略 react-native- 前缀):
mkdir -p packages && cd packages
# 原生模块示例
npx react-native-create-lib --module-name @sdcx/wechat --repo-name wechat --prefix RN --package-identifier com.sdcx.wechat wechat
# 原生组件示例
npx react-native-create-lib --module-name @sdcx/image-crop --repo-name image-crop --module-type components --prefix RN --package-identifier com.sdcx.imagecrop image-crop
创建完成后,在根目录再执行一次 yarn install 或 pnpm install 即可。
4. 主工程感知 packages:tsconfig paths
在主工程根目录的 tsconfig.json 中配置 paths,让 TypeScript 能解析 workspace 包源码:
{
"compilerOptions": {
"paths": {
"@sdcx/wechat": ["./packages/wechat/src"],
"@sdcx/image-crop": ["./packages/image-crop/src"],
"@sdcx/*": ["./packages/*/src"]
}
}
}
按实际包名与路径增减或修改。
5. 主工程感知 packages:Babel alias
在主工程的 babel.config.js 中为 module-resolver 配置与 paths 一致的 alias,保证运行时和打包能正确解析:
module.exports = {
presets: ["module:@react-native/babel-preset"],
plugins: [
[
"module-resolver",
{
root: ["./"],
extensions: [".ts", ".tsx", ".ios.js", ".android.js", ".js", ".json"],
alias: {
"@sdcx/wechat": "./packages/wechat/src",
"@sdcx/image-crop": "./packages/image-crop/src",
"@sdcx/*": "./packages/*/src",
},
},
],
],
};
若未安装 babel-plugin-module-resolver,先安装:
yarn add -D babel-plugin-module-resolver
# 或
pnpm add -D babel-plugin-module-resolver
6. 主工程依赖 workspace 包
在根目录 package.json 的 dependencies 中加入:
{
"dependencies": {
"@sdcx/wechat": "*",
"@sdcx/image-crop": "*"
}
}
*表示使用当前 workspace 内的版本。- Yarn 3+ / pnpm 也可写为
"workspace:*"明确使用 workspace 协议。
执行 yarn install 或 pnpm install 后,即可在业务代码中:
import { ... } from '@sdcx/wechat';
import { ... } from '@sdcx/image-crop';
四、Workspace 原理简述
Workspace 通过 软链 把 packages/* 映射到根目录的 node_modules(如 node_modules/@sdcx/wechat → packages/wechat)。
Node 和 Metro 解析 @sdcx/wechat 时会在 node_modules 找到该软链,从而指向真实源码;再配合 tsconfig paths 和 Babel alias,主工程即可直接使用各包的源码。
五、模块间依赖与约束
子包之间的依赖
packages/ 下的包也可以互相依赖。在子包的 package.json 中声明即可,例如:
{
"name": "@sdcx/image-crop",
"dependencies": {
"@sdcx/common": "*"
}
}
同样依赖 workspace 内的包时,用 * 或 workspace:*。
限制未声明依赖的导入
Monorepo 下仍可能通过相对路径(如 ../../packages/common)或未在 package.json 声明的包名导入其他模块,不利于维护。
可用 eslint-plugin-workspaces 做约束:
workspaces/no-relative-imports:禁止跨包相对路径导入。workspaces/require-dependency:使用某包时必须在当前 package.json 中声明依赖。
安装并配置示例:
yarn add eslint-plugin-workspaces -D -W
# 或(pnpm 时在根目录)
pnpm add -D eslint-plugin-workspaces
.eslintrc.js 示例:
module.exports = {
root: true,
plugins: ["workspaces"],
rules: {
"workspaces/no-relative-imports": "error",
"workspaces/require-dependency": "error",
},
};
必要时可配合 no-restricted-imports 做更细粒度限制。
六、安装依赖
- 根目录:主工程依赖用
yarn add xxx/pnpm add xxx。 - 某个 workspace 包:
- Yarn:
yarn workspace @sdcx/wechat add some-lib - pnpm:
pnpm --filter @sdcx/wechat add some-lib
- Yarn:
- 根目录 dev 依赖(所有包共用):
- Yarn:
yarn add -D xxx -W - pnpm:在根目录执行
pnpm add -D xxx
- Yarn:
依赖会被提升到根目录 node_modules,各子包共享同一套依赖。
小结
| 步骤 | 说明 |
|---|---|
| 1 | 用 npx @react-native-community/cli init 创建主工程 |
| 2 | 根目录 package.json 配置 workspaces: ["packages/*"](或 pnpm-workspace.yaml) |
| 3 | 在 packages/ 下用 react-native-create-lib 等创建原生模块/组件库 |
| 4 | 主工程 tsconfig 配置 paths,babel 配置 module-resolver alias |
| 5 | 主工程 dependencies 中通过 * 或 workspace:* 引用 packages |
按上述方式,高版本 React Native 即可在 Monorepo 下稳定使用。