Irgendwann musste ich mich dringend mit Webkomponenten vertraut machen und einen Weg finden, sie bequem zu entwickeln. Ich habe vor, eine Reihe von Artikeln zu schreiben, die dies tun würden
Organisieren Sie irgendwie das Wissen über Webkomponenten, lit-element und geben Sie anderen eine kurze Einführung in diese Technologie. Ich bin kein Experte für diese Technologie und nehme gerne Feedback entgegen.
lit-element ist ein Wrapper (Basisvorlage) für native Webkomponenten. Es implementiert viele bequeme Methoden, die nicht in der Spezifikation enthalten sind. Aufgrund seiner Nähe zur nativen Implementierung zeigt lit-element in verschiedenen Benchmarks im Vergleich zu anderen Ansätzen (Stand 02.06.2019) sehr gute Ergebnisse.
Boni, die ich durch die Verwendung von lit-element als Basisklasse von Webkomponenten sehe:
- Diese Technologie implementiert bereits die zweite Version und wurde „krank mit Kinderkrankheiten“, die den gerade erschienenen Instrumenten eigen sind.
- Die Montage kann sowohl mit Polymer als auch mit Webpack, Typoskript, Rollup usw. durchgeführt werden. Auf diese Weise können Sie lit-element problemlos in jedes moderne Projekt einbetten.
- Das beleuchtete Element verfügt über ein sehr praktisches System zum Arbeiten mit Eigenschaften, um Werte einzugeben, zu initiieren und zu konvertieren.
- lit-element implementiert fast die gleiche Logik wie die Reaktion, d.h. Es bietet das Minimum - eine einzige Vorlage zum Erstellen von Komponenten und deren Rendering - und schränkt den Entwickler bei der Auswahl eines Ökosystems und zusätzlicher Bibliotheken nicht ein.
Erstellen Sie eine einfache Webkomponente für lit-element. Wenden wir uns der Dokumentation zu. Wir brauchen folgendes:
- Fügen Sie unserer Baugruppe das npm-Paket mit lit-element hinzu
npm install --save lit-element
- Erstellen Sie unsere Komponente.
Zum Beispiel müssen wir eine Webkomponente erstellen, die im my-component
Tag initialisiert ist. Erstellen Sie dazu die js-Datei my-component.js
und definieren Sie deren Grundvorlage:
Zuerst importieren wir unsere Basisvorlage:
import { LitElement, html } from 'lit-element';
Zweitens erstellen Sie die Webkomponente selbst mit LitElement
Und das Letzte ist, die Webkomponente im Browser zu registrieren
customElements.define('my-component', MyComponent);
Als Ergebnis erhalten wir Folgendes:
import { LitElement, html } from 'lit-element'; class MyComponent extends LitElement { render() { return html`<p>Hello World!</p>` } } customElements.define('my-component', MyComponent);
Wenn Sie die Notwendigkeit ausschließen, my-component.js
mit HTML zu verbinden, dann ist es das. Die einfachste Komponente ist fertig.
Ich schlage vor, das Rad nicht neu zu erfinden und die fertige Montage des Rollups mit beleuchteten Elementen vorzunehmen. Befolgen Sie die Anweisungen:
git clone https://github.com/PolymerLabs/lit-element-build-rollup.git cd lit-element-build-rollup npm install npm run build npm run start
Nachdem alle Befehle ausgeführt wurden, gehen wir zur Seite im Browser http: // localhost: 5000 / .
Wenn wir uns HTML ansehen, werden wir sehen, dass sich webcomponents-loader.js vor dem schließenden Tag befindet. Dies ist ein Satz von Polyfüllungen für Webkomponenten, und für den browserübergreifenden Betrieb der Webkomponente ist es wünschenswert, dass diese Polyfüllung vorhanden ist. Schauen wir uns die Tabelle der Browser an , die alle Standards für die Arbeit mit Webkomponenten implementieren. Dort heißt es, dass EDGE die Standards immer noch nicht vollständig implementiert (ich schweige über IE11, das noch unterstützt werden muss).

2 Optionen für diese Polyfüllung implementiert:
- webcomponents-bundle.js - Diese Version enthält alle möglichen Optionen für die Polyfüllung. Sie werden alle initiiert, aber jede Polyfüllung funktioniert nur auf der Grundlage der erkannten Zeichen.
- webcomponents-loader.js ist ein minimaler Bootloader, der basierend auf den erkannten Symptomen die erforderlichen Polyfills lädt
Ich bitte Sie auch, auf eine andere Polyfüllung zu achten - custom-elements-es5-adapter.js . Gemäß der Spezifikation können nur native ESE-Klassen zu ES6-Klassen hinzugefügt werden. Für eine optimale Leistung sollte ES6-Code nur an Browser übergeben werden, die ihn unterstützen, und ES5 an alle anderen. Dies ist nicht immer möglich. Aus Gründen der besseren Cross-Browser-Kompatibilität wird empfohlen, den gesamten ES6-Code in ES5 zu konvertieren. In diesem Fall können die Webkomponenten von ES5 jedoch nicht in Browsern verwendet werden. Um dieses Problem zu lösen, gibt es custom-elements-es5-adapter.js.
Öffnen ./src/my-element.js
Datei ./src/my-element.js
import {html, LitElement, property} from 'lit-element'; class MyElement extends LitElement {
Die lit-html-Vorlagen-Engine kann eine Zeichenfolge unterschiedlich verarbeiten. Ich werde Ihnen mehrere Optionen geben:
Tipps zur Optimierung der Funktion render ():
- darf den Zustand eines Elements nicht ändern,
- sollte keine Nebenwirkungen haben,
- sollte nur von den Eigenschaften des Elements abhängen,
- sollte das gleiche Ergebnis zurückgeben, wenn die gleichen Werte übertragen werden.
Aktualisieren Sie das DOM nicht außerhalb der Funktion render ().
Lit-html ist für das Rendern des lit-Elements verantwortlich. Dies ist eine deklarative Methode, um zu beschreiben, wie die Webkomponente angezeigt werden soll. lit-html garantiert schnelle Aktualisierungen, indem nur die Teile des DOM geändert werden, die geändert werden müssen.
Fast der gesamte Code befand sich in einem einfachen Beispiel, aber der Dekorator @property
wurde für die Eigenschaft @property
hinzugefügt. Dieser Dekorateur gibt an, dass wir in unserem my-element
ein Attribut namens myprop
erwarten. Wenn kein solches Attribut festgelegt ist, wird der Zeichenfolgenwert standardmäßig auf stuff
.
<my-element></my-element> <my-element myprop="else"></my-element>
lit-element bietet zwei Möglichkeiten, mit property
zu arbeiten:
- Durch den Dekorateur.
- Über einen statischen Getter
properties
.
Die erste Option ermöglicht es, jede Eigenschaft separat anzugeben:
@property({type: String}) prop1 = ''; @property({type: Number}) prop2 = 0; @property({type: Boolean}) prop3 = false; @property({type: Array}) prop4 = []; @property({type: Object}) prop5 = {};
Die zweite besteht darin, alles an einer Stelle anzugeben. In diesem Fall muss die Eigenschaft, wenn sie einen Standardwert hat, in die Klassenkonstruktormethode geschrieben werden:
static get properties() { return { prop1: {type: String}, prop2: {type: Number}, prop3: {type: Boolean}, prop4: {type: Array}, prop5: {type: Object} }; } constructor() { this.prop1 = ''; this.prop2 = 0; this.prop3 = false; this.prop4 = []; this.prop5 = {}; }
Die API für die Arbeit mit Eigenschaften in lit-element ist ziemlich umfangreich:
- Attribut : Gibt an, ob eine Eigenschaft zu einem beobachtbaren Attribut werden kann. Wenn
false
, wird das Attribut von der Beobachtung ausgeschlossen, und es wird kein Getter dafür erstellt. Wenn true
oder ein attribute
fehlt, entspricht die im Getter im LowerCamelCase-Format angegebene Eigenschaft dem Attribut im Zeichenfolgenformat. Wenn eine Zeichenfolge angegeben wird, z. B. my-prop
, entspricht sie in den Attributen demselben Namen. - Konverter : Enthält eine Beschreibung, wie ein Wert von / in ein Attribut / eine Eigenschaft konvertiert wird. Der Wert kann eine Funktion sein, mit der der Wert serialisiert und deserialisiert werden kann, oder er kann ein Objekt mit den Schlüsseln
fromAttribute
und toAttribute
. Diese Schlüssel enthalten separate Funktionen zum Konvertieren der Werte. Standardmäßig enthält die Eigenschaft eine Konvertierung in die Basistypen Boolean
, String
, Number
, Object
und Array
. Die Konvertierungsregeln sind hier aufgelistet. - Typ : Gibt einen der Basistypen an, die diese Eigenschaft enthalten wird. Es wird als „Hinweis“ für den Konverter verwendet, welchen Typ die Eigenschaft enthalten soll.
- Reflect : Gibt an, ob das Attribut der Eigenschaft (
true
) zugeordnet und gemäß den Regeln von type
und converter
geändert werden soll. - hasChanged : Jede Eigenschaft verfügt über eine Funktion, die bestimmt, ob zwischen dem alten und dem neuen Wert geändert wird, und einen
Boolean
Wert zurückgibt. Wenn true
, wird das Element aktualisiert. - noAccessor : Diese Eigenschaft akzeptiert einen
Boolean
Wert und ist standardmäßig false
. Es verbietet die Erzeugung von Gettern und Setzern für jede Eigenschaft, um von der Klasse aus auf sie zuzugreifen. Dadurch wird die Konvertierung nicht abgebrochen.
Lassen Sie uns ein hypothetisches Beispiel machen: Wir schreiben eine Webkomponente, die einen Parameter enthält, der eine Zeichenfolge enthält. Dieses Wort sollte auf dem Bildschirm gezeichnet werden, in dem jeder Buchstabe größer als der vorherige ist.
<ladder-of-letters letters=""></ladder-of-letters>
Am Ende bekommen wir:

Wenn auf die Schaltfläche geklickt wurde, wurde die Eigenschaft geändert, wodurch die Prüfung zuerst ausgelöst und dann zum erneuten Zeichnen gesendet wurde.

und mit reflect
wir auch HTML-Änderungen sehen

Wenn Sie dieses Attribut mit Code außerhalb dieser Webkomponente ändern, wird auch die Webkomponente neu gezeichnet.
Betrachten Sie nun das Styling der Komponente. Wir haben zwei Möglichkeiten, lit-element zu stylen:
- Styling durch Hinzufügen eines Style-Tags zur Rendermethode
render() { return html` <style> p { color: green; } </style> <p>Hello World</p> `; }

- Über statische Getter-
styles
import {html, LitElement, css} from 'lit-element'; class MyElement extends LitElement { static get styles() { return [ css` p { color: red; } ` ]; } render() { return html` <p>Hello World</p> `; } } customElements.define('my-element', MyElement);
Als Ergebnis erhalten wir, dass ein Tag mit Stilen nicht erstellt, sondern gemäß der Spezifikation in das Shadow DOM
Elements geschrieben wird ( >= Chrome 73
). Dies verbessert die Leistung bei einer großen Anzahl von Elementen, weil Wenn er eine neue Komponente registriert, weiß er bereits, welche Eigenschaften seine Stile bestimmen. Sie müssen nicht jedes Mal registriert und nachgezählt werden.

Wenn diese Spezifikation nicht unterstützt wird, wird in der Komponente ein reguläres style
Tag erstellt.

Vergessen Sie außerdem nicht, dass wir auf diese Weise auch trennen können, welche Stile auf der Seite hinzugefügt und berechnet werden. Zum Beispiel, um Medienabfragen nicht in CSS, sondern in JS zu verwenden und beispielsweise nur den gewünschten Stil zu implementieren (dies ist wild, muss es aber sein):
static get styles() { const mobileStyle = css`p { color: red; }`; const desktopStyle = css`p { color: green; }`; return [ window.matchMedia("(min-width: 400px)").matches ? desktopStyle : mobileStyle ]; }
Dementsprechend wird dies angezeigt, wenn sich der Benutzer an einem Gerät mit einer Bildschirmbreite von mehr als 400 Pixel angemeldet hat.

Dies ist der Fall, wenn der Benutzer die Site von einem Gerät mit einer Breite von weniger als 400 Pixel aus besucht hat.

Meine Meinung: Es gibt praktisch keinen angemessenen Fall, in dem ein Benutzer während der Arbeit an einem mobilen Gerät plötzlich einem vollwertigen Monitor mit einer Bildschirmbreite von 1920 Pixel gegenübersteht. Hinzu kommt das verzögerte Laden von Komponenten. Als Ergebnis erhalten wir eine sehr optimierte Front mit schnellem Komponenten-Rendering. Das einzige Problem ist die Schwierigkeit bei der Unterstützung.
Jetzt schlage ich vor, mich mit den Lebenszyklusmethoden von lit-element vertraut zu machen:
- render () : Implementiert eine Beschreibung des DOM-Elements mit
lit-html
. Im Idealfall ist die render
eine reine Funktion, die nur die aktuellen Eigenschaften des Elements verwendet. Die render()
-Methode wird von der update()
-Funktion aufgerufen. - shouldUpdate (definedProperties) : Wird implementiert, wenn die Aktualisierung und das Rendern gesteuert werden müssen, wenn Eigenschaften geändert wurden oder
requestUpdate()
aufgerufen wurde. Das Argument für die Funktion " changedProperties
ist eine Map
mit Schlüsseln für die geänderten Eigenschaften. Standardmäßig gibt diese Methode immer true
, aber die Logik der Methode kann geändert werden, um die Aktualisierung der Komponente zu steuern. - performUpdate () : implementiert, um die Aktualisierungszeit zu steuern, z. B. um sie in den Scheduler zu integrieren.
- update (definedProperties) : Diese Methode ruft
render()
. Außerdem werden die Attribute eines Elements entsprechend dem Wert der Eigenschaft aktualisiert. Das Festlegen von Eigenschaften innerhalb dieser Methode führt nicht zu einem weiteren Update. - firstUpdated (definedProperties) : Wird nach der ersten Aktualisierung des DOM-Elements unmittelbar vor dem Aufruf von update
updated()
aufgerufen. Diese Methode kann nützlich sein, um Links zu visualisierten statischen Knoten zu erfassen, mit denen Sie direkt arbeiten müssen, z. B. in updated()
. - aktualisiert (geänderte Eigenschaften) : Wird aufgerufen, wenn das DOM eines Elements aktualisiert und angezeigt wird. Eine Implementierung zum Ausführen von Aufgaben nach dem Aktualisieren über die DOM-API, z. B. mit Schwerpunkt auf einem Element.
- requestUpdate (name, oldValue) : Ruft eine asynchrone Aktualisierungsanforderung für ein Element auf. Dies sollte aufgerufen werden, wenn das Element basierend auf einem Status aktualisiert werden muss, der nicht durch das Festlegen der Eigenschaft verursacht wird.
- createRenderRoot () : Erstellt standardmäßig eine Schattenwurzel für das Element. Wenn die Verwendung des Schatten-DOM nicht erforderlich ist, sollte die Methode
this
.
Wie wird das Element aktualisiert:
- Die Eigenschaft erhält einen neuen Wert.
- Wenn die
hasChanged(value, oldValue)
false
zurückgibt, wird das Element nicht aktualisiert. Andernfalls wird ein Update durch Aufrufen von requestUpdate()
. - requestUpdate () : Aktualisiert das Element nach der Mikrotask (am Ende der Ereignisschleife und vor dem nächsten Neuzeichnen).
- performUpdate () : Das Update wird ausgeführt und mit dem Rest der Update-API fortgesetzt.
- shouldUpdate (definedProperties) : Das Update wird fortgesetzt, wenn
true
zurückgegeben wird. - firstUpdated (changedProperties) : Wird aufgerufen, wenn das Element zum ersten Mal aktualisiert wird, unmittelbar bevor update () aufgerufen wird.
- update (definedProperties) : Aktualisiert das Element. Das Ändern von Eigenschaften in dieser Methode führt nicht zu einem weiteren Update.
- render () : Gibt eine
lit-html
Vorlage zum Rendern eines Elements im DOM zurück. Das Ändern von Eigenschaften in dieser Methode führt nicht zu einem weiteren Update.
- aktualisiert (geänderte Eigenschaften) : Wird aufgerufen, wenn ein Element aktualisiert wird.
Um alle Nuancen des Komponentenlebenszyklus zu verstehen, empfehle ich Ihnen, die Dokumentation zu konsultieren.
Bei der Arbeit habe ich ein Projekt zu Adobe Experience Manager (AEM), in dessen Authoring der Benutzer Komponenten per Drag & Drop auf die Seite ziehen kann. Gemäß der AEM-Ideologie enthält diese Komponente ein script
Tag, das alles enthält, was zur Implementierung der Logik dieser Komponente erforderlich ist. Tatsächlich führte dieser Ansatz jedoch zu vielen blockierenden Ressourcen und Schwierigkeiten bei der Implementierung der Front in diesem System. Um die Front zu implementieren, wurden Webkomponenten ausgewählt, um das serverseitige Rendering nicht zu ändern (was er sehr gut gemacht hat) und um die alte Implementierung mit einem neuen Ansatz sanft und bitweise zu bereichern. Meiner Meinung nach gibt es mehrere Möglichkeiten, das Laden von Webkomponenten für dieses System zu implementieren: Sammeln Sie ein Bundle (es kann sehr groß werden) oder teilen Sie es in Blöcke auf (viele kleine Dateien, dynamisches Laden ist erforderlich), oder verwenden Sie den aktuellen Ansatz, bei dem jeweils ein Skript eingebettet wird eine Komponente, die auf der Serverseite gerendert wird (ich möchte wirklich nicht darauf zurückkommen). Meiner Meinung nach ist die erste und dritte Option keine Option. Für den zweiten benötigen Sie einen dynamischen Bootloader wie in der Schablone. Für das beleuchtete Element in der "Box" ist dies jedoch nicht vorgesehen. Es gab einen Versuch der Lit-Element-Entwickler , einen dynamischen Loader zu erstellen , aber es ist ein Experiment, und es wird nicht empfohlen, ihn in der Produktion zu verwenden. Auch von Lit-Element-Entwicklern gibt es ein Problem im Repository für Webkomponentenspezifikationen mit dem Vorschlag, der Spezifikation die Möglichkeit hinzuzufügen, die erforderlichen JS für die Webkomponente basierend auf dem HTML-Markup auf der Seite dynamisch zu laden. Meiner Meinung nach ist dieses native Tool eine sehr gute Idee, mit der Sie einen Initialisierungspunkt für Webkomponenten erstellen und ihn einfach allen Seiten der Website hinzufügen können.
Um die Webkomponenten mit beleuchteten Elementen dynamisch mit den PolymerLabs-Mitarbeitern zu laden, wurde Split-Element entwickelt. Dies ist eine experimentelle Lösung. Es funktioniert folgendermaßen:
- Um ein SplitElement zu erstellen, schreiben Sie zwei Elementdefinitionen in zwei Module.
- Einer davon ist ein Stub, der die geladenen Teile eines Elements definiert: Normalerweise sind dies der Name und die Eigenschaften. Eigenschaften müssen mit einem Stub definiert werden, damit lit-element rechtzeitig beobachtbare Attribute generieren kann, um
customElements.define()
. - Der Stub muss außerdem über eine statische asynchrone Lademethode verfügen, die eine Implementierungsklasse zurückgibt.
- Eine andere Klasse ist die "Implementierung", die alles andere enthält.
- Der
SplitElement
Konstruktor lädt die Implementierungsklasse und führt upgrade()
.
Stub Beispiel:
import {SplitElement, property} from '../split-element.js'; export class MyElement extends SplitElement {
Implementierungsbeispiel:
import {MyElement} from './my-element.js'; import {html} from '../split-element.js';
SplitElement-Beispiel für ES6:
import {LitElement, html} from 'lit-element'; export * from 'lit-element';
Wenn Sie die oben im Rollup vorgeschlagene Assembly weiterhin verwenden, müssen Sie babel so einstellen, dass dynamische Importe verarbeitet werden können
npm install @babel/plugin-syntax-dynamic-import
Und in den .babelrc Einstellungen hinzufügen
{ "plugins": ["@babel/plugin-syntax-dynamic-import"] }
Hier habe ich ein kleines Beispiel für die Implementierung des verzögerten Ladens von Webkomponenten erstellt: https://github.com/malay76a/elbrus-split-litelement-web-components
Ich habe versucht, den Ansatz des dynamischen Ladens von Webkomponenten anzuwenden, bin jedoch zu folgendem Ergebnis gekommen: Das Tool funktioniert recht gut. Sie müssen alle Definitionen von Webkomponenten in einer Datei sammeln und die Beschreibung der Komponente selbst separat durch Blöcke verbinden. Ohne http2 funktioniert dieser Ansatz nicht, weil Es entsteht ein sehr großer Pool kleiner Dateien, die die Komponenten beschreiben. Basierend auf dem Prinzip des Atomdesigns muss der Import von Atomen im Körper bestimmt werden, aber der Körper muss bereits als separate Komponente verbunden sein. Einer der Engpässe besteht darin, dass der Benutzer viele Definitionen von Benutzerelementen im Browser erhält, die auf die eine oder andere Weise im Browser initialisiert werden, und der Anfangszustand wird bestimmt. Eine solche Lösung ist überflüssig. Eine der Optionen für eine einfache Lösung für den Komponentenlader ist der folgende Algorithmus:
- Erforderliche Dienstprogramme laden,
- Polyfills laden,
- Bauen Sie benutzerdefinierte Elemente aus Light DOM zusammen:
- Alle DOM-Elemente, die einen Bindestrich im Tag-Namen enthalten, werden ausgewählt
- Die Liste wird gefiltert und eine Liste der ersten Elemente gebildet.
- :
- Intersection Observer,
- +- 100px import.
- 3 shadowDOM,
- , shadowDOM , , import JS.
- lit-element open-wc.org . webpack rollup, - storybook, IDE.
:
- Let's Build Web Components! Part 5: LitElement
- Web Component Essentials
- A night experimenting with Lit-HTML…
- LitElement To Do App
- LitElement app tutorial part 1: Getting started
- LitElement tutorial part 2: Templating, properties, and events
- LitElement tutorial part 3: State management with Redux
- LitElement tutorial part 4: Navigation and code splitting
- LitElement tutorial part 5: PWA and offline
- Lit-html workshop
- Awesome lit-html