Monorepo 在 React Native 项目中的实践

本文由 AI 协助更新

高版本 React Native 已能很好地支持 Monorepo

推荐做法:先用官方 CLI 创建主工程,再通过 Yarn Workspacespnpm Workspacespackages/ 下管理多个原生库,主工程通过 workspace 依赖和路径配置引用这些库。


一、什么是 Monorepo

Monorepo 是一种代码组织方式:把多个相关子项目放在同一个 Git 仓库里,按目录划分模块,通过依赖声明控制谁可以引用谁。

  • Monolith:单一大仓库,模块多、依赖杂,维护成本高。
  • Multirepo:每个模块一个仓库,物理隔离好,但开发、构建、联调不便。
  • Monorepo:一个仓库、多目录子项目,兼顾模块边界和统一开发体验。

在 iOS/Android 里也很常见:例如用 Cocoapods、Android Studio 多模块项目做 Monorepo。在 React Native / 前端里,通常用 Yarn Workspacespnpm 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 installpnpm 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.jsondependencies 中加入:

{
  "dependencies": {
    "@sdcx/wechat": "*",
    "@sdcx/image-crop": "*"
  }
}
  • * 表示使用当前 workspace 内的版本。
  • Yarn 3+ / pnpm 也可写为 "workspace:*" 明确使用 workspace 协议。

执行 yarn installpnpm install 后,即可在业务代码中:

import { ... } from '@sdcx/wechat';
import { ... } from '@sdcx/image-crop';

四、Workspace 原理简述

Workspace 通过 软链packages/* 映射到根目录的 node_modules(如 node_modules/@sdcx/wechatpackages/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
  • 根目录 dev 依赖(所有包共用)
    • Yarn:yarn add -D xxx -W
    • pnpm:在根目录执行 pnpm add -D xxx

依赖会被提升到根目录 node_modules,各子包共享同一套依赖。


小结

步骤说明
1npx @react-native-community/cli init 创建主工程
2根目录 package.json 配置 workspaces: ["packages/*"](或 pnpm-workspace.yaml)
3packages/ 下用 react-native-create-lib 等创建原生模块/组件库
4主工程 tsconfig 配置 paths,babel 配置 module-resolver alias
5主工程 dependencies 中通过 *workspace:* 引用 packages

按上述方式,高版本 React Native 即可在 Monorepo 下稳定使用。

上次更新: