Angular Ivy Renderer готовность 93%

Kliment Ru
Angular Soviet
Published in
6 min readDec 19, 2018

Команда Angular ведет разработку нового движка Rendered3 (Ivy), который должен ускорить работу Angular приложений и уменьшить их размер. На данный момент Ivy готов более чем на 90%, и хотя разработка еще не завершена, многие фичи уже работают.

В этой статье рассмотрим, насколько Ivy готов к реальным приложениям с внедрением зависимостей и несколькими модулями, а так же разберем внутреннее устройство некоторых элементов движка.

Обозначения

  • ⚠️ — важная информация
  • ✏️ — интересная информация
  • 🔧 — Ivy изнутри

Быстрый старт 🚗

Создать Ivy приложение можно с помощью angular-cli(лучше обновиться до последней версии), выполнив команду ng new my-app --experemental-ivy. Посмотрим, что поменялось, и если что-то пошло не так, сможем настроить приложение самостоятельно.

В файле tsconfig.app.json добавилась строка enableIvy, но не со значением true, как это написано в документации, а со значением “ngtsc”. Связанно это с изменением pipeline компилятора(issue).

Небольшая поправка. Все-таки нужно оставлять true(issue).

tsconfig.app.json

Запуск приложения выполняется через метод ɵrenderComponent:

main.ts

BrowserModule тоже больше не нужен:

app.module.ts

Теперь можно запускать ng serve и смотреть на первую ошибку 😅

first ivy error

Issue уже есть на github, и сборка чинится запуском в aot режиме. Первое приложение заработало!!!

⚠️ После установки node modules необходимо запустить команду ivy-ngcc . Это нужно для компиляции модулей Angular в режим, совместимый с Ivy, в противном случае многие фичи и приложение могут не работать.

✏️ Наверно, все, кто смотрел исходники angular, видели символ ɵ в качестве префикса для приватных полей. Вот откуда это пошло(issue). За информацию спасибо Angular Fanatic.

🔧 После появления первого приложения можно посмотреть, во что превращается компонент после компиляции. Для этого добавим скирпт “compile”: “ngc -p src/tsconfig.app.json” в pakcage.json. После его выполнения появится директория out-tsc, в которой будут находится js файлы. Вот как стал выглядеть AppComponent:

Компоненту добавилось статическое свойство ngComponentDef с результатами выполнения функции ɵdefineComponent. В него передаются тип компонента, селектор, фабрика для создания экземпляра и метод template для работы с шаблоном. Разберем его более подробно.

Функция AppComponent_Template состоит из набора инструкций, указывающих, какие именно ноды ДОМ дерева нужно создать и какие манипуляции с ними производить, и принимает на вход два аргумента:

  • rf — сокращение от RenderFlag, который разделяет функцию на две секции. Первая(rf & 1) содержит набор инструкций, которые выполняются при создании компонента. Вторая(rf & 2) запускается при изменениях и содержит инструкции по обновлению компонента;
  • ctx — контекст, т.е. данные, которые передаются из экземпляра класса компонента.

Как видно из кода выше, в инструкции, создающие ноды( ɵelementStart, ɵtext), передаются индексы. При обновлении компонента (rf & 2) происходит не полная перерисовка шаблона, а обновляются только те узлы, в которых изменился контекст. В данном случае это текстовая нода с идентификатором 1, в которой с помощью функции ɵtextBinding обновляется значение.

✏️В новом движке рендеринг шаблона производится с помощью набора специальных инструкций Ivy Instruction Set.

🔧C помощью функции ɵsetClassMetadata(строка 30) задаются метаданные компонента, ранее описанные в декораторе.

Модули

Модуль создается и добавляется в родительский так же, как раньше, и судя по инициализации приложения через компонент, есть вероятность, что команда Angular откажется от них. Поэтому данный вопрос подробно рассматриваться не будет (если интересно, можно посмотреть самостоятельно в репозитории с демо).

Data Binding

В начальном приложении в шаблон уже передавалось свойство title, после компиляции доступное как ctx.title. Теперь можно создать отдельный компонент счетчика и проверить, как обрабатываются события.

counter.component.html
counter.component.ts

Секция шаблона после компиляции

counter.component.js template

В дереве инструкций компонента появился обработчик события клика мыши с очень длинным, но содержательным названием CounterComponent_Template_button_click_listener.

Несмотря на то, что ошибок в консоли нет, при нажатии кнопки значение счетчика не будет увеличиваться 😕. Дело в том, что на данный момент автоматическое обнаружение изменений не работает, метод template не вызывается, и код в секции rf & 2 никогда не будет выполнен (issue). Исправить это можно с помощью метода ɵmarkDirty, который сообщает планировщику, что при следующем обходе дерева компонентов(tick) для данного компонента необходимо будет вызвать метод template.

markDirty

🔧В dev режиме можно отметить компонент вручную через инструменты разработчика. Для этого в консоли браузера нужно выполнить следующие команды:

get component and markDirty from console

✏️Теперь в stacktrace ошибки будут короче и в них будут попадать названия новых функций и методов такие как AppComponent_Template и CounterComponent_Template_button_click_listener. Это сделает отладку и отлов багов более простым, а сами stacktrace более читаемыми.

Внедрение зависимостей

Для проверки внедрения зависимостей инициализация счетчика будет вынесена в отдельный сервис и добавлена в компонент через DI.

counter.service.ts
counter.component.ts

Тут стоит обратить внимание на следующие вещи:

  • ɵmarkDirty переехал в подписку, что логично, т.к. теперь изменение значения counter вызывается в ней;
  • ⚠️при попытке сделать счетчик в корневом компоненте обнаружилось, что в нем не срабатывает хук ngOnInit;
  • ⚠️AsyncPipe тоже запустить не удалось.

🔧В скомпилированном виде сервис будет выглядеть так:

counter.service.js

Как и в компоненте, у сервиса есть статическое свойство, только называется оно ngInjectableDef и содержит результат выполнения функции ɵdefineInjectable (строка 14).

Помимо метода фабрики есть свойство token (строка 15), которое служит идентификатором при внедрении зависимостей.

counter.component.js

🔧В метаданных компонента третий элемент стал массивом с описанием зависимостей (строки 15–19). В данном случае это сервис (type: CounterService).

Директивы

В Angular директивы делятся на два вида:

  • атрибутивные — добавляют элементу атрибуты (class, style и т.д.), пишутся обычно в квадратных скобках;
  • структурные — меняют структуру дом дерева, добавляя или удаляя ноды (*ngIf, *ngFor, *ngSwitch). Начинаются такие директивы со звездочки.

Простой компонент для проверки работоспособности директив:

directives.component.ts
directives.component.html

⚠️ Стандартные директивы хранятся в @angular/common. Не забудьте после установки node modules выполнить ivy-ngcc, иначе при импорте модулей будут ошибки и директивы не заработают (issue).

🔧После компиляции:

directives.component.js

Выглядит страшновато 😰, но если разобраться, все не так плохо.

На что стоит обратить внимание:

  • в ngComponentRef появилось свойство directives со списком используемых структурных директив, импортированных из модуля Common (строка 56);
  • в шаблоне появилась инструкция ɵtemplate (строка 50), которая и есть реализация хорошо знакомого Angular-разработчикам ng-template;
  • содержимое структурных директив вынесено в отдельные функции для ngIf (строка 23) и для ngFor (строка 8), которые выполняются согласно их инструкциям и определенным в них условиям. В противном случае место ng-template занимает нода с типом COMMENT_NODE(проще говоря в DOM дереве на этом месте будет комментарий);
  • для атрибутивной директивы class.ivy функция для рендера не создается, а вместо этого вызывается несколько инструкций, начиная с ɵelementClassProp(строка 17), которая, в зависимости от выполнения условия, присваивает элементу класс.

Выводы

Основным минусом является отсутствие документации. Пришлось долго танцевать с бубном, чтобы приложение запустилось. Были варианты с использованием Bazel и Rollup вместо стандартной сборки через angular-cli, и казалось, что даже Hello World не взлетит.

Но по факту оказалось, что Ivy практически готов. Можно пробовать писать тестовые приложения с его использованием и ждать официального релиза.

На данный момент нет смысла сравнивать производительность или проверять насколько лучше стал tree shaking. Но уже точно можно сказать, что возможность избавления от zone и создания приложения вообще без модулей будет удобна для маленьких проектов и создания кастомных элементов с использованием Angular Elements.

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

Ссылки

Ivy example app

The Theory of Angular Ivy | Alex Rickabaugh | AngularConnect 2018

Angular Ivy by example | Jason Aden | AngularConnect 2018

--

--