在一个大型 Vue Monorepo 项目中,如何利用 `pnpm` 或 `Turborepo` 等工具,管理依赖、构建和部署流程?

各位好,今天咱们来聊聊大型 Vue Monorepo 项目里的那些事儿,特别是怎么用 pnpmTurborepo 这两把刷子来管理依赖、构建和部署流程。 保证听完之后,你的项目也能像整理过的房间一样,井井有条!

Monorepo 是个啥?为啥要用它?

首先,咱们得先搞清楚 Monorepo 是个什么玩意儿。简单来说,就是把多个项目(或者说 Package)的代码都放在同一个代码仓库里。

那为啥要用 Monorepo 呢?好处多多:

  • 代码复用更容易: 多个项目之间共享代码,不用到处复制粘贴,减少重复代码。
  • 依赖管理更简单: 统一管理依赖,避免版本冲突,升级依赖也更方便。
  • 原子性变更: 修改一个公共库,所有依赖它的项目都可以同步更新,保证一致性。
  • 协作更高效: 团队成员更容易了解整个项目,促进协作。

当然,Monorepo 也有缺点:

  • 仓库体积大: 所有代码都在一起,仓库体积可能会比较大。
  • 构建时间长: 构建整个仓库可能需要比较长的时间。
  • 权限管理复杂: 需要更精细的权限管理,防止误操作。

不过,有了 pnpmTurborepo,这些缺点都可以得到很好的解决。

pnpm:高效的依赖管理器

pnpm 是一个快速、节省磁盘空间的 Node.js 包管理器。它最大的特点就是使用硬链接符号链接来共享依赖,避免重复安装,大大节省磁盘空间。

pnpm 的优势:

  • 节省磁盘空间: 避免重复安装依赖,节省磁盘空间。
  • 安装速度快: 利用缓存和硬链接,安装速度更快。
  • 更强的安全性: 默认开启严格模式,避免幽灵依赖。
  • 支持 Monorepo: 内置支持 Monorepo,方便管理多个项目。

如何在 Monorepo 中使用 pnpm

  1. 安装 pnpm

    npm install -g pnpm
  2. 初始化 Monorepo:

    在项目根目录下创建一个 pnpm-workspace.yaml 文件,声明 Monorepo 的结构。

    packages:
      - 'packages/*'  # 所有在 packages 目录下的项目
      - 'apps/*'      # 所有在 apps 目录下的项目

    这个文件告诉 pnpm,哪些目录是独立的 Package。

  3. 安装依赖:

    在项目根目录下运行 pnpm installpnpm 会根据 pnpm-workspace.yaml 文件,为每个 Package 安装依赖。

  4. 添加/删除依赖:

    • 为单个 Package 添加依赖:

      pnpm add <package-name> -w  # -w 代表在根目录下添加
      pnpm add <package-name> -P  # -P 添加到生产依赖中
      pnpm add <package-name> -D  # -D 添加到开发依赖中
      pnpm add <package-name> --filter <package-name> # 只给指定package安装依赖
    • 为所有 Package 添加依赖:

      pnpm add <package-name> -r  # -r 代表递归地添加到所有 Package 中
  5. 运行脚本:

    • 运行单个 Package 的脚本:

      pnpm --filter <package-name> <script-name>
    • 运行所有 Package 的脚本:

      pnpm run <script-name> -r

Turborepo:智能的构建工具

Turborepo 是一个高性能的构建工具,专门为 Monorepo 设计。它利用缓存并行构建,大大缩短构建时间。

Turborepo 的优势:

  • 增量构建: 只构建发生变化的项目,避免重复构建。
  • 并行构建: 同时构建多个项目,提高构建效率。
  • 缓存: 缓存构建结果,下次构建时直接使用缓存,避免重复构建。
  • 依赖分析: 自动分析项目之间的依赖关系,确定构建顺序。

如何在 Monorepo 中使用 Turborepo

  1. 安装 Turborepo

    npm install -g turbo
  2. 初始化 Turborepo

    在项目根目录下运行 turbo initTurborepo 会自动创建 turbo.json 文件。

  3. 配置 turbo.json

    turbo.json 文件是 Turborepo 的配置文件,用于定义构建任务、依赖关系和缓存策略。

    {
      "$schema": "https://turbo.build/schema.json",
      "pipeline": {
        "build": {
          "dependsOn": ["^build"], // 依赖其他 package 的 build 任务
          "outputs": ["dist/**", "build/**", ".next/**"], // 构建输出的文件
          "cache": {
            "glob": ["**/*"],
            "hash": ["package.json", "tsconfig.json", "src/**/*"]
          }
        },
        "test": {
          "dependsOn": ["build"], // 依赖 build 任务
          "inputs": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts", "test/**/*.tsx"],
          "cache": {
            "glob": ["coverage/**"]
          }
        },
        "lint": {},
        "dev": {
          "cache": false, // 开发环境不缓存
          "persistent": true // 在后台运行
        }
      }
    }
    • pipeline:定义构建任务的流程。
    • dependsOn:定义任务之间的依赖关系。^ 表示依赖所有依赖的 Package 的任务。
    • outputs:定义任务的输出文件,用于缓存。
    • cache:配置缓存策略。
    • inputs: 定义任务的输入文件,用于检测文件是否变化。
    • persistent: 定义任务是否在后台运行。
  4. 运行构建任务:

    turbo run build # 构建所有项目
    turbo run test  # 测试所有项目
    turbo run lint  # lint所有项目
    turbo run dev   # 运行所有项目的dev任务

    Turborepo 会根据 turbo.json 文件的配置,自动分析项目之间的依赖关系,并并行构建。

