高效配置 TypeScript Monorepo 使用 pnpm Workspace(二十六)

在现代前端开发中,Monorepo 成为了越来越受欢迎的项目管理方式。通过将多个相关项目放在同一个代码仓库中,Monorepo 能够简化代码共享、版本管理和依赖协调。本文将详细介绍如何使用 TypeScript 和 pnpm Workspace 配置一个高效的 Monorepo 项目。

什么是 Monorepo?

Monorepo(单一仓库)是一种将多个相关项目放在同一个代码仓库中的开发模式。与传统的多仓库模式相比,Monorepo 有以下几个优点:

  • 代码共享:多个项目可以轻松共享代码和资源。
  • 版本管理:所有项目共享同一个版本控制历史,便于追踪和回滚。
  • 依赖管理:依赖关系更加清晰,减少重复配置。
  • 构建效率:通过项目引用和增量编译,提高构建速度。

为什么选择 pnpm Workspace?

pnpm 是一个现代的包管理器,原生支持 Workspace 功能。Workspace 允许你在同一个仓库中管理多个包,并且能够自动处理包之间的依赖关系。以下是 pnpm Workspace 的一些关键特性:

  • 自动链接:pnpm 会自动将 packages 目录下的包链接在一起。
  • 递归脚本:可以通过一个命令递归执行所有包的同名脚本。
  • 性能优化:pnpm 在安装依赖时会进行缓存,提高安装速度。

项目结构

一个典型的 Monorepo 项目结构如下:

my-monorepo/
├── packages/
│   ├── utils/
│   │   ├── src/
│   │   │   └── index.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── ui-components/
│   │   ├── src/
│   │   │   ├── Button.tsx
│   │   │   └── index.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── app/
│       ├── src/
│       │   └── index.tsx
│       ├── package.json
│       └── tsconfig.json
├── package.json
├── tsconfig.base.json
└── pnpm-workspace.yaml

根目录 package.json

根目录的 package.json 文件用于配置整个 Monorepo 的基本信息和脚本。以下是示例配置:

{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "build": "pnpm -r run build",
    "clean": "pnpm -r run clean",
    "type-check": "pnpm -r run type-check",
    "test": "pnpm -r run test",
    "build:watch": "pnpm -r --parallel run build:watch",
    "dev": "pnpm --filter @my-org/app run dev"
  }
}

基础 TypeScript 配置

为了确保所有包使用一致的 TypeScript 配置,我们在根目录下创建一个 tsconfig.base.json 文件。这个文件包含了通用的编译器选项:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true
  }
}

工具包配置

每个包都有自己的 tsconfig.json 文件,继承基础配置并覆盖特定选项。以下是 packages/utils/tsconfig.json 的示例配置:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "declarationDir": "./dist/types",
    "declaration": true,
    "declarationMap": true,
    "module": "ESNext",
    "composite": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
}

应用程序配置

主应用程序的 tsconfig.json 文件同样继承基础配置,并添加路径别名和项目引用。以下是 packages/app/tsconfig.json 的示例配置:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "@my-utils/*": ["../utils/src/*"],
      "@my-ui/*": ["../ui-components/src/*"]
    }
  },
  "references": [
    { "path": "../utils" },
    { "path": "../ui-components" }
  ],
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

包之间的依赖

在 package.json 中声明对同仓库包的依赖。以下是 packages/app/package.json 的示例配置:


{
  "name": "@my-org/app",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "@my-org/utils": "workspace:*",
    "@my-org/ui-components": "workspace:*",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.0",
    "typescript": "^5.0.0"
  }
}

构建脚本

在根目录的 package.json 中定义统一的构建脚本,以便一次性构建所有包。以下是示例脚本:

{
  "scripts": {
    "build": "pnpm -r run build",
    "clean": "pnpm -r run clean",
    "type-check": "pnpm -r run type-check",
    "test": "pnpm -r run test",
    "build:watch": "pnpm -r --parallel run build:watch",
    "dev": "pnpm --filter @my-org/app run dev"
  }
}

注意事项

  • 包命名规范:使用 @org-name/package 格式。
  • 独立版本:每个包可以独立版本管理。
  • workspace 协议:使用 workspace:* 引用同仓库包。
  • 构建顺序:被依赖的包需要先构建。

总结

Monorepo 是现代前端项目管理的推荐模式。通过使用 pnpm Workspace 和 TypeScript 的项目引用功能,我们可以高效地管理多个相关项目。以下是一些关键点:

  • pnpm Workspace:原生支持 Monorepo,自动处理包依赖。
  • 项目引用:实现增量编译,提高构建效率。
  • 路径别名:便捷引用同仓库包。
  • 统一管理:共享配置和依赖,简化项目管理。

希望本文能帮助你更好地理解和配置 Monorepo 项目。如果你的项目包含多个相关包,强烈建议考虑采用 Monorepo 方案。