Галоўная / Артыкулы / Пасэпэйкаванне прамыслов pnpm і Turborepo для монарепаў у прыкладнах Node.js, шаг за шагам

Пасэпэйкаванне прамыслов pnpm і Turborepo для монарепаў у прыкладнах Node.js, шаг за шагам

Створыце манорэпа на TypeScript з порожней папкі за допамогай пакета pnpm workspaces і Turborepo, а потым запускайце, будуйце і фільтруйце задачы для веб-дзялоў, API і спакетаў, якія дзелююцца між сабой.

1702 слоў

Калі продукт уже мае фронтэнд, бэкэнд і неабходны код, аддзельныя репазітарыі пачына ствараць проблемы: спяшаныя типы, настройкі копіююцца вручную, а адна змянка паўтараецца ў калькі прызначэнных для згуртавання змян. pnpm workspaces плюс Turborepo рашаюць гэтыя проблемы без неабходнасці злучання проектаў у адны. У гэтым карыце мы пройдземся ад порожньяй дырэктарыі да двух праектаў на TypeScript і аднаго спяльванага пакета, які можна развіваць, складваць і фільтруваць з адной корневай дырэктарыі.

Готавыя структуры:

my-monorepo/
├── apps/
│   ├── web/
│   └── api/
│
├── packages/
│   ├── types/
│   └── eslint-config/
│
├── package.json
├── pnpm-workspace.yaml
├── turbo.json
├── tsconfig.json
└── pnpm-lock.yaml

Што дае манорепазітарый

Манорепазітарый — это адзін репазітарый Git, які мае калькі праектаў і пакетаў. Альтэрнатыва — адзін репазітарый на кожную задачу:

frontend-repository
backend-repository
shared-types-repository
ui-library-repository

У манорепазітарыях гэтыя праекты і пакеты становяцца папкамі: код, які можна развярнуць, знаходзіцца ў папцы apps, а код, які можна перадаць іншым праектам, — у папцы packages:

my-monorepo/
├── apps/
│   ├── web/
│   └── api/
│
└── packages/
    ├── types/
    └── ui/

Код пасуюць безпосередна, замест таго каб спачатку яго публікавалі. Пакет з типамі, які викорыстоўваюцца і фронтэндам, і бэкэндам, значыць, змены паданых дадзеннях доходзяць да обох частак у аднам комітэ:

apps/web
      ↓
packages/types
      ↑
apps/api

Дзе падходзіць Turborepo

pnpm workspaces сяюць пакетамі; Turborepo выбірае, як задачы выконваюцца між нимі. Ён адказвае за оркестрацыю задач, правільную аранжаванне на аднойчыну залежнасцямі, паралельную експансію, локальнае і удаленнае кэшаванне, інкрементальныя будовы і падтрымку workspaces.

Возьмім рэпазітарыю з трыма workspaces:

apps/web
apps/api
packages/types

Кожны з іх можа задаць тыя ж скрыпты:

build
lint
test
dev

У замест на тое, каб увайсці ў кожны каталог па правильнам порядку, яны запускаюцца з коранавага каталогу, а Turborepo паралелізуе ўсё там, дзе гэта можліва. Чырпак, калі гэты падход є корыстным, адзін гдэ Turborepo падходзіць у NestJS monorepo і калі яго можна праўачыць.

Прыямуны

Вам патрэбны Node.js, pnpm, Git і рэдагувальнік. Пераканайцеся, што є Node.js:

node -v

А таксама pnpm:

pnpm -v

Якщо pnpm не ёсць, Corepack, які ўключаецца разам з Node.js, можа яго запраўдзіць. Актывацыя яго:

corepack enable

Актывацыя найновейшага падмету pnpm:

corepack prepare pnpm@latest --activate

Паказаць падтверджэнне:

pnpm -v

Настроўкі коранавага рабочаг прастору

Стварыце каталог:

mkdir my-monorepo
cd my-monorepo

Ініцыялізацыя Git:

git init

Стварэнне коранавага маніфесту:

pnpm init

Што залишаецца:

my-monorepo/
└── package.json

Установіце Turborepo ў корэні

Turborepo обслугоўвае весь репазітарый, таму параметр --workspace-root установіце яго ў корэні, а не ў пакете:

pnpm add turbo --save-dev --workspace-root

У файле-маніфесте корэню ў канцы застаюцца скрыпты, якія перадаюць задачы на turbo run; атрыбут private не дазволяе публікаваць корэнь:

{
  "name": "my-monorepo",
  "private": true,
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "test": "turbo run test"
  },
  "devDependencies": {
    "turbo": "..."
  }
}

Версія вашага turbo залежыць ад часу установкі. Няўзледжыце выданні таксама ачакваюць наявнасць поля packageManager у файле package.json корэню; якщо turbo не можа выявіць вашага менеджера пакетаў, пераканайцеся ў даследжэннях.

Адзначыце працоўныя прасторы

pnpm знаходзіць пакеты чераз файл у корэні:

pnpm-workspace.yaml

Указаце патэрыны, якія трэба спрацавваць як пакеты:

packages:
  - "apps/*"
  - "packages/*"

Кожны каталог, які знаходзіцца безпасульва пад імі, стае працоўным прасторам:

apps/*
packages/*

Створыце папкі для обох дапрыянняў і пакета types:

mkdir -p apps/web
mkdir -p apps/api
mkdir -p packages/types

Дрэва структуры нараз:

my-monorepo/
├── apps/
│   ├── web/
│   └── api/
│
├── packages/
│   └── types/
│
├── package.json
└── pnpm-workspace.yaml

Дадзенне двух дапрыянняў

Веб-дапрыянне

Ёжы ўвагу на манорепо, веб-дапрыянне нараз являе сабой просты Node.js. Уведзіце яго:

cd apps/web

Дадзіце яму маніфест:

pnpm init

Цэльвая структура:

apps/web/
├── package.json
└── src/
    └── index.ts

Створыце файл-вхід:

mkdir src
touch src/index.ts

Дадзіце месца для заполнення:

console.log("Hello from Web application");

Кожная рабочая прастора патрабуе TypeScript, таму вярніцеся да корневай папкі:

cd ../..

І установіце яго аднойчы:

pnpm add typescript --save-dev --workspace-root

API

Той самы патэрн: уведзіце і ініцыялізаваце:

cd apps/api
pnpm init

Створыце файл-вхід:

mkdir src
touch src/index.ts

Дадзіце месца для заполнення яго:

console.log("Hello from API application");

Оба дапрыяння тепер адпавядаюць:

apps/
├── web/
│   ├── src/
│   │   └── index.ts
│   └── package.json
│
└── api/
    ├── src/
    │   └── index.ts
    └── package.json

Аднароджэнне канфігурацыі TypeScript

Вернуцца да корана:

cd ../..

Стварыць базовую настройку:

tsconfig.json

У яй зберагаюцца параметры, якія выкарыстоўваюцься ўсімі рабочымі прасторамі: сучасная мета, рэшанаванне проблем з NodeNext і строгая перацэнка:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

Кожны прыклад праграмы расшыроўвае яе і дадае толькі локальныя настройкі. Для веб-прыкладу праграмы стварыць:

apps/web/tsconfig.json

Яна паказывае на файл-коран, задае папку выходных даўжынакоў і компілюе толькі катэгорію src:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": ["src"]
}

API отрымае той самы файл:

apps/api/tsconfig.json

З ідэнтычным зместам:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": ["src"]
}

Змены ў ступені строгасці чыльнай меты тепер выконваюцца ў адным месцы.

Надача скрыптав для кожнага рабочага прастору

Turborepo запускае скрыпты, якія задаюцься рабочымі прасторамі. Ачніце веб-маніфест:

apps/web/package.json

Задаць імя з дзеякім дыапазонам і тры скрыпты: build за дапамою tsc, dev у режыме стазірвання, lint за дапамою ESLint:

{
  "name": "@repo/web",
  "private": true,
  "scripts": {
    "build": "tsc",
    "dev": "tsx watch src/index.ts",
    "lint": "eslint ."
  }
}

Потым маніфест API:

apps/api/package.json

За своім сабе іменам:

{
  "name": "@repo/api",
  "private": true,
  "scripts": {
    "build": "tsc",
    "dev": "tsx watch src/index.ts",
    "lint": "eslint ."
  }
}

Фільтры і залежнасці рабочага прастору апілююцца да гэтых імен @repo/.... Скрыпт dev патрэбуе tsx:

pnpm add tsx --save-dev --workspace-root

Скрыпт lint таксама прыяжджае, што ESLint ўстановлена і налаштавана; дадзіце яго або адмовіцеся ад скрыпта, інакш pnpm lint не будзе працаваць.

Настройка пайплайна задач

turbo.json описвае, як кожная задача дзейнае. Створыце яго ў корні:

turbo.json

Задаць задачы:

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

Што трэба з’важыць:

  • "dependsOn": ["^build"] заставляе пакет чакаць на завершэнне будовы пакетаў рабочага прастору, ад якіх ён залежыць; знак кареткі означае залежнасці.
  • outputs паказвае, што трэба захаваць і вярнуць, таму пакеты, якія не зменіліся, не перабудуюцца.
  • dev не захаваны і ў стане persistent, таму што процес нагляду ніколі не завершваецца.
  • lint таксама спачатку запускае залежнасці.
  • tasks — гэта ныяны ключ; старэйшыя версіі викорыстоўвалі pipeline, таму старэйшыя прыклады можа знадобіцца паспраўдзіць.

    Запуск і будова з корневага рэгістру

    Запускайце всі процесы развіцця:

    pnpm dev
    

    Гэта працуе таму, што скрыпт dev у корневым рэгістры ёсць:

    "dev": "turbo run dev"
    

    Turborepo знаходзіць dev у кожным рабочым просторе і запускае іх адночасна, заменяючы адну тэрмінал-вікно для веб-дзеяння:

    cd apps/web
    pnpm dev
    

    І іншую для API:

    cd apps/api
    pnpm dev
    

    За дапамою адной команды з корневага рэгістру:

    pnpm dev
    

    Будуеце все адной і той жа спосабам:

    pnpm build
    

    Turborepo аранжуець процес будавання па калекцыях залежнасцей пакетаў, спачатку шырокая калекцыя пакетаў:

    pnpm build
          │
          ▼
    turbo run build
          │
          ├── packages/types
          │
          ├── apps/api
          │
          └── apps/web
    

    Такі порядак вынікае з заданых залежнасцей: паккі packages/types не будзе будаваны першым, пакуль у яго ня будзе файла package.json і пакеты не будуць ад яго залежаць, Turborepo гэта не зрабіць.

    Наведчыцца на адну рабочую прастору

    Функцыя --filter у pnpm запускае скрыпт у адной рабочай прасторы, напрыклад, сервер развіцця API:

    pnpm --filter @repo/api dev
    

    Або процес будавання веб-праграмы:

    pnpm --filter @repo/web build
    

    Свой фільтр Turborepo даглядае за кэшаваннем і аранжуванням:

    pnpm turbo run build --filter=@repo/api
    

    Каманды, якія вы будете вжываць ўсё час

    Установіце все:

    pnpm install
    

    Пачніце розработку:

    pnpm dev
    

    Будавайце ўсё:

    pnpm build
    

    Працаваце з правіламі форматування ўсьго:

    pnpm lint
    

    Будавайце адні пакет:

    pnpm --filter @repo/api build
    

    Запускайце адну праграму:

    pnpm --filter @repo/web dev
    

    Дадзіце залежнасць у адну рабочую прастору:

    pnpm --filter @repo/api add express
    

    Залежнасць ад локальнага пакета; workspace:* нарабатывае звязак з копіяй репазітарыю заместо запрашэння дадзеных з реўістры:

    pnpm --filter @repo/api add @repo/types@workspace:*
    

    Чаму не адмахнуцца ад падтрымкі workspaces у pnpm?

    Можна паспакоўвацца толькі workspaces:

    apps/
    packages/
    

    Аднак, калі репазітарый расте, коордынацыя стае болей ручной:

    build
    test
    lint
    typecheck
    dev
    dependencies
    task ordering
    caching
    

    Turborepo даўное арганізуе весь процес: адна команда разумее атрыбуты пакетаў, прыходзіць за скрытымі змянамі і запускае незалежныя задачы паралельна:

    pnpm turbo run build
    

    Для адной прыкладнай програмы і адного пакета можа быць дапатныя простыя workspaces. Яшчэ выбіраеце менеджар пакетаў? Паглядзіце нашу порэванае npm і pnpm.

    Узагальненне

    Хорашы манорепазітарый — это спакоючая среда, дзе прыкладныя програмы і пакеты развіваюцца пад аднай наборам засобаў. Тут колькасць компанентаў мінімальная:

    pnpm
    +
    Turborepo
    +
    TypeScript
    

    Пачатак з двух аплікацыяў:

    apps/
    ├── web/
    └── api/
    

    Развітак у больш захоўнікаў:

    apps/
    ├── web/
    ├── admin/
    ├── api/
    └── worker/
    

    Падтрымванае саюзнымі пакетамі:

    packages/
    ├── ui/
    ├── types/
    ├── database/
    ├── auth/
    └── utils/
    

    Працэс складаецца з аднароўнення коду, типаў, настройкаў і процэсаў работы, пры чым кожная аплікацыя застаецца незалежна організаванай. Колі вы яе расширяеце:

    • заявіце локальныя залежнасці за дапамогай workspace:*, ўжо бы практыкі складання веліся правільна
    • запісаце кожны артыфакт у outputs, іншаўна вярненне кэшу яго працягне
    • зберагучы базовыя настройкі ў корэні і расширюючы іх
    • дадзіце packageManager і інструменты, такія як ESLint, прычаму не паслугаваючыся скрыптамі корэна ў CI

    Аднаколы: дасведчэнні Turborepo, репазітарый Turborepo, дасведчэнні pnpm і дасведчэнні Node.js.