从零开始搭建现代化 Monorepo 开发模板:TypeScript + Rollup + Jest + 持续集成完整指南

发布于:2025-06-11 ⋅ 阅读:(17) ⋅ 点赞:(0)

在现代前端开发中,Monorepo(单体仓库)架构已经成为管理多个相关包的主流方案。无论是 React、Vue、还是 Angular 等知名框架,都采用了 Monorepo 的组织方式。本文将带您从零开始,一步步搭建一个功能完整的 Monorepo 开发模板,涵盖 TypeScript、Rollup 打包、Jest 测试、代码质量控制以及 CI/CD 持续集成等核心功能。

什么是 Monorepo?

Monorepo(Monolithic Repository)是一种代码组织策略,将多个相关的项目或包存储在同一个 Git 仓库中。与传统的多仓库(Multi-repo)相比,Monorepo 具有以下优势:

  • 统一依赖管理:共享相同的依赖版本,避免版本冲突
  • 简化跨包开发:可以同时修改多个包并保持同步
  • 统一工具链:使用相同的构建、测试、代码质量工具
  • 原子化提交:相关改动可以在一次提交中完成
  • 更好的代码重用:包之间可以更容易地共享代码

项目初始化

1. 创建项目结构

首先创建项目根目录并初始化:

mkdir monorepo_rollup_tpl
cd monorepo_rollup_tpl
npm init -y

创建基本的目录结构:

mkdir -p packages/package1/src packages/package1/__tests__
mkdir -p packages/package2/src packages/package2/__tests__
mkdir -p .github/workflows
mkdir .changeset

最终的项目结构如下:

monorepo_rollup_tpl/
├── packages/
│   ├── package1/
│   │   ├── src/
│   │   ├── __tests__/
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── jest.config.ts
│   └── package2/
│       ├── src/
│       ├── __tests__/
│       ├── package.json
│       ├── tsconfig.json
│       └── jest.config.ts
├── .github/
│   └── workflows/
├── .changeset/
├── package.json
├── tsconfig.json
├── rollup.config.js
├── jest.config.ts
├── babel.config.js
└── eslint.config.mjs

2. 配置包管理器

本项目使用 pnpm 作为包管理器,它对 Monorepo 有原生支持。安装 pnpm:

npm install -g pnpm

创建 pnpm-workspace.yaml 文件来定义工作空间:

packages:
  - 'packages/*'

TypeScript 配置

1. 根目录 TypeScript 配置

在根目录创建 tsconfig.json,此处列出主要配置:

