Kolumne: Webentwicklung mit Angular

Edle Optik: Das bringt der neue Ivy Renderer in Angular 6

Karsten Sitterberg

@ Shutterstock / Olha Tsiplyar

In dieser Folge der Kolumne Webentwicklung mit Angular betrachtet Karsten Sitterberg den neuen Renderer „Ivy“, der im kommenden Angular-6-Release mit an Bord sein wird. Was hat es damit auf sich, und wieso ist der neue Renderer nicht automatisch aktiv?

Mit dem neuen Renderer wurde in Angular 5 die ohnehin schon sehr gute Performance des JavaScript-Frameworks weiter verbessert. Auch die Größe der entstehenden Anwendungen wurde im Schnitt um bis zu 60% kleiner. Doch bis auf besondere Fälle haben Entwickler davon nichts oder nur wenig mitbekommen. In Angular 6 wird nun eine neue View Engine verfügbar werden: der Ivy Renderer.

Das steckt hinter Ivy

Bei der Entwicklung von Ivy stand vor allem ein Ziel fest: Angular sollte automatische Optimierungen besser unterstützen. Der typische Buildprozess wird aktuell mit Webpack implementiert, wovon Angular CLI abstrahiert und eine einfache Fassade anbietet. Unter der Haube wurden weitere Verbesserungen vorgenommen: Tree-Shaking und der Build-Optimizer erzeugen noch kleinere und schnellere Anwendungen. Bei diesen Buildschritten werden nicht benötigte Bestandteile von Libraries entfernt.

Google hat schon vor geraumer Zeit mit dem für JavaScript gedachten Closure-Compiler gezeigt, dass erhebliche Optimierungen bei Ausführungsgeschwindigkeit und resultierender Anwendungsgröße möglich sind. Dem Angular-Team ist das Potential natürlich nicht verborgen geblieben: Mit TypeScript als Sprache sind zur Build-Zeit Typinformationen vorhanden und damit Analysen möglich, um nicht verwendeten Code zu eliminieren (Dead Code Elimination, DCE).

Doch bisher war die View-Engine an dieser Stelle ein Problem, ein Programm ließ sich damit nur mäßig gut analysieren. Der Ivy Renderer soll genau dies beheben: Das Ziel ist eine Engine, die Anwendungen besonders gut durch Build-Tools analysierbar und optimierbar macht. Man kann sagen, dass das Ziel erreicht wurde: Ein früher Prototyp von Ivy erlaubt es, eine Hello-World-Angular-Anwendung zu erzeugen, welche unter 4kB zu übertragende Daten besitzt (gemessen mit gzip als Kompression bei der Übertragung eine durchaus realistische Rahmenbedingung).

Eine Hello-World-Anwendung lässt als synthetischer Benchmark natürlich nur bedingt Rückschlüsse darauf zu, wie viel Ersparnis bei realistischen Anwendungen zu erwarten ist. Sicherlich hängt schlussendlich die Größe davon ab, wie viele Features genutzt werden, welche Dritt-Libraries eingebunden werden und wie groß der Umfang an CSS und weiteren Assets ausfällt.

Wichtig ist die minimal erzielbare Größe auch für ein weiteres Projekt: Angular Elements. Möchte Angular auch bei reinen Komponenten, wie einem Like-Button, eine ernsthafte Alternative darstellen, müssen diese Komponenten in vertretbarer Bundle-Größe realisierbar sein.

Schlüssel für die möglichen Optimierungen durch Build-Tools ist der Verzicht auf NgFactory-Klassen, welche bisher als Wrapper-Klassen für den Angular Decorator-Code dienten. Mit Ivy werden die Decorators als statische Variable innerhalb der Klassen bereitgestellt. Damit entsteht aus je einer TypeScript-Quelldatei genau eine kompilierte JavaScript-Datei. So werden die Voraussetzungen für JIT- und AOT-Kompilation vereinheitlicht, was die Erstellung und Verwendung etwa von Angular-Komponenten-Libraries einfacher macht.

Weiterhin werden die Komponenten-Templates direkt in JavaScript-Anweisungen umgesetzt, wodurch sich die Templates direkt analysieren lassen. Damit wiederum wird die Dead-Code-Elimination auf der Template-Seite unterstützt.

Ivy zum Anfassen

Ivy steht bereits für erste Experimente bereit und kann mit einem normalen Angular-Projekt verwendet werden. Am komfortabelsten geschieht dies mit Angular CLI, wobei in diesem Artikel von einer Angular CLI Version von mindestens 1.7.0 ausgegangen wird. Zum Erzeugen eines minimalen Beispielprojektes – ohne Tests und mit Inline-Templates – wird der Schalter “–minimal” gesetzt.

ng new MyDemo --minimal

Danach wird das neu erstellte Projekt zunächst auf die neueste Angular-Version 6 aktualisiert.

cd MyDemo
ng update --next

Angular 6 ermöglicht die Verwendung des Ivy Renderer, der Default ist allerdings auch in Angular 6 der bisherige Renderer2. Dieser wurde bereits mit Angular 4 eingeführt. Um Ivy einzuschalten, muss die entsprechende Compiler-Option für den Angular-Compiler aktiviert werden. Dies geschieht, indem folgende Zeilen in die src/tsconfig.app.json hinzugefügt werden.

"angularCompilerOptions": {
    "enableIvy": true
}

Bisher wird für die Interaktion von Angular mit dem Browser/DOM das Paket @angular/platform-browser verwendet. Da der Ivy Renderer jedoch die DOM-Zugriffe selbst implementiert, besteht hier (noch) eine Inkompatibilität, die bei Verwendung des Ivy Renderer berücksichtigt werden muss. In der Demo-Anwendung äußert sich das dadurch, dass das BrowserModule aus dem AppModule (src/app/app.module.ts) entfernt werden muss.

Um sich die Funktionsweise des neuen Renderers im Detail anzuschauen, wird in der package.json folgendes Script angelegt, mit dem die TypeScript-Qullen durch den Angular-Kompiler (ngc) in JavaScript übersetzt werden können.

{
  "scripts": {
    "ngc": "ngc -p src/tsconfig.app.json"
    //...
  }
  //...
}

Die durch dieses Skript generierten Dateien (npm run ngc) werden im Ordner out-tsc abgelegt. Im Unterschied zum vorherigen Renderer fällt direkt auf, dass deutlich weniger Dateien generiert werden: Zu jeder Komponente wurden bisher immer auch Metadaten-Dateien und eine NgFactory-Datei generiert. Dies entfällt nun komplett, es wird pro Komponente lediglich eine JavaScript-Datei erzeugt und optional die zum Debugging verwendeten zugehörigen SourceMap-Dateien.

Ivy Renderer im Detail

Schauen wir uns nun einmal eine mit Hilfe des Ivy Renderer übersetzte Komponente an. Als Ausgangskomponente zeigen wir in diesem Fall eine AppComponent:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    
<div style="text-align:center">
      
