ADR-UI-001. Технологический стек фронтенда
Запускается фронтенд ICD (src-ui). Нужно зафиксировать базовый стек UI: фреймворк,
подход к компонентной базе, вспомогательные пакеты — и явно обозначить, что используется, а что
сознательно не включается. Ключевая особенность: компонентную базу строим свою, а не берём
готовый UI-kit, с прицелом на переиспользование набора в других проектах.
1. Контекст и постановка задачи
Фронтенд ICD — тонкий клиент (браузерное веб-приложение), запускается с нуля, наследия нет. Нужно зафиксировать базовый стек, чтобы дальнейшие решения принимались осознанно, а не по инерции.
Дополнительное требование, определяющее выбор: компонентную базу (от элементарных кнопок и полей до крупных таблиц с сортировкой и редактированием ячеек) хотим сделать своей и переиспользуемой в других проектах. Это прямой довод против готового UI-kit как фундамента — иначе каждый проект-потребитель тянул бы за собой стороннюю библиотеку и её визуальный язык.
2. Драйверы решения
- Актуальность — новый проект стартует на самой свежей стабильной версии Angular.
- Переносимость своего UI-набора — компоненты должны безболезненно выноситься в другой проект (целиком или по одному), без завязки на доменный код и без чужой темы.
- Контроль над визуалом и API — свой дизайн-язык и свой контракт компонентов, а не адаптация под чужую тему.
- Минимум зависимостей — каждая внешняя библиотека несёт цену поддержки; подключаем только то, что дорого и рискованно писать самим.
- Покрытие официальной документацией — стек опирается на Angular и его CDK, без редких пакетов.
3. Рассмотренные варианты
- A. Готовый UI-kit (ng-zorro / Angular Material / PrimeNG). Быстрый старт, но навязывает сторонний визуальный язык и делает будущую библиотеку компонентов обёрткой над чужой зависимостью — против цели переносимости.
- B. Собственный набор компонентов на headless-фундаменте
@angular/cdk. Свой визуал и API; CDK закрывает дорогую механику (overlay, a11y, sticky-таблица, virtual-scroll, drag-drop) без навязывания внешнего вида. - C. Собственный набор полностью без зависимостей (даже без CDK). Максимальная независимость, но overlay-позиционирование, focus-trap и a11y легко реализовать некорректно — высокий риск при небольшой выгоде.
4. Решение
Выбран Вариант B: Angular (последний стабильный) + собственный набор компонентов на
@angular/cdk, без готового UI-kit.
4.1. Базовые зависимости
| Пакет | Версия | Назначение |
|---|---|---|
@angular/* |
22.x |
Фреймворк. |
@angular/cdk |
22.x |
Headless-примитивы (overlay, a11y/focus-trap, sticky-таблица, virtual-scroll, drag-drop) — фундамент собственных компонентов, без навязанного визуала. |
rxjs |
7.x |
Реактивные потоки: HTTP, мост в сигналы (toSignal). |
date-fns |
4.x |
Работа с датами, локаль ru (при работе с таймзонами добавляется date-fns-tz). |
sass (SCSS) |
— | Препроцессор стилей. Дизайн-токены — на CSS custom properties (тема задаётся переменными снаружи, что и делает набор переносимым). |
4.2. Формы
Формы — на Signal Forms (@angular/forms/signals), а не на Reactive Forms. Фронт строится
signals-first: значения и валидность формы — сигналы, без моста к реактивным потокам. В
актуальной версии Angular это стабильный публичный API, так что выбор не противоречит
драйверу стабильности.
4.3. Собственный набор компонентов — принципы переносимости
Компоненты живут в src-ui/src/app/ui/ (пока не отдельная npm-библиотека), но
проектируются так, чтобы выноситься в другой проект без переписывания:
- Зависимости только «вниз»: Angular,
@angular/cdk, свои ui-примитивы, дизайн-токены. Ни одного импорта из доменного/прикладного кода проекта. - Ноль доменных знаний: данные — через
input()/generics, поведение — черезoutput()/колбэки. - Стили на дизайн-токенах (CSS custom properties), без хардкода цветов; тема — снаружи.
- Гранулярность и слабая связность: компонент — своя папка со своим barrel; один компонент не тянет весь набор.
- Единый alias и barrel-экспорты (
@ui/*) — набор перемещается одним движением. - Минимум внешних зависимостей, standalone-компоненты.
4.4. Что не используем
- Готовый UI-kit (ng-zorro / Angular Material / PrimeNG) как компонентную базу — по причинам выше.
- Глобальный store (NgRx /
@ngneat/elf). Состояние компонента — через сигналы; разделяемое — через@Injectable({providedIn:'root'})-сервисы с сигналами. Store вводим только при кейсе, который сервисом не решается. @ngneat/until-destroy— эквивалент нативногоtakeUntilDestroyed()+DestroyRef, внешняя зависимость избыточна.- Глобальный event bus — взаимодействие через сервисы и роутер. Понадобится широковещание — заведём отдельный ADR.
- Провайдер анимаций (
@angular/animations/provideAnimations). Анимации — нативными CSS-механизмами (animate.enter/animate.leave) по мере надобности; deprecated-провайдер в фундамент не тянем.
5. Положительные следствия
- Полный контроль над визуалом и API компонентов; набор переносим между проектами.
- Сигналы и
takeUntilDestroyed— нативно, без сторонних пакетов; меньше зависимостей — дешевле апгрейд Angular. - CDK снимает самое дорогое и рискованное (overlay, a11y), не навязывая внешний вид.
6. Отрицательные следствия и компромиссы
- Свой набор компонентов — долгосрочное обязательство: поддержка, a11y, кросс-браузерность, документация. Окупается при переиспользовании в нескольких проектах; на одном — дороже готового kit.
- Крупные компоненты (таблица с сортировкой/редактированием, date-picker) пишем сами поверх CDK — заметный объём разовой работы.
- Без глобального store сложные shared-кейсы (если появятся) пишутся руками через сигналы в сервисе.
7. Проверка
- Каркас
src-uiсобирается и запускается на Angular 22 (standalone, страница-приветствие), без обращения к глобальному store и без готового UI-kit. - Первый крупный компонент (таблица) собирается на
@angular/cdkс сортировкой и режимом редактирования ячеек, оставаясь независимым от доменного кода.
8. Открытые вопросы / отложено
- Тестирование — фреймворк и пакеты не зафиксированы; вероятно
vitest(идёт сng new). Решение отдельным пунктом позже. - Zoneless — режим change detection Angular 22; свериться с текущим каркасом и зафиксировать явно.
- Вынос набора в отдельную библиотеку (npm-пакет / registry) — возможная будущая веха;
сейчас набор живёт в
ui/, но проектируется переносимым. - Событийная шина — вернёмся, если рост числа фич потребует широковещания.