{
  "compilerOptions": {
    "target": "esnext",
    "jsx": "react-jsx",
    "module": "esnext",
    "moduleResolution": "bundler",
    "noEmit": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

关键配置说明:

  • "noEmit": true:只做类型检查,不输出编译结果,编译交给 Rollup 处理
  • "moduleResolution": "bundler":使用打包器的模块解析策略
  • "jsx": "react-jsx":支持现代的 JSX 转换

2. 子包 TypeScript 配置

在每个子包目录下创建 tsconfig.json

{
  "extends": "../../tsconfig.json", // 继承根目录配置文件
  "compilerOptions": {
    // 编译选项
    "paths": {
      // 设置应用别名
      "package1": ["./src/index.ts"],
      "package1/*": ["./src/*.ts"]
    }
  },
  "include": ["src"] // 限定类型检查范围
}

Rollup 打包配置

Rollup 是一个专为库打包设计的工具,支持输出多种模块格式。创建 rollup.config.js

const createBabelConfig = require('./babel.config.js')
const resolve = require('@rollup/plugin-node-resolve')
const babelPlugin = require('@rollup/plugin-babel')
const commonjs = require('@rollup/plugin-commonjs')
const { dts } = require('rollup-plugin-dts')

const extensions = ['.ts', '.tsx']

const getBabelOptions = () => {
  return {
    ...createBabelConfig,
    extensions,
    babelHelpers: 'bundled',
    comments: false,
  }
}

// TypeScript 声明文件配置
function createDeclarationConfig(input, output) {
  return {
    input,
    output: {
      file: output,
      format: 'esm',
    },
    plugins: [dts()],
  }
}

// ESM 格式配置
function createESMConfig(input, output) {
  return {
    input,
    output: {
      file: output,
      format: 'esm',
    },
    plugins: [
      resolve({ extensions }),
      commonjs(),
      babelPlugin(getBabelOptions()),
    ],
  }
}

// CommonJS 格式配置
function createCJSConfig(input, output) {
  return {
    input,
    output: {
      file: output,
      format: 'cjs',
    },
    plugins: [
      resolve({ extensions }),
      commonjs(),
      babelPlugin(getBabelOptions()),
    ],
  }
}

// UMD 格式配置
function createUMDConfig(input, output, name) {
  return {
    input,
    output: {
      file: output,
      format: 'umd',
      name,
    },
    plugins: [
      resolve({ extensions }),
      commonjs(),
      babelPlugin(getBabelOptions()),
    ],
  }
}

module.exports = (args) => {
  const packageName = process.env.PACKAGE
  const input = `packages/${packageName}/src/index.ts`
  const output = `packages/${packageName}/dist`

  return [
    createDeclarationConfig(input, `${output}/index.d.ts`),
    createESMConfig(input, `${output}/index.mjs`),
    createCJSConfig(input, `${output}/index.cjs`),
    createUMDConfig(input, `${output}/index.umd.js`, packageName),
  ]
}

Babel 配置

创建 babel.config.js 来配置 Babel 转换:

module.exports = {
  presets: [['@babel/preset-env', { targets: { node: 14 } }]],
  plugins: [
    '@babel/plugin-transform-typescript',
    '@babel/plugin-transform-react-jsx',
  ],
}

包的导出配置

在每个子包的 package.json 中配置导出字段:

{
  "name": "package1",
  "version": "1.0.0",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.mjs"
      },
      "require": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.cjs"
      }
    }
  },
  "scripts": {
    "typecheck": "tsc"
  }
}

Jest 测试配置

1. 根目录 Jest 配置

创建 jest.config.ts

export default {
  projects: ['<rootDir>/packages/package1', '<rootDir>/packages/package2'],
}

创建 jest.preset.js 作为共享配置:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
  testMatch: ['**/__tests__/**/*.(test|spec).(ts|tsx|js)'],
  collectCoverageFrom: ['src/**/*.(ts|tsx)', '!src/**/*.d.ts'],
}

2. 子包 Jest 配置

在每个子包中创建 jest.config.ts

export default {
  ...require('../../jest.preset.js'),
  displayName: 'package1',
}

代码质量控制

1. ESLint 配置

创建 eslint.config.mjs

import eslint from '@eslint/js'
import tseslint from '@typescript-eslint/eslint-plugin'
import tsParser from '@typescript-eslint/parser'
import prettierConfig from 'eslint-config-prettier'

export default [
  eslint.configs.recommended,
  {
    files: ['**/*.{js,jsx,ts,tsx}'],
    languageOptions: {
      parser: tsParser,
      parserOptions: {
        ecmaVersion: 'latest',
        sourceType: 'module',
        ecmaFeatures: {
          jsx: true,
        },
      },
    },
    plugins: {
      '@typescript-eslint': tseslint,
    },
    rules: {
      '@typescript-eslint/no-unused-vars': 'error',
      '@typescript-eslint/no-explicit-any': 'warn',
    },
  },
  prettierConfig,
]

2. Prettier 配置

package.json 中添加 Prettier 配置:

{
  "prettier": {
    "semi": false,
    "singleQuote": true
  }
}

3. 构建脚本

在根目录 package.json 中添加构建和质量检查脚本:

{
  "scripts": {
    "test": "jest --passWithNoTests --config jest.config.ts",
    "eslint": "eslint --fix '**/src/*.{js,jsx,ts,tsx}'",
    "eslint:ci": "eslint '**/src/*.{js,jsx,ts,tsx}'",
    "prettier": "prettier '**/{src,__tests__}/**/*.{js,jsx,ts,tsx,md}' --write",
    "prettier:ci": "prettier '**/{src,__tests__}/**/*.{js,jsx,ts,tsx,md}' --list-different",
    "typecheck": "pnpm -r --parallel run typecheck",
    "build": "concurrently 'pnpm:build:*'",
    "build:package1": "rollup -c --environment PACKAGE:package1",
    "build:package2": "rollup -c --environment PACKAGE:package2"
  }
}

版本管理和发布

1. Changesets 配置

Changesets 是一个用于管理版本和 changelog 的工具。创建 .changeset/config.json

{
  "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
  "changelog": [
    "@changesets/changelog-github",
    { "repo": "your-username/monorepo_rollup_tpl" }
  ],
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}

2. 发布脚本

添加发布相关的脚本:

{
  "scripts": {
    "changeset": "changeset",
    "version": "changeset version",
    "release": "changeset publish"
  }
}

持续集成 (CI/CD)

1. 测试工作流

创建 .github/workflows/test.yml

name: ci

on:
  push:
    branches: [main]
  pull_request:
    types: [opened, synchronize]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 9

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile --prefer-offline

      - name: Run Tests
        run: pnpm run test

2. 代码质量检查工作流

创建 .github/workflows/lint-and-type.yml

name: Lint and Type Check

on:
  push:
    branches: [main]
  pull_request:
    types: [opened, synchronize]

jobs:
  lint-and-type:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 9

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Lint check
        run: pnpm run eslint:ci

      - name: Prettier check
        run: pnpm run prettier:ci

      - name: Type check
        run: pnpm run typecheck

3. 发布工作流

创建 .github/workflows/release.yml

name: Release

on:
  push:
    branches: [main]

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 9

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Create Release Pull Request or Publish
        id: changesets
        uses: changesets/action@v1
        with:
          publish: pnpm run release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

GITHUB_TOKEN和NPM_TOKEN生成:详见link

依赖安装

安装项目所需的所有依赖:

# 安装开发依赖
pnpm add -Dw @babel/core @babel/plugin-transform-react-jsx @babel/plugin-transform-typescript @babel/preset-env @changesets/changelog-github @changesets/cli @eslint/compat @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve @types/jest @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser concurrently eslint eslint-config-prettier eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks globals jest jest-environment-jsdom prettier rollup rollup-plugin-dts ts-jest ts-node typescript

使用示例

1. 创建包内容

packages/package1/src/index.ts 中:

const add = (a: number, b: number) => {
  return a + b
}

const greeting: string = 'Hello, Monorepo!'

export { add, greeting }

2. 添加测试

packages/package1/__tests__/index.test.ts 中:

import { add, greeting } from '../src'

describe('package1', () => {
  test('add function', () => {
    expect(add(1, 2)).toBe(3)
  })

  test('greeting export', () => {
    expect(greeting).toBe('Hello, Monorepo!')
  })
})

3. 构建和测试

# 运行测试
pnpm test

# 类型检查
pnpm typecheck

# 代码格式化
pnpm prettier

# 代码质量检查
pnpm eslint

# 构建所有包
pnpm build

最佳实践和总结

1. 命名约定

  • 包名使用 kebab-case
  • 文件名使用 camelCase 或 kebab-case
  • 导出的函数和变量使用 camelCase

2. 版本管理

  • 使用 Changesets 管理版本和 changelog
  • 遵循语义化版本规范
  • 每次发布前确保所有测试通过

3. 代码质量

  • 配置 ESLint 和 Prettier 保证代码一致性
  • 使用 TypeScript 提供类型安全
  • 保持高测试覆盖率

4. 持续集成

  • 自动化测试、代码质量检查
  • 自动化发布流程
  • 使用缓存优化 CI 速度

总结

本文介绍了如何从零开始搭建一个现代化的 Monorepo 开发模板,涵盖了:

  1. 项目结构设计:合理的目录组织和包管理
  2. TypeScript 配置:类型安全和开发体验
  3. Rollup 打包:支持多种模块格式的库打包
  4. Jest 测试:完整的测试解决方案
  5. 代码质量控制:ESLint + Prettier + TypeScript
  6. 版本管理:Changesets 自动化版本和发布
  7. 持续集成:GitHub Actions 自动化流程

这个模板为开发多包项目提供了坚实的基础,可以根据具体需求进行扩展和定制。通过统一的工具链和自动化流程,大大提高了开发效率和代码质量。

无论是开发组件库、工具库还是应用集合,这个 Monorepo 模板都能为您的项目提供专业级的开发体验。