57. Monorepo с Nx
60. Monorepo с Nx 🗂️
Заголовок раздела «60. Monorepo с Nx 🗂️»Привет! Яша здесь. Nx — это инструмент для управления monorepo, который превращает хаос из 50 репозиториев в одну хорошо организованную рабочую область. Разберём как Nx помогает масштабировать Angular-проекты 🚀
Зачем нужен Nx
Заголовок раздела «Зачем нужен Nx»Проблема: Большая компания с несколькими Angular-приложениями и общим кодом.
Без Nx:
- Каждое приложение — отдельный репозиторий
- Общие компоненты дублируются или публикуются в NPM
- Нет уверенности что изменение одного кода не сломает другой
- CI/CD запускает всё всегда (долго и дорого)
С Nx:
- Один репозиторий — все приложения и библиотеки вместе
nx affected— запускает только то, что изменилось- Граф зависимостей — видно кто от кого зависит
- Кэш — не пересобирать если ничего не изменилось
Создание Nx Workspace
Заголовок раздела «Создание Nx Workspace»npx create-nx-workspace@latest my-org --preset=angular-monorepo
# Добавить Nx к существующему Angular проектуng add @nx/angular
# Структура workspace# my-org/# ├── apps/# │ ├── shop/ ← Angular приложение# │ └── admin/ ← Angular приложение# ├── libs/# │ ├── ui/ ← UI компоненты (shared)# │ ├── feature-auth/ ← Feature модуль# │ ├── data-access/ ← HTTP сервисы# │ └── util/ ← Утилиты# ├── nx.json# └── package.json ← один package.json на всехГенерация приложений и библиотек
Заголовок раздела «Генерация приложений и библиотек»# Создать приложениеnx generate @nx/angular:application shop --routing --style=scss
# Создать библиотекуnx generate @nx/angular:library ui-components --directory=shared --buildable
# Типы библиотек (Convention от Nx)nx generate @nx/angular:library feature-login --directory=auth # feature/nx generate @nx/angular:library ui-button --directory=shared # ui/nx generate @nx/angular:library data-access-users --directory=users # data-access/nx generate @nx/angular:library util-validators --directory=shared # util/Архитектура библиотек: 4 типа
Заголовок раздела «Архитектура библиотек: 4 типа»apps/ shop/ ← компонует feature libraries admin/ ← компонует feature libraries
libs/ ├── feature/ ← «умные» компоненты, страницы │ └── auth/ ← LoginPage, RegisterPage (специфичны для приложения) │ ├── ui/ ← «тупые» презентационные компоненты │ └── shared/ ← Button, Input, Modal (переиспользуемые везде) │ ├── data-access/ ← HTTP сервисы, NgRx, состояние │ └── users/ ← UserService, userReducer │ └── util/ ← чистые функции, pipes, validators └── shared/ ← formatDate, validators (нет Angular зависимостей)@Component({ selector: 'ui-button', standalone: true, template: `<button [class]="variant" [disabled]="disabled"><ng-content /></button>`,})export class ButtonComponent { @Input() variant: 'primary' | 'secondary' = 'primary'; @Input() disabled = false;}
// libs/ui/shared/src/index.ts — barrel exportexport { ButtonComponent } from './lib/button/button.component';export { InputComponent } from './lib/input/input.component';export { ModalComponent } from './lib/modal/modal.component';
// Использование в приложении:// import { ButtonComponent } from '@my-org/ui/shared';tsconfig paths: алиасы для импортов
Заголовок раздела «tsconfig paths: алиасы для импортов»// tsconfig.base.json — автоматически обновляется Nx{ "compilerOptions": { "paths": { "@my-org/ui/shared": ["libs/ui/shared/src/index.ts"], "@my-org/feature/auth": ["libs/feature/auth/src/index.ts"], "@my-org/data-access/users": ["libs/data-access/users/src/index.ts"], "@my-org/util/shared": ["libs/util/shared/src/index.ts"] } }}// ✅ Правильный импорт через алиас (не относительный путь!)import { ButtonComponent } from '@my-org/ui/shared';import { UserService } from '@my-org/data-access/users';import { validateEmail } from '@my-org/util/shared';
// ❌ Неправильно — относительный путь между библиотекамиimport { ButtonComponent } from '../../libs/ui/shared/src/lib/button/button.component';nx affected: умный CI/CD
Заголовок раздела «nx affected: умный CI/CD»# Показать что изменилось относительно mainnx affected:apps # приложения затронутые изменениямиnx affected:libs # библиотеки затронутые изменениями
# Запустить тесты только для изменённых проектовnx affected:test
# Собрать только изменённые приложенияnx affected:build
# Запустить lint для всех затронутыхnx affected:lint --parallel=5
# Относительно конкретного коммитаnx affected:test --base=HEAD~3 --head=HEADГраф зависимостей
Заголовок раздела «Граф зависимостей»# Открыть интерактивный граф в браузереnx graph
# Граф только для конкретного проектаnx graph --focus=shop
# Просмотр затронутых проектовnx affected:graphВизуализация зависимостей:
shop ──→ feature/auth ──→ data-access/users ──→ util/shared ──→ ui/shared ──→ data-access/cart
admin ──→ feature/auth ──→ data-access/users ──→ ui/sharedПравила зависимостей: module-boundary
Заголовок раздела «Правила зависимостей: module-boundary»// .eslintrc.json — запрет на нарушение архитектуры{ "rules": { "@nx/enforce-module-boundaries": [ "error", { "depConstraints": [ { "sourceTag": "type:feature", "onlyDependOnLibsWithTags": ["type:ui", "type:data-access", "type:util"] }, { "sourceTag": "type:ui", "onlyDependOnLibsWithTags": ["type:util"] }, { "sourceTag": "type:data-access", "onlyDependOnLibsWithTags": ["type:util"] }, { "sourceTag": "scope:shop", "onlyDependOnLibsWithTags": ["scope:shop", "scope:shared"] } ] } ] }}Nx Cloud: распределённое кэширование
Заголовок раздела «Nx Cloud: распределённое кэширование»# Без Nx Cloud: каждая машина строит с нуля# С Nx Cloud: результаты кэшируются в облаке
# Подключить Nx Cloudnx connect-to-nx-cloud
# Результат: если сборка уже была на другой машине — она возьмётся из кэша# nx build shop → retrieved from cache in 2 seconds (instead of 45 seconds!)// nx.json — настройка кэша{ "tasksRunnerOptions": { "default": { "runner": "nx-cloud", "options": { "cacheableOperations": ["build", "test", "lint", "e2e"], "accessToken": "your-nx-cloud-token" } } }}Генераторы (сниппеты для команды)
Заголовок раздела «Генераторы (сниппеты для команды)»// Кастомный генератор для создания feature по стандарту командыimport { Tree, formatFiles, generateFiles, names } from '@nx/devkit';
export default async function (tree: Tree, options: { name: string; scope: string }) { const { fileName, className } = names(options.name);
generateFiles(tree, join(__dirname, 'files'), `libs/feature/${fileName}`, { ...options, fileName, className, tmpl: '', });
await formatFiles(tree);}
// Запуск: nx generate @my-org/tools:feature-module --name=checkout --scope=shopNx Executor: кастомные команды
Заголовок раздела «Nx Executor: кастомные команды»// project.json — кастомный executor{ "targets": { "build-storybook": { "executor": "@nx/storybook:build", "options": { "uiFramework": "@storybook/angular" } }, "analyze": { "executor": "@nx/webpack:webpack", "options": { "statsJson": true } } }}Playground 🎮
Заголовок раздела «Playground 🎮»Визуализатор графа зависимостей Nx monorepo: