Stencil und Web Components
Web Components sind eine Sammlung von Webstandards, welche es erlauben modulare und wiederverwendbare HTML Elemente zu erstellen. Gängige Funktionen großer Frameworks wie Lebenszyklen von Komponenten und dynamic werden nativ unterstützt. Da diese nur auf Standards basieren, braucht man zur Ausführung kein Framework sondern ein moderner Browser ist genug.
Web Components bestehen primär aus folgenden 4 APIs aus den Webstandards:
Custom Elements
Der Custom Elements Standard gibt uns die Möglichkeit eigene HTML Elemente zu definieren.
Shadow DOM
Mit dem Shadow DOM können Stylesheets und Markup vom Rest des Markups abgekapselt werden, so dass diese sich nicht mehr gegenseitig beeinflussen.
HTML Templates
Mit HTML Templates kann man wiederverwendbare Snippets definieren. Innerhalb eines HTML Templates werden keine Skripte ausgeführt, Stylesheets angewandt oder Bilder geladen. So lange es nicht angewandt wurde ist es kein regulärer Bestandteil des aktuellen Dokuments.
ES Modules
Die ES Modules Spezifikation definiert einen Standardweg für das Importieren und Wiederverwenden von Javascript Dateien auf möglichst performante Art und Weise.
Stencil building blocks
Stencil ist ein Web Component Compiler welche vom Team rund um das Ionicframework entwickelt wird. Stencil ist hierbei eben nicht das nächste Framework, sondern ein Compiler dessen Ausgabeformat Standardkonforme Web Components sind. Es gibt noch ein paar andere Compiler und Frameworks welche dies tun. Es gibt zwar die Möglichkeit ohne einen solchen Compiler zu arbeiten, dies ist in der Handhabung aber recht sperrig und macht gerade für eine größere Codebase kaum Sinn.
import { Component, Prop, h } from '@stencil/core';
@Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true,
})
export class MyComponent {
@Prop() name: string;
render() {
return <div>Hello, World! I'm ${this.name}</div>;
}
}
Code-Sprache: JavaScript (javascript)
Je nach persönlichen Erfahrungen mit verschiedenen Frontend Frameworks sieht eine Stencil Komponente auf den ersten Blick etwas wie eine Mischung von Angular und React aus.
Stencil nutzt für das Templating TSX (JSX in Typescript) und Typescript als führende Sprache für die Entwicklung. Gängige Stylesheet Präprozessoren wie Sass werden genauso unterstützt wie Less und PostCSS.
Hands on mit Nx
Zu Nx hat mein Kollege Robert Weyres hier auch schon einen Artikel zur Entwicklung mit Nx und Angular Bibliotheken geschrieben.
Für ein kleines Beispiel legen wir nun ein leeres Nx Monorepo an:
npx create-nx-workspace --preset=empty
Code-Sprache: PHP (php)
Und installieren die für uns wichtigen Plugins:
npx nx g @nxext/stencil:lib core --style='css' --buildable
Code-Sprache: JavaScript (javascript)
Framework Integration
Stencil hat Unterstützung um für Frameworks wie Angular, React, Vue und Svelte extra Wrapper zu generieren. Obwohl die meisten dieser Frameworks mit Web Components umgehen können, fühlen sich diese trotzdem wie Fremdkörper in der Applikation an. Ich nehme in den Beispielen hier Angular als Beispiel.
Über das Stencil Nx Plugin kann hier nun die Outputconfig mit dazugehöriger Angular Bibliothek generiert werden.
npx nx g @nxext/stencil:add-outputtarget core --outputType='angular'
Code-Sprache: JavaScript (javascript)
Hierdurch wird die stencil.config.ts um folgendes ergänzt:
...
angularOutputTarget({
componentCorePackage: '@agnostic-library/core',
directivesProxyFile: '../../../libs/core-angular/src/generated/directives/proxies.ts',
valueAccessorConfigs: angularValueAccessorBindings,
}),
...
Code-Sprache: CSS (css)
Damit haben wir innerhalb des „libs“ Verzeichnisses folgende Ordnerstruktur:
|
|-libs|-core
| |-core-angular
|-...
Mit jedem build der „core“ Bibliothek werden nun Wrapper-Komponenten mit Angular für jede Stencil Komponente in den Ordner der „core-angular“ Bibliothek generiert. Diese ist eine Angular Bibliothek ohne eigene Komponenten welche nur ein Angular Modul für den export und die generierten Wrapper enthält.
Damit die neue Angular Bibliothek nun auch die Komponenten nutzbar macht müssen diese exportiert werden. Hierfür wird das neue Angular Modul erweitert und die neue Komponente bekannt und in Anwendungen nutzbar gemacht:
...
import { MyComponent } from '../generated/directives/proxies';
const components = [
MyComponent
]
@NgModule({
imports: [CommonModule],
declarations: components,
exports: components
})
export class CoreAngularModule {}
Code-Sprache: JavaScript (javascript)
Eine Angular Applikation kann nun die Bibliothek importieren und nutzen, damit alles funktioniert müssen nun noch die Web Components im Browser bekannt gemacht werden. Vorher steht man vor dem Problem, dass die Komponenten nicht im Browser gerendert werden. Hierfür tragen wir folgendes in die main.ts der Angular Applikation ein:
import { defineCustomElements } from '@agnostic-library/core/loader';
defineCustomElements();
Code-Sprache: JavaScript (javascript)
Die generierten Angular Komponenten nutzen die ursprünglichen Web Components und sorgen dafür, dass sie sich wie native Angular Komponenten anfühlen.
Dies an sich klingt erstmal nicht sonderlich spektakulär. Was ich über die Jahre gerade in großen Unternehmen beobachten konnte ist, dass die genutzten Frameworks für das Frontend immer mal ausgetauscht werden oder mehrere parallel von verschiedenen Teams genutzt werden. Wenn man nun über Standards wie Web Components Komponentenbibliotheken erstellt, welche z. B. dem Corporate Design entsprechen, lassen sich diese recht schnell für alle Frameworks adaptieren. Hierdurch können Anstrengungen gebündelt werden, weil ein „Build once, use everywhere“ Ansatz genutzt werden kann. Nicht jede Komponente muss in jedem Framework neu geschrieben werden sondern sie können gemeinsam genutzt werden. Als kleine Zugabe und da Web Components als Umgebung nur einen Browser brauchen kann man sie auch ganz ohne Framework z. B. auf der Unternehmenswebsite oder Landing Pages genutzt werden.
Auf Github befindet sich ein kleines Beispiel und in der Nxext Demo ein etwas größeres Repository mit umfangreicheren Komponenten, eine Angular Anwendung mit Backend.