Що таке Angular Ivy?

Якщо ви останнім часом стежили за розвитком Angular, ви, мабуть, зіткнулися з словом “Ivy”.

За цим кодовим ім’ям ховається величезна робота для Angular team, і крок у майбутнє. Але важко зрозуміти, що таке Ivy. Давайте дізнаємося.

Ваш JS framework є компілятором

Ваш JS framework є компілятором. Це вірно для більшості систем JS, але це особливо актуально для Angular.

У Angular, коли ви пишете компонент, ви пишете компонент в TypeScript і його шаблон в HTML, доповнений синтаксисом Angular template ( ngIfngForі т.д.).

Те, що багато розробників не знають, це те, що цей HTML ніколи не буде відображатися у браузері. Він буде скомпільований Angular в інструкції JavaScript, щоб створити відповідний DOM, коли компонент з’явиться на сторінці, і оновити компонент при зміні його стану. Ось чому велика частина Angular є її компілятором: він бере весь ваш HTML і генерує необхідний код JS. Цей компілятор (і час виконання) був повністю переписаний за останній рік, і це є Ivy. Це не перший перезапис: Ivy означає “IV”, 4 – римськими числами. Останнє переписання було зроблено в Angular 4.0, і, можливо, ви навіть не помітили його. Але це, безумовно, найглибше переписування внутрішніх пристроїв з моменту першого випуску Angular: Angular команда буквально змінює двигун (раніше називався View Engine) під час руху.

Цілі Ivy

Ivy є дуже важливим ступенем історії Angular. Вона змінює спосіб внутрішньої роботи рамки, не змінюючи способів написання Angular.

Якщо паралель має певний сенс, він дуже схожий на React і “Fiber rewrite”. React Fiber – це повна переписування внутрішніх елементів React, і особливо пропонується більш інтенсивне візуалізація. Перезапис тривав більше року, і відкрив двері для нових можливостей (наприклад, знамениті Hooks, які були випущені в React 16.8 і які спираються на Fiber).

Angular досягає цього ж з цим зусиллям: Ivy – це повна переписування компілятора (і середовища виконання), щоб:

  • Досягнення кращого часу збірки
  • Досягнути кращих розмірів збірки
  • Неможливо відкрити нові потенційні можливості (метапрограмування або компоненти вищого порядку, ліниве завантаження компонентів замість модулів, нова система виявлення змін, яка не базується на zone.js…)

Ніяких зусиль з вашого боку

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

Але може статися, що Ivy не має точно такої ж поведінки для деяких випадків. Щоб уникнути порушення додатків, коли ми переходимо до Ivy, команда Angular написала сценарії міграції (схеми оновлення), щоб проаналізувати ваш код і підготувати його до Ivy, якщо це необхідно. Таким чином, коли ви будете оновлювати до Angular 8, схеми будуть працювати і налаштувати кілька речей у вашому коді, щоб бути “Ivy-ready”, коли настане час. План полягає в тому, щоб включити Ivy за замовчуванням в майбутньому, можливо, у V9.

Відмінності у створеному коді

Давайте зануримося в подробиці 🤓.

Для компонента PonyComponentз шаблоном:

<figure>
  <img [src]="getPonyImageUrl()">
  <figcaption>{{ ponyModel.name }}</figcaption>
</figure>

Двигун перегляду, движок попереднього Ivy, згенерований код:

function View_PonyComponent() {
  return viewDef([
    elementDef(2, 'figure'),
    elementDef(0, 'img', ['src']),
    elementDef(1, 'figcaption'),
    textDef()
  ], (checkBinding, view) => {
    var component = view.component;
    const currVal_0 = component.getPonyImageUrl();
    checkBinding(view, 1, currVal_0);
    const currVal_1 = component.ponyModel.name;
    checkBinding(view, 4, currVal_1);
  });
}

Це те, що називається ng_factory , функція, що визначає визначення подання з двома частинами:

  • статичний опис DOM для створення
  • функція, яка викликається, коли стан компонента змінено

Ivy генерує інший код для одного компонента. Спочатку він більше не генерує ng_factory : він вбудовує сформований код в статичне поле. @DirectiveДекоратора стає поле під назвою ngDirectiveDef@InjectableДекоратора стає полем під назвою ngInjectableDef@ComponentДекоратора стає поле під назвою ngComponentDef.

Таким чином, наш компонент стає:

class PonyComponent {
  static ngComponentDef = defineComponent({
    type: PonyComponent,
    selectors: [['ns-pony']],
    factory: () => new PonyComponent(),
    // number of elements, templates, content, bound texts to allocate...
    consts: 4,
    vars: 2,
    template: (renderFlags: RenderFlags, component: PonyComponent) => {
      if (renderFlags & RenderFlags.Create) {
        elementStart(0, 'figure');
        element(1, 'img');
        elementStart(2, 'figcaption');
        text(3);
        elementEnd();
        elementEnd();
      }
      if (renderFlags & RenderFlags.Update) {
        select(1)
        property('src', component.getPonyImageUrl());
        select(3)
        textBinding(3, interpolation1('', component.ponyModel.name, ''));
      }
    }
  });
}

Зауважте, що код, створений у templateчастині, має приблизно однакову форму, ніж ng_factory(частина створення та оновлення), але використовує різні інструкції.

Але найбільша різниця – це, мабуть, новий locality principle. Це може мати велике значення при розробці програми Angular та скорочення часу відновлення. Але це також дозволяє відправляти заздалегідь скомпільований код до NPM безпосередньо!

Простіше публікувати

Якщо ви хочете опублікувати Angular бібліотеку на NPM, вам доведеться скомпілювати код TypeScript у JavaScript, а потім запустити компілятор Angular для створення metadata.jsonфайлів.

Потім, коли хтось будував програму з вашою бібліотекою, ng buildбудував ng_factory.jsфайли для ваших компонентів і для компонентів, що надходять з бібліотек. Це означає, що якщо програма використовувала 3 бібліотеки з 10 компонентами кожна, а сама програма мала 50 компонентів, ng buildбуло складено 80 компонентів.

У Ivy, як ви вже зрозуміли, не існує більше ng_factory.jsабо metadata.jsonфайлів. Це означає, що, як автор бібліотеки, ви можете безпосередньо відправити до NPM скомпільований код JS, з результатом компіляції Ivy (зі статичними полями, що генеруються для кожного компонента, директиви, сервісу…).

Потім, коли хтось створює програму з вашою бібліотекою «Ivy-ready», вони не витрачатимуть ресурси на складання компонентів вашої бібліотеки! Це повинно прискорити час відновлення в циклі розробки, коли ми працюємо, ng serveі ми чекаємо перевірки модифікації, яку ми зробили.

(Re) час побудови

Коли бібліотека вже скомпільована, то нам не доведеться перекомпілювати її кожного разу. 

Але також виходить, що раніше, коли ви працювали над вашим додатком, Angular довелося перекомпілювати все всередині вашого модуля, щоб дізнатися, що змінилося, оскільки згенерований код компонента міг використовувати внутрішні деталі іншого компонента.

Тепер кожен компонент посилається на директиви та компоненти, які він використовує, тільки їх публічними API. Отже, якщо ви зміните внутрішню деталь компонента або директиви, перекомпілюватимуться лише фактичні компоненти, які його використовують. Це може призвести до величезних переваг у відновленні часу для додатків з десятками компонентів і директив, так як ви перейдете з перекомпіляції всіх з них, щоб перекомпілювати тільки ті, що потрібні.

Візьмемо приклад з нашим, PonyComponentякий оголошує його вхід таким чином:

@Input('pony') ponyModel: PonyModel;

Зазвичай, властивість ( this.ponyModel) виводиться як вхід з іншим ім’ям ( pony). Тому, коли компонент використовує PonyComponentу своєму шаблоні, він виглядає так:

<ns-pony [pony]="myPony"></ns-pony>

А згенерований код в Ivy, виглядає так:

// ...
  if (renderFlags & RenderFlags.Update) {
    select(0);
    // updates the public `pony` property
    property('pony', component.myPony);
  }
},
directives: [PonyComponent]

Але з видом двигуна виглядав так:

// ...
elementDef(0, 'ns-pony'),
// updates the private `ponyModel` field
directiveDef('PonyComponent', { ponyModel: [0, 'ponyModel'] }
// ...

Можливо, це не схоже на велику різницю, але View Engine посилається лише на приватне поле, а не на його загальнодоступне ім’я.

Це принцип місцевості. Щоб скомпілювати AppComponentцей PonyComponentшаблон, Ivy не знає нічого про компонент pony. Висновок компілятора Ivy Appcomponent залежить тільки від коду AppComponent.

Це було невірно в ViewEngine, де код, згенерований для, AppComponent також залежав від коду PonyComponent (в даному прикладі, від імені поля, що ponyModelпідтримує вхід pony).

Це схоже на детальну реалізацію, але має наслідки для часу перебудови, оскільки компілятор Ivy може зробити краще, ніж перекомпілювати все, як це було зроблено в View Engine.

Трохи дрібниць історії Angular: модулі були введені досить недавно в розвитку Angular, всього за кілька місяців до стабільного випуску. Раніше під час бета-фази 2.0 ви повинні були вручну посилатися на кожен компонент і директиву, що використовуються в компоненті, безпосередньо в декораторі. Модулі були введені, щоб уникнути цього, але недоліком було те, що він став найменшою одиницею компіляції: зміна одного елемента модуля призводить до перекомпіляції всіх елементів модуля. Ви можете побачити, як код, згенерований в Ivy, повертає нас до того, що було спочатку розроблено командою Angular, з directivesвластивістю, сформованою в ngComponentDefполі.

Розміри пакетів

Новий набір інструкцій розроблено для досягнення вищезазначених цілей. Більш точно, він був розроблений, щоб бути повністю деревом тремтіння. Це означає, що якщо ви не використовуєте особливу особливість Angular, інструкції, що відповідають цій функції, не будуть у вашому остаточному пакеті. Більше, ніж час виконання Ivy не буде мати коду для запуску цієї інструкції, тоді як View Engine не був деревом, і він завжди містив все.

Ось чому команда очікує великих поліпшень розмірів невеликих додатків, а особливо програми Hello World (яка раніше виробляла великий пакет для Hello World), або для Angular Element .

Для середніх і великих додатків ситуація не повинна сильно змінитися з першим випуском Ivy. Пакети повинні мати приблизно однакові розміри (або навіть трохи більші), як і з View Engine. Команда Angular встигне зосередитися на цьому, коли вони впевнені, що немає регресії з Ivy, і ми можемо сподіватися на менші пачки в кожному випадку в майбутньому.

Час виконання

Ivy не приділяє особливої ​​уваги виставам, принаймні не в першому випуску. Вона була розроблена, щоб бути дуже ефективною пам’яттю, деякі механіки були покращені, і вона все ще розроблена, щоб уникнути мегаморфних викликів, але загалом ви не повинні бачити великих поліпшень або втрат. Якщо ви помітили втрату продуктивності, команда, ймовірно, буде дуже рада почути про це.

Ivy відкриває кілька можливостей для майбутнього. Тепер має бути можливим запускати додаток без zone.js і вручну управляти виявленням змін (подібно до React). Ці API вже існують, але є експериментальними, не задокументованими і, ймовірно, зміниться найближчим часом.

Краща перевірка типу шаблону

Кутове компілятор має варіант , який часто упускається з виду , на мій погляд: fullTemplateTypeCheck. Якщо увімкнено, компілятор намагається глибше аналізувати шаблони. Я показав деякі приклади того, на що він здатний, коли він був введений в Angular 5 . Ця опція в даний час більш потужна в Ivy, і, ймовірно, буде ще більш потужним в майбутньому.

Наприклад, однією з функцій, які вже доступні в Ivy, є перевірка типу компонентних і директивних входів. Уявіть собі, ImageComponentщо вхід називається sizeтипом number. Якщо інший компонент використовує ImageComponentі намагається передати логічне значення в sizeвласність, ви побачите повідомлення про помилку: Type 'boolean' is not assignable to type 'number'..

Це лише приклад того, на що Ivy буде здатна, і ці функції дуже цікаві для великих додатків.

Зворотна сумісність

Я пояснив, що компілятор Ivy приймає декоратори в коді TypeScript, а потім генерує статичне поле в класі. Але поточні бібліотеки, що поставляються на NPM, більше не мають свого декоратора, вони зазвичай відправляють код JavaScript, отриманий в результаті компіляції. А Ivy потрібні ці статичні поля для правильної роботи, так що ми застрягли, поки кожна бібліотека, яку ми використовуємо, не поставила нову версію?

Команда Angular створила компілятор сумісності ngcc. Цей компілятор має одне важливе завдання: він приймає node_modulesвашу програму, шукає кутові бібліотеки, читає їхні metadata.jsonфайли і код JS, і виводить той самий код JS, але з статичними полями, яким потрібно Ivy!

Це справді вражаючий інженерний пристрій, в основному прихований від наших очей, оскільки він безпосередньо вбудований в CLI. Так що в перший раз ви будете працювати ng serveабо ng build, ви помітите, завдання займає більше часу, ніж зазвичай, як ngccі робити свою магію за лаштунками. Але не бійтеся: це треба робити тільки один раз, а потім він знову не запуститься (за винятком випадків, коли ви додаєте до вашої програми іншу бібліотеку Angular).

Майбутні можливості

Angular 8.0 – перший крок для Ivy. Коли він буде достатньо стабільним, він стане типовим. І тоді команда може почати працювати над додаванням інших функцій легше. Як сервіс i18n , можливо, одна з найбільш очікуваних функцій. Або можливість мати метапрограмування або компоненти вищого порядку . Або ліниво-завантажувати один компонент замість модуля. Або мати компоненти JiT і компоненти AoT працювати один з одним. Або створити шаблон вручну, написавши вручну згенеровані інструкції, щоб вичавити найкращі показники. І, мабуть, тонни інших ідей, яких команда має, і про які ще не говорили.

Я сподіваюся, що це трохи роз’яснило, про що Ivy, і що ви спробуєте в наступні місяці!

Переклад статті “What is Angular Ivy?