pnpm + Turborepo:最佳拍档

pnpmTurborepo 搭配使用,可以发挥更大的威力。pnpm 负责依赖管理,Turborepo 负责构建。两者配合,可以大大提高 Monorepo 项目的开发效率。

一个完整的示例

假设我们有一个 Monorepo 项目,包含以下几个 Package:

  • packages/ui:UI 组件库
  • packages/utils:工具函数库
  • apps/web:Web 应用
  • apps/mobile:移动应用
  1. 创建项目结构:

    monorepo-example/
    ├── pnpm-workspace.yaml
    ├── turbo.json
    ├── packages/
    │   ├── ui/
    │   │   ├── package.json
    │   │   └── src/
    │   ├── utils/
    │   │   ├── package.json
    │   │   └── src/
    ├── apps/
    │   ├── web/
    │   │   ├── package.json
    │   │   └── src/
    │   ├── mobile/
    │   │   ├── package.json
    │   │   └── src/
  2. pnpm-workspace.yaml

    packages:
      - 'packages/*'
      - 'apps/*'
  3. turbo.json

    {
      "$schema": "https://turbo.build/schema.json",
      "pipeline": {
        "build": {
          "dependsOn": ["^build"],
          "outputs": ["dist/**", "build/**", ".next/**"],
          "cache": {
            "glob": ["**/*"],
            "hash": ["package.json", "tsconfig.json", "src/**/*"]
          }
        },
        "test": {
          "dependsOn": ["build"],
          "inputs": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts", "test/**/*.tsx"],
          "cache": {
            "glob": ["coverage/**"]
          }
        },
        "lint": {},
        "dev": {
          "cache": false,
          "persistent": true
        }
      }
    }
  4. package.json (根目录):

    {
      "name": "monorepo-example",
      "private": true,
      "scripts": {
        "build": "turbo run build",
        "test": "turbo run test",
        "lint": "turbo run lint",
        "dev": "turbo run dev"
      },
      "devDependencies": {
        "turbo": "^1.10.16"
      }
    }
  5. package.json (packages/ui):

    {
      "name": "@monorepo-example/ui",
      "version": "0.0.1",
      "scripts": {
        "build": "vue-cli-service build",
        "test": "jest"
      },
      "dependencies": {
        "@monorepo-example/utils": "workspace:*"
      },
       "devDependencies": {
        "@vue/cli-service": "^5.0.0",
        "jest": "^29.0.0"
      }
    }
  6. package.json (packages/utils):

    {
      "name": "@monorepo-example/utils",
      "version": "0.0.1",
      "scripts": {
        "build": "tsc",
        "test": "jest"
      },
      "devDependencies": {
        "typescript": "^5.0.0",
        "jest": "^29.0.0"
      }
    }
  7. package.json (apps/web):

    {
      "name": "@monorepo-example/web",
      "version": "0.0.1",
      "scripts": {
        "dev": "vue-cli-service serve",
        "build": "vue-cli-service build",
        "test": "jest"
      },
      "dependencies": {
        "@monorepo-example/ui": "workspace:*"
      },
       "devDependencies": {
        "@vue/cli-service": "^5.0.0",
        "jest": "^29.0.0"
      }
    }
  8. package.json (apps/mobile):

    {
      "name": "@monorepo-example/mobile",
      "version": "0.0.1",
      "scripts": {
        "dev": "react-native start",
        "build": "react-native build",
        "test": "jest"
      },
      "dependencies": {
        "@monorepo-example/ui": "workspace:*"
      },
       "devDependencies": {
        "react-native": "^0.72.0",
        "jest": "^29.0.0"
      }
    }

    注意:workspace:* 表示依赖本地的 Package。

  9. 运行构建:

    pnpm install # 安装依赖
    pnpm run build # 构建所有项目

    Turborepo 会自动分析项目之间的依赖关系,先构建 packages/utils,然后构建 packages/ui,最后构建 apps/webapps/mobile

部署流程

部署流程和普通的项目没有太大的区别。你可以使用 Docker、Kubernetes 等工具来部署 Monorepo 项目。

  • 构建镜像: 为每个应用构建 Docker 镜像。
  • 部署应用: 将 Docker 镜像部署到 Kubernetes 集群或其他云平台上。

总结

pnpmTurborepo 是管理大型 Vue Monorepo 项目的利器。pnpm 负责依赖管理,Turborepo 负责构建。两者配合,可以大大提高开发效率,让你的项目更加井井有条。

希望今天的讲座对你有所帮助! 祝大家的项目都能告别“脏乱差”,走向“高大上”!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注