Пасэпэйкаванне прамыслов pnpm і Turborepo для монарепаў у прыкладнах Node.js, шаг за шагам
Створыце манорэпа на TypeScript з порожней папкі за допамогай пакета pnpm workspaces і Turborepo, а потым запускайце, будуйце і фільтруйце задачы для веб-дзялоў, API і спакетаў, якія дзелююцца між сабой.
Калі продукт уже мае фронтэнд, бэкэнд і неабходны код, аддзельныя репазітарыі пачына ствараць проблемы: спяшаныя типы, настройкі копіююцца вручную, а адна змянка паўтараецца ў калькі прызначэнных для згуртавання змян. 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.