Skip to content

使用 pnpm 创建 Monorepo 项目

什么是 Monorepo

Monorepo(Monolithic Repository,单体仓库)是一种代码管理的策略,与“多仓库”相对。它指的是将多个项目、包或应用程序存储在同一个版本控制仓库(通常是 Git)中。

简单来说,就是把原本分散在几十个甚至上百个独立仓库里的代码,全部放到同一个仓库里进行管理。

优点

  • 代码共享与复用及其方便

如果 web-app 需要修改 utils 中的一个函数,你只需要在同一个仓库中修改,然后直接发布新版本即可。

不需要像多仓库那样,需要去 utils 仓库提 PR,等待合并,然后在 web-app 仓库升级版本,再拉取代码。

  • 统一的依赖管理

所有项目使用相同的 package.json(或通过锁文件管理),避免了版本冲突。例如,不会出现 A 项目用 React 18,B 项目用 React 17 导致的不兼容问题。

安装依赖只需运行一次命令(如 npm install),大大节省磁盘空间和时间。

  • 原子化提交

你可以在一次 Commit 中同时修改 UI 组件库和业务应用。这保证了代码的一致性,不会出现“库改了,应用还没适配”导致中间状态报错的情况。

  • 统一的 CI/CD 和工具链

所有项目可以使用同一套 ESLint、Prettier、TypeScript 配置和构建脚本。

只需要维护一套 CI/CD 流程,可以针对被修改的文件进行增量构建,提高效率。

  • 简化跨团队协作

团队成员可以更容易地查看和调试其他团队的代码,因为所有代码都在手边,不需要切换仓库。

使用 pnpm 创建 Monorepo 项目

安装 pnpm

bash
npm install -g pnpm

初始化 Monorepo

bash
pnpm init
  • 根目录会有一个 package.json, 最重要的就是 name(后期会用)

  • 里面scripts里面写命令,pnpm -r run dev就是运行所有子项目的 dev 命令 -r就是递归

json
{
  "name": "basic",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "all:start": "pnpm -r run dev"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "packageManager": "pnpm@10.15.1"
}

添加工作区

  • 我这里就是举个例子,实际项目按照自己的需求来划分
bash

baisc
├── apps
   ├── h5
   └── web
├── packages
   ├── command
   ├── date
   └── ui
├── pnpm-workspace.yaml
├── .gitignore
├── package.json
└── README.md

pnpm-workspace.yaml

  • 这里就是规定了包, 他只会找到这些包,其他包不会找
yaml
packages:
  - "packages/**"
  - "apps/**"

创建每个文件夹下面的 package.json

  • packages/command/package.json

注意

  1. name 必须以 @开头,后面是 workspace 的 name,然后是包的 name,用 / 分隔
  2. scripts 里面写命令,pnpm run dev就是运行当前项目的 dev 命令
  3. type: module 必须写,当成模块

代码如下

json
{
  "name": "@basic/command",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "packageManager": "pnpm@10.15.1",
  "type": "module"
}
  • packages/date/package.json

注意

  1. name 必须以 @开头,后面是 workspace 的 name,然后是包的 name,用 / 分隔
  2. scripts 里面写命令,pnpm run dev就是运行当前项目的 dev 命令
  3. type: module 必须写,当成模块

代码如下

json
{
  "name": "@basic/date",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "packageManager": "pnpm@10.15.1",
  "type": "module"
}
  • packages/ui/package.json

注意

  1. name 必须以 @开头,后面是 workspace 的 name,然后是包的 name,用 / 分隔
  2. scripts 里面写命令,pnpm run dev就是运行当前项目的 dev 命令
  3. type: module 必须写,当成模块

代码如下

json
{
  "name": "@basic/ui",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "packageManager": "pnpm@10.15.1",
  "type": "module"
}

为各个不同的文件夹写对应的方法

  • packages/command/index.js
js
export function countChars(str) {
  const result = {};
  for (let i = 0; i < str.length; i++) {
    const char = str[i];
    if (result[char]) {
      result[char]++;
    } else {
      result[char] = 1;
    }
  }
  return Object.entries(result);
}

console.log(countChars("helloworld"));
  • packages/date/index.js
js
export function formatDate(date) {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}

console.log(formatDate(new Date()));
  • packages/ui/index.js
js
export function toUpperCase(str) {
  return str.toUpperCase();
}

console.log(toUpperCase("adadada"));

到根目录

  • 运行命令
bash
npm run all:start

结果

  • 控制台要是没报错,并且打印出结果.则代表成功

apps 文件夹

创建每个文件夹下面的 package.json

  • apps/h5/package.json

注意

  1. name 必须以 @开头,后面是 workspace 的 name,然后是包的 name,用 / 分隔
  2. scripts 里面写命令,pnpm run dev就是运行当前项目的 dev 命令
  3. type: module 必须写,当成模块
  4. dependencies 里面写依赖,@basic/command就是依赖 workspace 的 command 包

代码如下

json
{
  "name": "@basic/h5",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "packageManager": "pnpm@10.15.1",
  "type": "module",
  "dependencies": {
    "@basic/command": "workspace:*",
    "@basic/date": "workspace:*",
    "@basic/ui": "workspace:*"
  }
}
  • apps/web/package.json

注意

  1. name 必须以 @开头,后面是 workspace 的 name,然后是包的 name,用 / 分隔
  2. scripts 里面写命令,pnpm run dev就是运行当前项目的 dev 命令
  3. type: module 必须写,当成模块
  4. dependencies 里面写依赖,@basic/command就是依赖 workspace 的 command 包

代码如下

json
{
  "name": "@basic/web",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "packageManager": "pnpm@10.15.1",
  "type": "module",
  "dependencies": {
    "@basic/command": "workspace:*",
    "@basic/date": "workspace:*",
    "@basic/ui": "workspace:*"
  }
}

安装依赖

  • 运行命令
bash
pnpm install

为各个不同的文件夹写对应的方法

  • apps/h5/index.js
js
import { countChars } from "@basic/command";
import { formatDate } from "@basic/date";
import { toUpperCase } from "@basic/ui";

console.log(toUpperCase("testone"));

console.log(formatDate(new Date()));

console.log(countChars("goodafternoon"));
  • apps/web/index.js
ts
import { countChars } from "@basic/command";
import { formatDate } from "@basic/date";
import { toUpperCase } from "@basic/ui";

console.log(toUpperCase("testone"));

console.log(formatDate(new Date()));

console.log(countChars("goodafternoon"));

到根目录

  • 运行命令
bash
pnpm run all:start

结果

  • 控制台要是没报错,并且打印出结果.则代表成功

总结

  • 这样一个基础版本的 Monorepo 就完成了