Bei der Aufnahme der letzen Folge Working Draft (Revision 173) ging es um die neuen Features in Chrome 36, der aktuell im Beta-Stadium ist. Die Neuerungen sind vor allem für Web Components relevant … aber was sind „Web Components“ eigentlich? Was machen Sie, kann man sie schon benutzen und wie verhalten sich sich zu Projekten wie Polymer und X-Tag? Zeit das alles mal aufzuschreiben.
Welches Problem lösen Web Components?
Es gibt heutzutage kein Plugin-System für Frontend-Entwicklung. Es gibt allerlei Plugins für verschiedene Dinge, aber ein übergreifendes System steckt nicht dahinter. Einer Webseite eine neue Funktion beizubringen ist eine recht unangenehme Aufgabe, da es so viele verschiedene Wege gibt und all diese Wege irgendwelche Knackpunkte haben. Kommt die neue Funktion als jQuery-Plugin daher, braucht man jQuery. Wird sie in Form eines AMD-Moduls geliefert, muss man auch hierfür eine Library zur Hand haben und sich obendrein einen Wolf konfigurieren. NPM-Module müssen durch Browserify geheizt werden, „normale“ Libraries rümpeln irgendwelche Variablen in den globalen Scope (wenn man richtig Glück hat ist auch CSS dabei) und egal welchen Weg man beschreitet: am Ende muss man sich mit einer idiosynkratischen JavaScript-API herumschlagen. So baut man zum Beispiel eine Google-Map ein:
<script src="http://maps.googleapis.com/maps/api/js?key=APIKEY&sensor=true"> </script> <script> new google.maps.Map(document.getElementById('Map'), { center: new google.maps.LatLng(-52.033, 8.533), zoom: 8, mapTypeId: google.maps.MapTypeId.ROADMAP }); </script>
Man darf also sowohl etwas HTML als auch zwei Scripts manuell irgendwie in die Seite bringen und man muss dafür sorgen, dass das JavaScript zum richtigen Zeitpunkt ausgeführt wird. Und das schlimmste ist: dieses Vorgehen und diese API funktionieren so nur für Google-Maps, auf andere Plugins kann man sein Wissen nicht anwenden. Ein ziehbares Element mit jQuery UI erstellt man völlig anders:
$('#draggable').draggable({ containment: "parent" });
Die API ist einerseits völlig anders, allerdings hat man auch hier das Vergnügen, die Abhängigkeiten und das Element selbst einzubauen, die JavaScript-Ausführung zu timen und das Ganze auf eine Art und Weise mit CSS zu versehen, die hoffentlich weder im Rest der Webseite noch in dem Plugin selbst etwas falsch macht.
Mit Web Components verändert sich der Plugin-Einbau so, dass die Google-Map der Zukunft so eingebunden werden wird:
<link rel="import" href="google-map-plugin.html"> <google-map latitude="-52.033" longitude="8.533" zoom="8" type="roadmap"></google-map>
Das Plugin ist ein selbstdefiniertes HTML-Element, das über ein <link rel="import">
in die Ziel-Seite geladen wird. Das Element enthält intern allerlei Markup, CSS und JavaScript, das vom Rest der Seite isoliert ist – externes CSS kann nicht ohne weiteres das interne Markup verunstalten und internes CSS kann die Außenwelt nicht beeinflussen. Die Konfiguration des Plugins erfolgt vor allem über HTML-Attribute, aber auch JavaScript-Methoden und DOM-Events können bereitgestellt werden.
Web Components haben gegenüber dem heutigen Plugin-Wirrwarr eine Reihe von Vorteilen:
- Sie sind einheitlich, denn alles ist ein HTML-Element
- Sie sind einfach zu benutzten, denn jeder kann schon HTML. Selbst für technisch nicht sonderlich bewanderte CMS-Redakteure sollten Web Components zu meistern sein.
- Sie sind kombinierbar, denn sie sind ja nur HTML-Elemente. Eine Konstruktion wie
<light-box><google-map></google-map></light-box>
funktioniert auch ohne dass die beiden Elemente aufeinander abgestimmt sein müssen, so wie auch<div><p></p></div>
einfach funktioniert - Sie kapseln ihren inneren Aufbau (HTML, CSS, JS) und verhindern so Konflikte mit anderen Plugins oder Scripts auf der Webseite.
Web Components lösen also das Problem, dass Frontend-Entwicklung kein Plugin-System hat. Das Webseiten-Plugin der Zukunft ist ein selbstdefiniertes HTML-Element mit komplexem, aber von der Außenwelt sauber abgekapseltem Innenleben, das sich nach außen verhält wie jedes andere Element auch. Damit all das aber in jedem Browser funktioniert, muss eine ganze Menge zusammenkommen …
Was konkret sind Web Components?
Ähnlich wie „HTML5“ keine konkrete Technologie, sondern ein Dachbegriff für viele verschiedene Technologien ist, gibt es auch keine direkte „Web-Component-API“ im Browser. Stattdessen erlaubt ein Set von neuen APIs das Erstellen von Web Components, doch diese APIs können auch für viele andere Dinge verwendet werden. Die wichtigsten APIs aus dem Web-Component-Universum sind die folgenden:
Custom Elements
Wer eigene HTML-Elemente definieren möchte, braucht dafür die entsprechende Browser-Schnittstelle, die von der Custom Elements Specification beschrieben wird. Vom Prinzip her ist das Ganze kein Hexenwerk; man meldet sein neues Element einfach per document.registerElement()
an und schon klappt es:
document.registerElement('x-foo', { prototype: Object.create(HTMLElement.prototype, { // ... }) });
Das hier erstellte Element <x-foo>
ist in dieser Form nicht mehr als ein etwas anders benanntes <div>
ohne besondere Fähigkeiten. Den neuen Elementen spezielle Funktionen beizubringen ist schon etwas komplizierter, geht aber natürlich auch. Aber Stichwort Name: selbstgebaute Elemente müssen im Unterschied zu normalen Elementen einen Bindestrich im Namen haben (z.B. <google-map>
oder <x-widget>
), genießen aber sonst ziemlich die gleichen Rechte wie normale, eingebaute HTML-Elemente.
HTML Templates
HTML Templates sind genau das, was der Name vermuten lässt: Templates. Das Markup in einem <template>
-Element wird vom Browser verarbeitet, aber nicht angezeigt – nur über eine JavaScript-API kann man den Inhalt (ein gebrauchsfertiges DocumentFragment
) auslesen, eventuell vorhandene Platzhalter ersetzen und das Endergebnis dann irgendwo einfügen:
<template> <p>Ich bin Eintrag Nr. {{num}}</p> </template> <script> var tpl = document.querySelector('template').content; for(var i = 0; i < 3; i++){ var p = tpl.querySelector('p').cloneNode(true); p.innerHTML = p.innerHTML.replace('{{num}}', i + 1); document.body.appendChild(p); } </script>
Mit HTML Templates gibt es einen bequemen Weg, unsichtbar Markup für die Verwendung in Scripts überall in einer Webseite (oder einer Komponente) unterzubringen.
Shadow DOM
Das Shadow DOM eines Elements ist eine Art Sub-DOM-Baum in einem Element, der nicht für normale Scripts und andere Einflüsse zugänglich ist – der Sub-Baum versteckt sich eben im Schatten. Nimmt man es ganz genau, so ist der Shadow-Baum nicht wirklich ein Sub-Baum des Elements, sondern steht komplett außerhalb des normalen DOM und wird nur mit dem Element verbunden.
Ein bekanntes Beispiel für Shadow DOM sind die Bausteine des <progress>
-Elements. In den meisten Browsern ist der Fortschrittsbalken über normales HTML umgesetzt, aber da es sich eben um Shadow DOM handelt, sieht man von diesem Unterbau nichts, wenn man das Element inspiziert. Und auch selbstgebautes Shadow DOM ist möglich! Im folgenden Codeschnipsel wird ein Shadow-DOM-Baum in ein ganz normales Element eingehängt. Dieses Shadow DOM erscheint dann wie ein fester Bestandteil des Elements, so wie die Steuerungselemente in einem HTML5-Videoplayer:
var widget = document.createElement('div'); var shadowRoot = widget.createShadowRoot(); var content = document.createElement('p'); content.innerHTML = 'Hallo Welt!'; shadowRoot.appendChild(content); document.body.appendChild(widget);
Es ist klar, dass für Web Components, die ja ein komplexes Innenleben in selbstgebauten HTML-Elementen umsetzen wollen, Shadow DOM eine ganz zentrale Technologie darstellt.
HTML Imports
HTML Imports erlauben das Laden von HTML-Dokumenten in andere HTML-Dokumente:
<link rel="import" href="plugin.html">
Das ganze funktioniert nicht direkt wie z.B. ein include()
in PHP o.Ä., sondern eher ein bisschen wie <template>
-Elemente. Der Browser lädt das verlinkte Dokument herunter, liest es ein, lädt alle dort referenzierten Ressourcen (CSS, JS etc.) und verarbeitet das HTML. Das Resultat wird aber nicht einfach an Ort und Stelle in die Seite gekippt, sondern in der DOM-Eigenschaft import
bereitgestellt. Hier findet man ein komplettes Document
-Objekt für die geladene Ressource vor und kann sich nach Herzenslust daran bedienen: enthaltene Templates klonen, eingebundene Scripts ausführen – was auch immer gerade anliegt.
Im Web-Component-Kontext kann ein solcher Import alle Daten für ein Element enthalten: die nötigen Templates, das Script zum Anmelden eines neuen Tags und was sonst noch so gebraucht wird. Für alle, die neue Elemente nur benutzen und nicht unbedingt selbst schreiben wollen, reduziert sich also der gesamte Arbeitsaufwand auf das Einbinden eines einzigen <link>
-Elements (und natürlich das Einbinden des Importierten Custom Elements selbst).
Diverse weitere Technologien
Es gibt noch diverse weitere neue Browser-APIs, die oft mit Web Components assoziiert werden – Object.observe(), Scoped Style Sheets und viele weitere. Inwiefern man das alles braucht, kommt ein bisschen darauf an, wie genau man an Web Components herangeht, denn theoretisch führen viele Wege nach Rom – im Prinzip auch heute schon.
Was kann man heutzutage schon mit Web Components anfangen?
Den meisten Browsern fehlen noch die meisten Technologien für Web Components. In Chrome wird ab Version 36 so gut wie alles fast komplett implementiert sein, aber auch nur fast – und von den anderen Browsern wollen wir lieber gar nicht reden. Allerdings lässt sich mit einer Mixtur aus Polyfill und Abstraktion das Meiste durchaus schon hinkriegen. Dinge wie ein <google-map>
-Element sind keine Papiertiger, sondern tatsächlich heute schon exakt so benutzbare Web Components. Und die Benutzung und Erstellung solcher Components passiert heute schon auf eine Weise, die recht nah an der zukünftigen Alltagspraxis sein dürfte. Natürlich braucht man heutzutage Polyfills und Libraries, doch die wird man auch in 5 Jahren noch brauchen – selbst wenn jeder Browser alle Web-Component-APIs vollständig unterstützt.
Nicht vergessen: „Web Components“ ist nur ein Überbegriff für viele verschiedene Technologien, die das Erstellen eigener Komponenten ermöglichen. Diese Technologien sind aber nicht allein auf den Komponenten-Bau ausgerichtet, sondern man kann sie für viele verschiedene Dinge benutzen. Es braucht immer noch einen JavaScript-Layer, der die Einzelteile zu einer komfortablen Component-API zusammenknotet. Entsprechend wird man auch in fernster Zukunft eine JavaScript-Library brauchen, die das Erstellen eigener Elemente mit Shadow DOM und Konsorten vereinfacht, eine Art jQuery für Eigenbau-Elemente. Heutzutage kommt auf diese Library einfach noch ein Haufen Polyfills obendrauf. Hat man aber einerseits die Polyfills, andererseits die Library und keine Angst vor experimenteller Software, so kann man heutztage durchaus schon seine eigenen Elemente bauen und in den meisten modernen Browsern herumspielen. Wie genau das konkret funktioniert, sehen wir dann in Teil 2 dieser kleinen Serie.
Kommentare (6)
Manuel ¶
3. Juni 2014, 09:40 Uhr
Bei den Imports kann man noch erwähnen, dass man als Autor selbst dafür verantwortlich ist das die Styles aus der Import Datei nicht das Hauptdokument beeinflussen.
Bastelt man sich einfach eine Datei mit Styles und importiert diese über das link-tag, verhalten diese sich dann wie Styles aus einer gewöhnlichen CSS-Datei.
Tilman ¶
4. Juni 2014, 08:14 Uhr
Wie wird das verhindert?
Peter Kröner ¶
4. Juni 2014, 10:11 Uhr
Ersten kann man (als Component-Autor) kann einen Spezial-Selektor als Namespace für „nur innerhalb meines Elements“ verwenden. Zweitens versucht Polymer schon, per
<link>
eingebundene Stylesheets zu inlinen, wenn sie denn im<template>
-Element stehen (siehe Docs). Man darf die Styles nur eben nicht ganz oben bei den HTML Imports einbinden.Tilman ¶
6. Juni 2014, 15:43 Uhr
Das klingt schlüssig, Danke. Wie verhält es sich mit Styles, und womöglich auch Javascript, aus dem Hauptdokuments? Muss davon ausgegangen werden, dass diese in die Web Component überschwappen und dort dann recht umfangreiche Resets nötig sind?
Peter Kröner ¶
6. Juni 2014, 17:33 Uhr
Jain. Selektoren können die Grenze zwischen Shadow DOM und Haupt-Dokument nicht überschreiten. Was im einem Bereich deklariert wurde, betrifft nicht den anderen Bereich. Allerdings werden Styles weiterhin vererbt, d.h. ein
body { color:red; }
im Haupt-Dokument macht auch den Text im Shadow DOM rot, sofern es dort nicht lokal Regeln dagegen gibt.Sehr gute Frage, werde ich ausführlicher und mit Beispiel nochmal im nächsten Fragen-Blogpost beantworten.
Tilman ¶
7. Juni 2014, 14:47 Uhr
Toll! Dann freue ich mich noch mehr auf den nächsten Fragen-Blogpost ;)