为什么使用monorepo?
聊到为什么使用 monorepo 那就要先了解不使用 monorepo 的痛点和使用 monorepo 的优点
痛点1:公共代码重复 or 发布频繁
方案A: 复制粘贴,把 A 项目代码粘贴到 B 项目等等,只要有一处改动其它的地方也要进行改动
方案B: 拆成独立 NPM 包
- 每次修改都要 改代码->打包->发版->各项目更新依赖
- 调试困难:本地改了 utils,要发 beta 版才能在 web-app 里试
痛点2:跨项目原子提交难以实现
你想同时:
- 在 utils 中 新增一个 formatDate 函数
- 在 web-app 和 mobile-app 中调用它
在 Multi-repo 下:
- 必须分 3 次提交 + 3 次 PR
- 如果中间某步失败,状态不一致
痛点3:工具链碎片化
- 每个项目有自己的 ESLint / Prettier / TypeScript 配置
- 团队规范难以统一,新人上手成本高
优点1:天然支持跨项目引用(无需发布)
通过 pnpm workspace
less
// apps/web-app/package.json
{
"dependencies": {
"@my-org/utils": "workspace:*"
}
}
**workspace: *** 代表直接链接到本地 packages/utils 目录
修改 utils 中的内容,web-app 项目中立即生效(无需发版)
调试时与在同一个项目中一样
优点2:原子提交:一次PR,多项目协同变更
一次提交,完整闭环。
Code Review 时能看到全貌,避免"改了 utils 但没人用"的问题。
优点3:统一工程化配置
根目录下可以配置统一的 tsconfig.ts、eslintrc、prettierrc
所有的子项目可以继承,强制统一规范
升级 TypeScript ,改一个地方,全部 repo 生效
优点4:高效构建与缓存(需配置 Turborepo / Nx)
第一次构建:按顺序 utils -> web-app -> mobile-app
第二次修改:只修改 web-app 中的内容,只构建web-app ,无需构建 utils ,直接命中缓存的 utils
CI时间大幅度缩减
所以:当协作成本 > 仓库管理成本时,Monorepo 就赢了。
典型的 monorepo 项目结构
perl
my-monorepo/
├── apps/
│ ├── web-app/ # 前端应用
│ └── mobile-app/ # 移动端
├── packages/
│ ├── shared-utils/ # 工具函数
│ ├── ui-components/ # 公共组件库(React/Vue)
│ └── api-types/ # API 类型定义(TS interface)
├── package.json # 定义 workspaces
└── pnpm-workspace.yaml # pnpm 工作区声明
根目录:pnpm-workspace.yaml
markdown
packages:
- apps/*
- packages/*
根目录:package.json
css
{
workspace: ["apps/*", "packages/*"]
}
pnpm 会自动将 packages链接到 node_modules中,实现本地 "npm包"
注意: 在开发过程中,如果软链失效,可以通过 pnpm i --force 强制从新安装,链接软链
现在就可以搭建一个 monorepo 的项目了
第一步:创建一个目录
使用下方命令进行项目初始化
csharp
pnpm init
第二步:创建项目目录和公共方法目录
在根目录创建一个 apps 的目录:这个目录下,放的是你的应用
在根目录创建一个 packages 的目录,这个目录下,放的是你的公共方法
增加 pnpm-workspace.yaml 管理项目
markdown
packages:
- apps/*
- packages/*
所以现在的目录结构会变为
go
my-monorepo/
├── apps/
│ ├── web-app/
│ └── mobile-app/
├── packages/
│ ├── shared-utils/
│ ├── ui-components/
│ └── api-types/
├── package.json
└── pnpm-workspace.yaml
第三步:给子项目的package.json中引入公共依赖
子项目 web-app 和 moblie-app 的package.json增加下面内容
scss
{
"devDependencies": {
"@shared-utils": "workspace:*",
"@ui-components": "workspace:*",
"@api-types": "workspace:*"
}
}
这样一个最基本的 monorepo 结构的项目就搭建好了。
如果子项目使用的是 vite ,需要修改一下配置
增加 alias , 在开发环境下,绕过构建产物,直接引用源码( src ),实现"热更新 + 实时调试"公共包。
javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path';
// https://vite.dev/config/
export default defineConfig(({ mode }) => {
const isDev = mode === 'development';
return {
plugins: [react()],
resolve: {
alias: isDev ? {
'@ui-components': resolve(import.meta.dirname, '../../packages/ui-components/src'), // 开发环境使用软连接
} : undefined,
},
}
})
为刚搭建好的 monorepo 项目增加工程化配置
现在我们的项目已经搭建好了,大致的目录结构为

第一步:先增加 TS 配置
根目录创建 tsconfig.json
内容根据自己项目的公共 ts 配置进行配置
json
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
}
}
子项目 web-app、mobile-app 的 tsconifg.json
json
{
"extends": "../../tsconfig.json",
"files": [],
"include": ["src", "vite.config.ts"]
}
安装 typescript
csharp
pnpm add -D -w typescript
根目录 package.json
json
{
"scripts": {
"check-types": "tsc -b"
},
}
通过命令 pnpm check-types 就可以检查整个 monorepo 项目的代码
第二步:增加 eslint 配置
安装 eslint 的依赖
sql
pnpm add -D -w @eslint/js
@typescript-eslint/eslint-plugin
@typescript-eslint/parser
eslint
eslint-config-prettier
eslint-plugin-react-hooks
eslint-plugin-react-refresh
根目录创建 eslint.config.cjs
php
// eslint.config.cjs
const eslintJs = require('@eslint/js');
const parser = require('@typescript-eslint/parser');
const plugin = require('@typescript-eslint/eslint-plugin');
const reactHooks = require('eslint-plugin-react-hooks');
const reactRefresh = require('eslint-plugin-react-refresh');
const eslintConfigPrettier = require('eslint-config-prettier');
const globals = require('globals');
// 手动构造 @typescript-eslint/recommended 的等效配置
const tsRecommended = {
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module'
}
},
plugins: {
'@typescript-eslint': plugin
},
rules: {
// 来自 @typescript-eslint/eslint-plugin 的 recommended 规则
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-var-requires': 'error',
'@typescript-eslint/no-empty-function': 'warn',
// 可根据需要扩展更多...
}
};
/** @type {import('eslint').Linter.FlatConfig[]} */
module.exports = [
{
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/*.d.ts']
},
// 基础 JS 规则
eslintJs.configs.recommended,
// TypeScript 规则(手动定义)
tsRecommended,
// React 规则
{
files: ['**/*.{js,jsx,ts,tsx}'],
languageOptions: {
globals: {
...globals.browser
}
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh
},
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'react-refresh/only-export-components': 'warn'
}
},
// Prettier 兼容(最后)
eslintConfigPrettier
];
我的项目用了 globals 依赖包 所以也需要安装一下
csharp
pnpm add -D -w globals
globals包提供了常见环境的全局变量列表,例如:
-
globals.browser→window,document,fetch...globals.node→process,require,__dirname...globals.jest→describe,test,expect...
ESLint 需要这些信息来判断某个变量是"未声明"还是"环境全局"。
根目录的 package.json 中增加 scripts
json
{
"scripts": {
"check-types": "tsc -b",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix"
},
}
第三步:增加 git commit 钩子函数
如果要使用 git 钩子,那就要安装一些依赖
csharp
pnpm add -D -w husky lint-staged
根目录的 package.json 中增加配置
json
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix"]
}
如果想要钩子运行起来还需要在根目录增加.husky文件夹,文件夹下创建 pre-commit 文件,使用下面命令进行创建
这个是 husky 官方提供的命令
bash
pnpm exec husky install
这个命令不会在 .husky 下生成 pre-commit 需要自己手动创建,内容为
这个是 husky v9版本以下写法
bash
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm lint-staged
v9 以上写法
pnpm lint-staged
然后就可以通过以下命令进行测试了
csharp
git init
git add .
git commit -m "改动的msg"
当出现这种就算成功了
