解决的问题
Monorepo
管理方式解决了如下问题:
- 跨项目的改动困难:更改核心库需要在多个仓库中手动更新。
- 版本不一致:不同项目可能因为未及时更新依赖而导致的版本冲突。
- 流程复杂化:多个代码仓可能意味着重复配置多个 CI/CD 流程。
- 团队协作问题:不同的项目分属不同的仓库,增加了团队之间沟通的工作量。
初始化仓库
# Node 18+、pnpm 9+ 建议
mkdir my-monorepo && cd my-monorepo
git init
pnpm init -y
在根 package.json
补充(指定工作区包管理器、方便工具识别):
{
"name": "my-monorepo",
"private": true,
"packageManager": "pnpm@9.0.0",
"scripts": {
"dev": "pnpm -r --parallel dev",
"build": "pnpm -r build",
"lint": "pnpm -r lint",
"test": "pnpm -r test"
}
}
新建 .npmrc
(一些常用工作区优化):
; 在工作区根目录只维护一个 pnpm-lock.yaml(所有子包共享同一把锁),
; 方便 CI 和保证各包依赖版本一致。设为 false 时每个子包都会各自生成锁文件。
shared-workspace-lockfile = true
; 当依赖能在本地工作区里找到且版本满足时,用符号链接直接链接本地包,
; 而不是从 registry 下载,提升本地开发联调效率。
link-workspace-packages = true
; 当既能用本地工作区的包也能用远端版本时,优先使用本地工作区包。
;(若想模拟“真实用户安装远端包”的场景,可临时关掉)
prefer-workspace-packages = true
; 自动安装缺失的 peerDependencies,减少“缺少 peer 依赖”的报错。
; 注意:自动装的 peer 通常不会写入 package.json,想显式固定版本建议手动添加。
auto-install-peers = true
定义工作区
根目录新建 pnpm-workspace.yaml
:
packages:
- "apps/*"
- "packages/*"
跨项目依赖
my-monorepo/
pnpm-workspace.yaml
packages/
utils/
package.json
src/index.ts
apps/
web/
package.json
src/main.ts
pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
packages/utils/package.json
{
"name": "@acme/utils",
"version": "0.0.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsup src/index.ts --dts"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.4.0"
}
}
packages/utils/src/index.ts
export const sum = (a: number, b: number) => a + b;
apps/web/package.json
{
"name": "web",
"private": true,
"type": "module",
"scripts": {
"dev": "node src/main.ts",
"build": "echo web build"
},
"dependencies": {
"@acme/utils": "workspace:*"
}
}
apps/web/src/main.ts
import { sum } from "@acme/utils";
console.log("sum:", sum(1, 2));
大概就是这样
加入 Turbo 做任务编排与缓存
官网: https://www.npmjs.com/package/turbo
pnpm -w add -D turbo
turbo.json:
{
"pipeline": {
"build": {
"outputs": ["dist/**", "build/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {},
"test": {}
}
}
根 package.json
脚本可改为:
{
"scripts": {
"dev": "turbo run dev --parallel",
"build": "turbo run build",
"lint": "turbo run lint",
"test": "turbo run test"
}
}
版本管理与发包
如果 packages/*
要发布到 npm
:
pnpm dlx changeset init
pnpm changeset # 交互选择要发版的包与版本类型
pnpm changeset version # 写回 package.json
pnpm -r publish --access public
每个可发布包建议加上:
"publishConfig": { "access": "public" }
使用
- 创建子包
# 在 packages 目录下创建 utils 包
pnpm create -f package packages/utils
生成后的 packages/utils/package.json
里需要指定 name
和 version
:
{
"name": "@ds-live-web-sdk/utils",
"version": "1.0.0",
"main": "index.js"
}
- 安装依赖
- 安装全局依赖(根目录)
# 安装开发依赖到根目录(仅在 workspace root 可用)
pnpm add -D typescript eslint prettier
依赖会存放在根目录
package.json
的devDependencies
中,并通过pnpm
的workspace
功能在子包中链接使用。
- 安装子包依赖(在子包目录下运行
pnpm install <pkg>
)
# 给某个子包安装依赖
pnpm add lodash --filter @ds-live-web-sdk/utils
--filter
表示只在指定子包里安装依赖。
- 子包间互相依赖
# utils 被 core 依赖
pnpm add @ds-live-web-sdk/utils --filter @ds-live-web-sdk/core
pnpm
会自动做 软链接(symlink),避免重复安装。
- 运行子包脚本
# 在某个子包里运行脚本
pnpm --filter @my-org/utils dev
# 运行所有子包的测试
pnpm -r test
- 发布包(仅子包)
# 发布某个子包
cd packages/utils
pnpm publish --access public
常见坑
- peerDependencies 未满足:在根或 app 安装 React 等 peer 依赖即可(
auto-install-peers=true
能减少报错)。 - 包互相引用循环或类型没刷:给库包加
tsup
/tsc -b
构建,app 依赖构建产物;或使用paths
+tsup --watch
。 - 路径解析问题:确保
moduleResolution: "Bundler"
(或NodeNext
)与工具一致;x
下通常没问题。 - 过滤器不好使:确认包名与
--filter
值一致(可用pnpm list -r
查看工作区包)。