<h1>
        Hallo {{title}}!
      </h1>

    </div>

    
  `
})
export class AppComponent {
  title = 'Ivy!';
}

Die AppComponent besitzt das Property title, das per Interpolation in einem schlichten Inline-Template ausgegeben wird. Im Template wird zusätzlich für das <div>-Element ein Inline-Style durch das style-Property definiert, um den Umgang von Ivy zu zeigen. Das Kompilationsresultat sieht dann wie folgt aus:

import { Component } from '@angular/core';
import * as i0 from "@angular/core";
var _c0 = ["style", "text-align:center"];
var AppComponent = /** @class */ (function () {
  function AppComponent() {
    this.title = 'Ivy!';
  }
  AppComponent.decorators = [
    { type: Component, args: [{
          selector: 'app-root',
          template: "\n 
<div style=\"text-align:center\">
\n          
<h1>\n            Hallo {{title}}!\n          </h1>

\n        </div>

        \n  "
      },] },
  ];
  /** @nocollapse */
  AppComponent.ctorParameters = function () { return []; };
  AppComponent.ngComponentDef = i0.ɵdefineComponent(
    {
      type: AppComponent,
      tag: "app-root",
      factory: function AppComponent_Factory() {
        return new AppComponent();
      },
      template: function AppComponent_Template(ctx, cm) {
        if (cm) {
          i0.ɵE(0, "div", _c0);
          i0.ɵE(1, "h1");
          i0.ɵT(2);
          i0.ɵe();
          i0.ɵe();
        }
        i0.ɵt(2, i0.ɵi1(" Hallo ", ctx.title, "! "));
      }
    }
  );
  return AppComponent;
}());
export { AppComponent };
//# sourceMappingURL=app.component.js.map

Die erste Zeile nach den Imports bildet die Initialisierung der Variable _c0. Wenn man sich deren Wert anschaut fällt auf, dass es sich bei dieser Variable um die Definition des Inline-Styles aus dem Komponenten-Template handeln muss. Diese Variablendefinition wird weiter unten im Rahmen des Template-Codes wieder Verwendung finden.

Als nächstes folgt im Code die eigentliche Definition der AppComponent. Innerhalb der Konstruktorfunktion wird das title-Property initialisiert. Auf den Konstruktor folgt die Definition der Metadaten (AppComponent.decorators) und der Konstruktor-Parameter (AppComponent.ctorParameters), wie sie der Angular-Compiler schon heute unabhängig von Ivy erzeugt. Als letztes sind die von Ivy verarbeiteten Metadaten wie oben beschrieben als statische Variable AppComponent.ngComponentDef vorhanden. Sie bilden die von Ivy erzeugte “Definition” der Komponente. Dabei ist zu beachten, dass die zur Komponentendefinition genutzte Funktion ɵdefineComponent() durch das Paket @angular/core bereitgestellt wird. Das Core-Paket von Angular wiederum wird als Namespace i0 zur Verfügung gestellt. Dies geschieht durch das zweite Import-Statement der Datei.

Die Funktion zur Definition der Komponente bekommt nun alle wichtigen Informationen zu dieser Komponente übergeben. Dazu zählen etwa der Typ, das Tag, mit dem die Komponente eingebunden werden kann, die Factory-Funktion zur Erzeugung des Komponenten-Objekts und schließlich eine Funktion zur Erzeugung des Templates der AppComponent. Diese Funktion bekommt zum einen ihren “Kontext” ctx, also eine Instanz der Komponenten-Klasse, und zum anderen ein boolesches Flag cm übergeben, welches das Rendering der Komponente steuert. Innerhalb des If-Blocks sieht man dann, wie verschiedene Elemente (i0.ɵE()) gerendert werden. Im Falle des
<div>-Elements geschieht dies beispielsweise unter Zuhilfenahme der Inline-Style-Definition _c0 von weiter oben.

Im weiteren Verlauf wird dann noch die Interpolation des title-Property der Klasse AppComponent (ctx.title) in JavaScript-Code umgesetzt (i0.ɵt() und i0.ɵi1()).

Die Art und Weise, wie Ivy das Template umsetzt, nämlich als hintereinander ausgeführte Funktionen, ermöglicht die Dead-Code-Elimination auch im Template: Verwendet eine Anwendung eine bestimmte Kontrollstruktur – zum Beispiel *ngFor – nicht, so wird die dazugehörige Funktion niemals aufgerufen und kann per Dead-Code-Elimination entfernt werden.

Ein weiterer Aspekt dieses Resultats ist, dass Komponenten so theoretisch auch von Hand, in purem JavaScript und ohne die Nutzung eines Compilers, geschrieben werden könnten: Wenn Ivy ausgereift ist, werden alle vorher zu NgFactories verarbeiteten Dekoratoren in statische Variablen übersetzt. Genauso wird das Template statt in HTML in einer JavaScript-Funktion ausgedrückt – auch hier entsteht keine hohe Hürde und eine manuelle Implementierung ist möglich. Diese interessante Möglichkeit wird jedoch in den meisten größeren Anwendungen nicht unbedingt sinnvoll einsetzbar sein, sondern sich auf kleine Projekte oder einzelne Komponenten beschränken.

Ausblick

Aktuell ist der Angular Ivy Renderer noch nicht vollständig – das ist jedoch nur eine Frage der Zeit. Da Angular 6 in der Default-Einstellung noch den herkömmlichen Renderer nutzt, besteht kein Risiko für bestehende Projekte. Gezieltes Testen ist jedoch bereits möglich.

Wenn keine schwerwiegenden Probleme auftauchen, könnte Ivy in Angular 7 zum Standard werden. Bis dahin wird Ivy weiter ausgestaltet. Zusammen mit Bazel als Build-Tool bleibt es spannend rund um Angular, eröffnet Ivy schließlich ganz neue Anwendungsbereiche und Optimierungsmöglichkeiten, zum Beispiel in Form von Angular Elements.

Verwandte Themen:

Geschrieben von
Karsten Sitterberg
Karsten Sitterberg
Karsten Sitterberg ist als freiberuflicher Entwickler, Trainer und Berater für Java und Webtechnologien tätig. Karsten ist Physiker (MSc) und Oracle-zertifizierter Java Developer. Seit 2012 arbeitet er mit trion zusammen.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: