搭建 monorepo 项目

为什么使用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"

当出现这种就算成功了

相关推荐
福兮说1 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理2 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆6662 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
凤城老人3 小时前
从 PyQt6 到 Electron:给 Edge TTS 做一个“多角色配音机“的踩坑手记
javascript·typescript·electron
Dovis(誓平步青云)3 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan3 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_4 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒4 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技4 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu5 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub