Мурад поправил одну строчку в web, а проверки в CI снова собирают и тестируют всё подряд: shared, ui, api, web. Девять минут ожидания ради строчки, которая не затрагивает ни api, ни shared. Менеджер пакетов здесь не поможет: он ставит и связывает пакеты, но не помнит, что уже было сделано. Для этого существуют оркестраторы задач, и самый распространённый среди них — Turborepo. В этом уроке вы настроите его, поймёте граф задач и кеш и увидите, как из девяти минут получаются секунды.

Зачем нужен оркестратор задач

Команда pnpm -r run build уже идёт от зависимостей к зависимым, но у такого запуска три слабых места.

  1. Нет памяти. Исходники не менялись, а build всё равно выполняется заново.
  2. Всё или ничего. Нельзя сказать «проверь только то, что затронуто моим изменением».
  3. Результат не делится. Что собрал ваш компьютер, не получит ни коллега, ни CI.

Turborepo — быстрая программа на Rust, которая запускает скрипты из package.json по графу зависимостей, распараллеливает всё, что можно, и запоминает результаты. Он не заменяет pnpm: pnpm ставит зависимости и связывает пакеты, а Turborepo запускает задачи. Ставится он один раз, в корень рабочей области. Сведения о менеджере пакетов он берёт из поля packageManager корневого package.json:

bash
pnpm add -D -w turbo
pnpm exec turbo --version

В корневой package.json добавьте скрипты-обёртки, а папку .turbo внесите в .gitignore — там Turborepo хранит кеш и логи:

json
{
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "check-types": "turbo run check-types"
  },
  "devDependencies": {
    "turbo": "^2.11.0"
  }
}

💡 В pnpm 12 порядок задач можно описать и без Turborepo, настройкой tasks, а кеш есть у экспериментальной команды pnpm pipeline. Turborepo остаётся самым распространённым выбором, с готовым удалённым кешем и богатым набором команд.

Два графа: пакетов и задач

Граф пакетов строится сам: если в apps/web/package.json записано "@repo/shared": "workspace:*", то web зависит от shared. Граф задач вы описываете в turbo.json: какая задача должна дождаться какой.

json
{
  "$schema": "https://turborepo.dev/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**", "!.next/dev/**"]
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "check-types": {
      "dependsOn": ["^check-types"]
    },
    "dev": {
      "dependsOn": ["^build"],
      "cache": false,
      "persistent": true
    }
  }
}

Каждая задача в tasks связана со скриптами с тем же именем в package.json пакетов. Главные ключи: