Frameworkunabhängige Komponentenbibliotheken entwickeln mit StencilJS und Nx

Foto des Autors
Dominik Pieper

In vielen Unternehmen werden mehrere moderne Frontend Frameworks genutzt und damit Komponenten immer wieder neu entwickelt. Ich gebe hier eine kurze Einführung in Web Components und wie mit StencilJS und Nx Komponenten entwickelt werden können, welche in gängigen Frameworks wie Angular, Vue und React wiederverwendet werden können.

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.

Das könnte Dich auch noch interessieren

Irisa Limani und Laura Sundara im Conciso Workgarden

Integrationstest-Set-up mittels Extensions vereinfachen

JUnit 5 bietet verschiedene Möglichkeiten, Tests zu erweitern. Wie du JUnit 5 Extensions verwendest, um das Set-up von Integrationstests zu ...
Titelbild zum Blogbeitrag "Rückblick: Moderne Frontends"

Rückblick: Moderne Frontends

Impressionen und Folien zum Conciso-Event “Moderne Frontends” am 28.02.24 ...
Selbststeuerung

Selbststeuerung

Habt Ihr "Pseudo-Scrum" ebenfalls schon mal erlebt? Wie es anders geht und welche Rolle die Entwickler dabei spielen, erfahrt ihr ...