Девятая версия Angular сменила движок рендеринга на Ivy, и с тех пор вся оптимизационная история фреймворка это история о том, как выжать из этой основы максимум: меньше байт в бандле, меньше лишних проверок при обновлениях, быстрее первый экран и честная работа с современными механизмами реактивности. Новые мажоры линейки добавляют синтаксис, который делает оптимизации удобнее, а не декларативнее, и разница между приложением по старым привычкам и приложением по современным приёмам измеряется мегабайтами и миллисекундами.

Статья разбирает рабочие рычаги: стратегию обновлений с подпиской на изменения, новый синтаксис потока управления, отложенные представления, ленивые границы бандла, сигнальную реактивность, гидрацию на сервере и дисциплину измерений. Каждый приём описан с кодом и границами применимости, чтобы собирать скорость поступательно, а не вслепую.

Что Ivy изменил в рендеринга Angular и почему это основа современных версий

Старый движок компилировал шаблоны в код, который таскал с собой интерпретатор: приложение возило рантайм, умеющий исполнять шаблоны любого вида. Ivy перевёл сборку в инструкции конкретного шаблона: компилятор выдаёт точные команды создания и обновления, неиспользуемое вылетает при встряхивании, и бандл платит только за то, что реально встречается в разметке.

Следствие номер один это размер: компоненты, которые не используются, не попадают в сборку, включая случаи, которые раньше требовали ручной регистрации. Следствие два это скорость компиляции: инкрементальная сборка пересобирает изменённое, а не весь проект, и разработка на крупной кодовой базе перестаёт ждать компилятор на каждом сохранении.

Следствие три это диагностика: шаблонные ошибки сообщаются шаблонами конкретного компонента с указанием места, а не абстрактной строкой в глубине стека. Опыт отладки вырос настолько, что это само по себе сократило время, которое команды тратили на разбор чужих шаблонов.

Отдельный слой это стиль: конструкторы стилей и вычисляемые классы, где оформление живёт рядом с логикой, встряхиваются вместе с шаблоном. Стили компонента, не попавшего в бандл, тоже не попадают в бандл, и эта симметрия работает на размер сама.

Основы производительности Ivy не требуют от разработчика действий: они включены самим фактом использования современной версии. Дальнейшие же ступени требует руки человека, и первая из них это стратегия обнаружения изменений.

Обнаружение изменений OnPush и конец слепых обновлений

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

Дисциплина иммутабельности обязательна: OnPush верит ссылкам, и мутация объекта без смены ссылки остаётся невидимой. Отсюда стиль обновлений через замену массивов и объектов новыми, а не через правку полей на месте, и этот стиль, к счастью, совпадает с лучшими практиками работы с состоянием.

Функция трекинга в повторах это второй рычаг той же природы: список из тысячи строк при изменении одной перестраивает один элемент, а не тысячу, и ключ трекинга это то, что отличает быструю таблицу от заметно дрожащей. Ключи обязаны быть стабильными и уникальными, индекс массива годится только для списков, которые никогда не меняют порядок.

OnPush не даётся даром по вниманию: граница ответственности смещается к разработчику, и компонент, забывший про иммутабельность, тихо перестаёт обновляться. Поэтому стратегия вводится вместе с правилами код-ревью и проверками в шаблонах, а не как разовый эксперимент.

Профилировщик обновлений делает стратегию зримой: дерево проверок на каждое событие видно глазами, и страницы, где после включения OnPush осталось три проверяемых компонента из трёхсот, выглядят как чистая победа. Такой осмотр проводят на самых интерактивных экранах продукта раз в спринт.

Новый синтаксис потока управления и трекинг элементов в повторах

Современный синтаксис потока управления описывает условия и циклы прямо в шаблоне знаками собаки: ветвление и повтор читаются как обычная разметка, без обёрток-контейнеров и специальных директив. Компилятор Ivy собирает такие конструкции в более компактные инструкции, и встряхивание удаляет неиспользуемые ветки целиком.

@Component({
  selector: "app-counter",
  template: `
    @if (count() > 0) {
      <button (click)="reset()">Reset {{ count() }}</button>
    } @else {
      <button (click)="increment()">Click</button>
    }
  `
})
export class Counter {
  count = signal(0);

  increment() {
    this.count.update((v) => v + 1);
  }

  reset() {
    this.count.set(0);
  }
}

Циклы получили встроенный трекинг и пустые состояния: повтор с ключом обновляет только сдвинувшиеся элементы, а пустой список рисует собственную заглушку без ручного ветвления. Читаемость шаблона растёт вместе со скоростью: меньше обёрток, меньше циклов-костылей, меньше мест для ошибок.

Миграция старых директив автоматизирована: схема обновляет шаблоны за один проход, и после неё ручной работы остаётся только проверка краевых случаев. Держать две стилистики в одном проекте дольше пары спринтов не стоит: единый синтаксис ускоряет и людей, и компилятор.

Вычисляемые значения в шаблонах живут отдельной темой: чистая функция, вызываемая на каждом цикле проверки, отличается от вычисляемого сигнала, который пересчитывается только при смене источников. Перевод тяжёлых вычислений в вычисляемые сигналы снимает с шаблонов скрытую работу и делает быстрые страницы быстрыми предсказуемо.

Отложенные представления и загрузка тяжёлых кусков по требованию

Тяжёлые компоненты, графики, редакторы, карты, съедают первый экран, хотя пользователь увидит их в лучшем случае после скролла. Отложенные представления решают это декларативно: блок помечается как отложенный, каркас показывается сразу, а реальный груз приезжает по триггеру, при попадании в область просмотра, по простою, по наведению или по явному событию.

