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

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

什么是 Monorepo?

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

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

项目初始化

1. 创建项目结构

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

bash 复制代码
mkdir monorepo_rollup_tpl
cd monorepo_rollup_tpl
npm init -y

创建基本的目录结构:

bash 复制代码
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:

bash 复制代码
npm install -g pnpm

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

yaml 复制代码
packages:
  - 'packages/*'

TypeScript 配置

1. 根目录 TypeScript 配置

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

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

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

Rollup 打包配置

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

javascript 复制代码
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 转换:

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

包的导出配置

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

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

typescript 复制代码
export default {
  projects: ['<rootDir>/packages/package1', '<rootDir>/packages/package2'],
}

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

javascript 复制代码
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

typescript 复制代码
export default {
  ...require('../../jest.preset.js'),
  displayName: 'package1',
}

代码质量控制

1. ESLint 配置

创建 eslint.config.mjs

javascript 复制代码
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 配置:

json 复制代码
{
  "prettier": {
    "semi": false,
    "singleQuote": true
  }
}

3. 构建脚本

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

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

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. 发布脚本

添加发布相关的脚本:

json 复制代码
{
  "scripts": {
    "changeset": "changeset",
    "version": "changeset version",
    "release": "changeset publish"
  }
}

持续集成 (CI/CD)

1. 测试工作流

创建 .github/workflows/test.yml

yaml 复制代码
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

yaml 复制代码
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

yaml 复制代码
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

依赖安装

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

bash 复制代码
# 安装开发依赖
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 中:

typescript 复制代码
const add = (a: number, b: number) => {
  return a + b
}

const greeting: string = 'Hello, Monorepo!'

export { add, greeting }

2. 添加测试

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

typescript 复制代码
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. 构建和测试

bash 复制代码
# 运行测试
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 模板都能为您的项目提供专业级的开发体验。

相关推荐
weixin_6600967844 分钟前
zsh中使用自动补全zsh-autosuggestions
linux·ubuntu·zsh·zshrc
一念一花一世界2 小时前
Arbess零基础学习 - 使用Arbess+soular实现统一认证登录
ci/cd·arbess·统一登录
用户600071819103 小时前
【翻译】TypeScript中可区分联合类型的省略
typescript
写代码的学渣4 小时前
ubuntu 22.04 新装的系统 xshell 连不上
linux·运维·ubuntu
虚伪的空想家8 小时前
KVM的ubuntu虚机如何关闭安全启动
linux·安全·ubuntu
t1987512813 小时前
在Ubuntu 22.04系统上安装libimobiledevice
linux·运维·ubuntu
晚风吹人醒.14 小时前
缓存中间件Redis安装及功能演示、企业案例
linux·数据库·redis·ubuntu·缓存·中间件
皮小白18 小时前
ubuntu开机检查磁盘失败进入应急模式如何修复
linux·运维·ubuntu
月弦笙音1 天前
【Promise.withResolvers】发现这个api还挺有用
前端·javascript·typescript
sulikey1 天前
Linux基础指令与权限管理深度解析:从入门到精通
linux·运维·服务器·ubuntu·centos·linux命令·linux权限