pnpm+turbo管理monorepo项目

#工程化#monorepo#turbo

需求背景

Q: pnpm通过workspace功能已经可以实现管理monorepo项目,为什么还需要引入turbo一起来管理


A: pnpm的workspace功能确实可以管理monorepo项目,但turbo提供了一些额外的优势

  • 更快的构建速度:
    turbo利用缓存和并行执行来显著加快构建过程。 它可以智能地跳过已经构建过的部分,只构建发生变化的部分。
  • 任务编排:
    turbo提供了强大的任务编排功能,可以定义复杂的任务依赖关系。 这使得管理大型monorepo项目中的构建、测试和部署流程变得更加容易。
  • 远程缓存:
    turbo支持远程缓存,可以在团队成员和CI/CD环境之间共享构建结果。 这进一步提高了整个团队的开发效率。
  • 更好的可视化:
    turbo提供了构建过程的可视化工具,帮助开发者理解和优化构建流程。
  • 与其他工具的集成:
    turbo可以与其他常用的开发工具和CI/CD系统无缝集成。

A: 以上是ai的回答,对我来说有用的并且我用到的就两方面。

  • 构建速度
  • 任务编排

总之,虽然pnpm的workspace功能可以满足基本的monorepo管理需求,但turbo通过提供更高级的功能和优化,可以进一步提高大型项目的开发效率和构建性能。

大纲

本文分为两个部分来介绍如何来实现pnpm+turbo来管理monorepo结构项目

  1. 通过pnpm来管理项目
  2. 引入turbo,和pnpm一起来管理monorepo项目

pnpm管理monorepo

首先我们来搭建一个基础的monorepo模板

  pnpm init

将生成的package.json的private改为true,type改为module

主仓/package.json
{
  "name": "turbo-post",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

创建子仓(pnpm init),并配置pnpm-workspace.yaml,这里拿eslint-config为例(隶属于internal 此时的目录结构

.
 |-internal
 | |-eslint-config
 | | |-package.json
 |-package.json
 |-pnpm-workspace.yaml
pnpm-workspace.yaml
packages:
  - 'internal/*'

eslint直接使用antfu的eslint-config

cd internal/eslint-config
pnpm i -D eslint @antfu/eslint-config
eslint-config/package.json
{
  "name": "@afe1/eslint-config",
  "type": "module",
  "version": "1.0.0",
  "private": true,
  "description": "",
  "author": "",
  "license": "ISC",
  "keywords": [],
  "devDependencies": {
    "@antfu/eslint-config": "^3.6.2",
    "eslint": "^9.10.0"
  }
}

创建eslint.config.mjs配置如下

eslint-config/package.json
// eslint.config.mjs
import antfu from '@antfu/eslint-config'

export default antfu({
  ignores: [
    'index.d.ts',
  ],
})

将”@afe1/eslint-config“添加到主仓/package.json的peerDependencies中

  pnpm i "@afe1/eslint-config@workspace:*" -w  -D

基本工作已完成,接下来我们引入turbo

pnpm+turbo管理monorepo

由于主仓下的eslint.config.mjs,使用的是子仓eslint-config的打包产物,这里我们还需要下载一个打包器这里选择的是unjs的unbild

  pnpm i unbuild turbo -w -D

现在去配置下eslint-config的unbuild配置

build.config.ts
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  clean: true,
  declaration: true,
  entries: ['eslint.config.mjs'],
})

type类型声明

index.d.ts
import type { Linter } from 'eslint';

declare const defineConfig: Linter.Config;
export default defineConfig;

package.json中配置unbuild打包产物引用路径和turbo里面tasks的脚步配置

eslint-config/package.json
{
  "name": "@afe1/eslint-config",
  "type": "module",
  "version": "1.0.0",
  "private": true,
  "description": "",
  "author": "",
  "license": "ISC",
  "keywords": [],
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "import": "./dist/index.mjs"
    }
  },
  "main": "./dist/index.mjs",
  "module": "./dist/index.mjs",
  "types": "./index.d.ts",
  "files": [
    "dist"
  ],
  "scripts": {
    "stub": "pnpm unbuild --stub"
  },
  "devDependencies": {
    "@antfu/eslint-config": "^3.6.2",
    "eslint": "^9.10.0"
  }
}

根目录配置turbo.json 根目录package.json配置packageManager

turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", "dist.zip"]
    },
    "stub": {
      "cache": false
    },
    "dev": {
      "persistent": true,
      "cache": false
    }
  }
}

执行 turbo run stub 这时候会看到eslint-config会生成dist的打包产物

在主仓的eslint.config.mjs中引用

import defineConfig from "@afe1/eslint-config";

export default defineConfig;

这样就实现了turbo和pnpm一起管理monorepo项目

参考