@defer (on viewport; prefetch on idle) {
  <app-heavy-chart />
} @placeholder {
  <app-skeleton />
}

Триггеры сочетаются с предзагрузкой: скелет уже на экране, фоновая загрузка уже идёт, и к моменту прокрутки компонент готов без ожидания. Зависимости отложенного блока вылетают из основного чанка, и метрика первого экрана улучшается даже у страниц, которые формально содержат мегабайты редких интерактивных возможностей.

Отложенность работает по всей вертикали: не только компонент, но и его стили и дочерние зависимости грузятся вместе с ним. Ошибка загрузки получает собственную ветку восстановления, и приложение, потерявшее сеть при дозагрузке, показывает вежливое сообщение, а не пустоту.

Ленивые маршруты standalone компоненты и границы бандла

Маршруты загружаются кусками по умолчанию давно, и современные версии дожали механику: самостоятельные компоненты без модулей упрощают граф зависимостей, а предзагрузка группами делает переходы мгновенными там, где это предсказуемо. Правило простое: пользовательский путь режется по экранам, экран это граница чанка, и редкий экран не живёт в основном бандле.

Самостоятельные компоненты сокращают и саму табличку сборки: меньше связующих файлов, меньше импортов по цепочке, и встряхивание вырезает больше мусора. Библиотеки компонентов подключаются по потребности, и это относится не только к тяжёлым сеткам и графикам, но и к иконкам, которые честно сабсетятся по фактическому использованию.

Бюджеты сборки превращают размер из надежды в правило: лимиты на стартовый чанк и на каждый кусок проверяются в конвейере, и нарушение останавливает слияние раньше, чем попадёт в продакшен. Бюджет подбирается по продуктовой цели и пересматривается не чаще раза в квартал, чтобы защита не превратилась в шум.

Виртуальная прокрутка завершает набор для длинных списков: окно видимых элементов держит в памяти десятки строк вместо десятков тысяч, и таблицы на миллионы записей перестают быть легендой. Готовые средства интерфейсного набора фреймворка решают её из коробки, а самописная реализация почти всегда проигрывает по краевым случаям.

Состав чанков проверяется глазами ежемесячно: карта сборки показывает, что именно приехало в стартовый кусок, и один случайный импорт из глубины библиотеки виден сразу. Чанк со странным весом это не повод для тревоги, а повод для точечного выноса в отложенную загрузку.

Сигналы и zoneless рендеринг куда движется фреймворк

Сигналы это точная реактивность: значение оборачивается в контейнер с подписчиками, и при изменении обновляются ровно зависимые, а не дерево на всякий случай. Шаблоны научились читать сигналы напрямую, входы и запросы к представлению получили сигнальные формы, и приложение постепенно переезжает от зоны, которая ловила все события подряд, к явно объявленным зависимостям.

Следующая ступень это отказ от зоны целиком: помеченное как бесзонное приложение больше не подписывается на все асинхронные источники мира, а обновляется по сигналам. Это убирает слой магии, экономит такты на страницах с плотными событиями и делает поведение предсказуемым для профилировщика. Переход поощряется постепенный: сигналы заводятся в новые компоненты, старые живут как жили.

Эффекты требуют уважения к уборке: подписка на сигналы внутри эффекта отписывается автоматически, а внешние источники освобождаются руками, и таблица уборки становится частью код-ревью. Эффект без уборки это утечка, которая проявляется не в демо, а в долгой сессии реального пользователя.

Смешанная экосистема требует аккуратности: компонент на сигналах рядом с компонентом на зоне обновляется по разным правилам, и до полного переезда стоит вести карту того, где какая модель работает. Финальная цель описывается одной строкой: изменение значения обновляет ровно те места экрана, где оно читается, и ни одним больше.

Гидрация на сервере и измеримая оптимизация

Серверный рендеринг отдаёт готовый первый экран, а гидрация пришивает события к уже нарисованной разметке без повторной сборки. Современная гидрация переиспользует серверную разметку целиком, без мигания контента и без двойной работы, и метрики первой отрисовки улучшаются на порядок ощутимее, чем любая микрооптимизация шаблона.

Измерения оформляются в дисциплину: профилировщик обновлений показывает дерево проверок, инструменты сборки показывают состав чанков, метрики скорости страницы замеряются в конвейере на реальных страницах. Оптимизация без измерения это хобби, измерение без бюджета это гадание, и зрелая команда держит и то и другое.

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

Порядок внедрения оптимизаций свёрнут в короткую лестницу:

  1. обновить линейку фреймворка и миграцией перейти на новый поток управления;
  2. включить стратегию OnPush и иммутабельные обновления в новых компонентах;
  3. вынести редкие экраны и тяжёлые виджеты в отложенные представления;
  4. проверить бюджеты сборки и включить их проверку в конвейер;
  5. перевести новые компоненты на сигналы и подготовить путь к бесзонному режиму;
  6. включить серверный рендеринг с переиспользующей гидрацией для первых экранов;
  7. замерять ключевые страницы в конвейере и сверяться с бюджетами ежемесячно.

Ivy дал Angular вторую молодость: рендеринг стал компилируемым, реактивность стала точной, а оптимизации стали признаками языка, а не трюками сообщества. Приложение, собранное по этим правилам, весит меньше, стартует быстрее и обновляется чище, и всё это без героизма, одной версией фреймворка и дисциплиной шаблонов.