Standardisierung von clientseitigen Vorlagen
Einführung
Das Konzept von Vorlagen ist in der Webentwicklung nicht neu. Tatsächlich gibt es serverseitige Vorlagensprachen/-engines wie Django (Python), ERB/Haml (Ruby) und Smarty (PHP) schon seit langem. In den letzten Jahren sind jedoch unzählige MVC-Frameworks entstanden. Alle sind etwas unterschiedlich, aber die meisten haben einen gemeinsamen Mechanismus zum Rendern ihrer Präsentationsebene (auch „View“ genannt): Vorlagen.
Seien wir ehrlich. Vorlagen sind fantastisch. Fragen Sie ruhig nach. Schon die Definition klingt gemütlich:
„…muss nicht jedes Mal neu erstellt werden…“ Ich weiß nicht, wie es Ihnen geht, aber ich vermeide gern zusätzliche Arbeit. Warum fehlt dann auf der Webplattform die native Unterstützung für etwas, das Entwicklern offensichtlich wichtig ist?
Die Antwort ist die WhatWG-Spezifikation für HTML-Vorlagen. Es definiert ein neues <template>-Element, das einen standardmäßigen DOM-basierten Ansatz für clientseitige Vorlagen beschreibt. Mit Vorlagen können Sie Markup-Fragmente deklarieren, die als HTML geparst werden, beim Seitenaufbau nicht verwendet werden, aber später zur Laufzeit instanziiert werden können. Rafael Weinstein hat es so formuliert:
Sie sind ein Ort, an dem Sie einen großen HTML-Block platzieren können, den der Browser aus irgendeinem Grund überhaupt nicht verändern soll.
Rafael Weinstein (Autor der Spezifikation)
Feature-Erkennung
Wenn Sie <template> erkennen möchten, erstellen Sie das DOM-Element und prüfen Sie, ob das Attribut .content vorhanden ist:
function supportsTemplate() {
return 'content' in document.createElement('template');
}
if (supportsTemplate()) {
// Good to go!
} else {
// Use old templating techniques or libraries.
}
Vorlageninhalt deklarieren
Das HTML-Element <template> stellt eine Vorlage in Ihrem Markup dar. Es enthält „Vorlageninhalte“, im Wesentlichen inaktive Teile des klonbaren DOM.
Vorlagen sind wie Gerüste, die Sie während der gesamten Lebensdauer Ihrer App verwenden (und wiederverwenden) können.
Wenn Sie vorlagenbasierte Inhalte erstellen möchten, deklarieren Sie ein Markup und umschließen Sie es mit dem <template>-Element:
<template id="mytemplate">
<img src="" alt="great image">
<div class="comment"></div>
</template>
Die Säulen
Wenn wir Inhalte in ein <template>-Element einfügen, erhalten wir einige wichtige Eigenschaften.
Der Inhalt ist bis zur Aktivierung inaktiv. Im Grunde ist Ihr Markup ein ausgeblendetes DOM und wird nicht gerendert.
Inhalte in einer Vorlage haben keine Nebenwirkungen. Das Skript wird nicht ausgeführt, Bilder werden nicht geladen, Audio wird nicht abgespielt – bis die Vorlage verwendet wird.
Inhalte werden als nicht im Dokument enthalten betrachtet. Wenn Sie
document.getElementById()oderquerySelector()auf der Hauptseite verwenden, werden keine untergeordneten Knoten einer Vorlage zurückgegeben.Vorlagen können an einer beliebigen Stelle in
<head>,<body>oder<frameset>platziert werden und können jede Art von Inhalt enthalten, der in diesen Elementen zulässig ist. „Überall“ bedeutet, dass<template>an Stellen verwendet werden kann, die der HTML-Parser nicht zulässt – alle außer Inhaltsmodell-Unterelemente. Es kann auch als untergeordnetes Element von<table>oder<select>platziert werden:
<table>
<tr>
<template id="cells-to-repeat">
<td>some content</td>
</template>
</tr>
</table>
Vorlage aktivieren
Wenn Sie eine Vorlage verwenden möchten, müssen Sie sie aktivieren. Andernfalls wird der Inhalt nie gerendert.
Am einfachsten geht das, indem Sie mit document.importNode() eine tiefe Kopie des .content erstellen. Das Attribut .content ist ein schreibgeschütztes DocumentFragment, das den Inhalt der Vorlage enthält.
var t = document.querySelector('#mytemplate');
// Populate the src at runtime.
t.content.querySelector('img').src = 'logo.png';
var clone = document.importNode(t.content, true);
document.body.appendChild(clone);
Nachdem eine Vorlage erstellt wurde, werden die Inhalte „live geschaltet“. In diesem Beispiel wird der Inhalt geklont, die Bildanfrage gestellt und das endgültige Markup gerendert.
Demos
Beispiel: Inertes Skript
Dieses Beispiel zeigt die Trägheit von Vorlageninhalten. Der <script> wird nur ausgeführt, wenn die Taste gedrückt wird, um die Vorlage zu stempeln.
<button onclick="useIt()">Use me</button>
<div id="container"></div>
<script>
function useIt() {
var content = document.querySelector('template').content;
// Update something in the template DOM.
var span = content.querySelector('span');
span.textContent = parseInt(span.textContent) + 1;
document.querySelector('#container').appendChild(
document.importNode(content, true)
);
}
</script>
<template>
<div>Template used: <span>0</span></div>
<script>alert('Thanks!')</script>
</template>
Beispiel: Shadow DOM aus einer Vorlage erstellen
Die meisten Nutzer hängen Shadow DOM an einen Host an, indem sie einen Markup-String auf .innerHTML festlegen:
<div id="host"></div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.innerHTML = '<span>Host node</span>';
</script>
Das Problem bei diesem Ansatz ist, dass je komplexer Ihr Shadow DOM wird, desto mehr Stringverkettungen Sie vornehmen müssen. Es ist nicht skalierbar, es wird schnell unübersichtlich und Babys fangen an zu weinen. Dieser Ansatz ist auch der Grund, warum XSS überhaupt entstanden ist. <template> ist die Lösung.
Sinnvoller wäre es, direkt mit dem DOM zu arbeiten, indem Sie den Vorlageninhalt an einen Shadow-Root anhängen:
<template>
<style>
:host {
background: #f8f8f8;
padding: 10px;
transition: all 400ms ease-in-out;
box-sizing: border-box;
border-radius: 5px;
width: 450px;
max-width: 100%;
}
:host(:hover) {
background: #ccc;
}
div {
position: relative;
}
header {
padding: 5px;
border-bottom: 1px solid #aaa;
}
h3 {
margin: 0 !important;
}
textarea {
font-family: inherit;
width: 100%;
height: 100px;
box-sizing: border-box;
border: 1px solid #aaa;
}
footer {
position: absolute;
bottom: 10px;
right: 5px;
}
</style>
<div>
<header>
<h3>Add a Comment
</header>
<content select="p"></content>
<textarea></textarea>
<footer>
<button>Post</button>
</footer>
</div>
</template>
<div id="host">
<p>Instructions go here</p>
</div>
<script>
var shadow = document.querySelector('#host').createShadowRoot();
shadow.appendChild(document.querySelector('template').content);
</script>
Gotchas
Hier sind einige Fallstricke, auf die ich bei der Verwendung von <template> gestoßen bin:
- Wenn Sie modpagespeed verwenden, achten Sie auf diesen Fehler. Vorlagen, die Inline-
<style scoped>definieren, können mit den CSS-Umschreibungsregeln von PageSpeed in den Kopf verschoben werden. - Es gibt keine Möglichkeit, eine Vorlage vorab zu rendern. Das bedeutet, dass Sie keine Assets vorab laden, JS verarbeiten, anfängliches CSS herunterladen usw. können. Das gilt sowohl für den Server als auch für den Client. Eine Vorlage wird nur gerendert, wenn sie live geht.
Seien Sie bei verschachtelten Vorlagen vorsichtig. Sie verhalten sich nicht so, wie Sie es erwarten würden. Beispiel:
<template> <ul> <template> <li>Stuff</li> </template> </ul> </template>Durch das Aktivieren der äußeren Vorlage werden die inneren Vorlagen nicht aktiviert. Das bedeutet, dass verschachtelte Vorlagen erfordern, dass ihre untergeordneten Vorlagen ebenfalls manuell aktiviert werden.
Der Weg zu einem Standard
Wir sollten nicht vergessen, wo wir herkommen. Der Weg zu standardbasierten HTML-Vorlagen war lang. Im Laufe der Jahre haben wir einige clevere Tricks entwickelt, um wiederverwendbare Vorlagen zu erstellen. Im Folgenden finden Sie zwei häufige Beispiele, die mir begegnet sind. Ich habe sie in diesem Artikel zum Vergleich aufgeführt.
Methode 1: Offscreen-DOM
Ein Ansatz, der schon lange verwendet wird, besteht darin, ein „Offscreen“-DOM zu erstellen und es mit dem Attribut hidden oder display:none auszublenden.
<div id="mytemplate" hidden>
<img src="logo.png">
<div class="comment"></div>
</div>
Diese Technik funktioniert zwar, hat aber einige Nachteile. So funktioniert diese Methode:
- DOM verwenden: Der Browser kennt DOM. Das kann es gut. Wir können sie ganz einfach klonen.
- Es wird nichts gerendert: Durch das Hinzufügen von
hiddenwird verhindert, dass der Block angezeigt wird. - Nicht inaktiv: Obwohl unsere Inhalte ausgeblendet sind, wird trotzdem eine Netzwerkanfrage für das Bild gestellt.
- Mühsames Styling und Design: Auf einer Einbettungsseite müssen alle CSS-Regeln mit dem Präfix
#mytemplateversehen werden, damit die Stile auf die Vorlage beschränkt werden. Das ist fehleranfällig und es gibt keine Garantie, dass es in Zukunft nicht zu Namenskonflikten kommt. Wenn die einbettende Seite beispielsweise bereits ein Element mit dieser ID hat, haben wir ein Problem.
Methode 2: Script überladen
Eine andere Technik besteht darin, <script> zu überladen und den Inhalt als String zu bearbeiten. John Resig war wahrscheinlich der Erste, der dies 2008 mit seinem Micro-Templating-Dienstprogramm gezeigt hat.
Inzwischen gibt es viele weitere, darunter auch einige neue wie handlebars.js.
Beispiel:
<script id="mytemplate" type="text/x-handlebars-template">
<img src="logo.png">
<div class="comment"></div>
</script>
So funktioniert diese Methode:
- Es wird nichts gerendert: Der Browser rendert diesen Block nicht, da
<script>standardmäßigdisplay:noneist. - Inert: Der Browser parst den Scriptinhalt nicht als JS, da der Typ nicht auf „text/javascript“ festgelegt ist.
- Sicherheitsprobleme: Hier wird die Verwendung von
.innerHTMLempfohlen. Das Parsen von Strings mit vom Nutzer bereitgestellten Daten zur Laufzeit kann leicht zu XSS-Sicherheitslücken führen.
Fazit
Erinnern Sie sich noch, wie einfach die Arbeit mit dem DOM dank jQuery war? Das Ergebnis war, dass querySelector()/querySelectorAll() der Plattform hinzugefügt wurde. Das ist doch ein klarer Vorteil, oder? Eine Bibliothek hat das Abrufen von DOM mit CSS-Selektoren populär gemacht und Standards haben es später übernommen. Das klappt nicht immer, aber ich liebe es, wenn es funktioniert.
Ich denke, <template> ist ein ähnlicher Fall. Es standardisiert die Art und Weise, wie wir clientseitige Vorlagen erstellen, und macht vor allem unsere Hacks von 2008 überflüssig.
Meiner Meinung nach ist es immer gut, den gesamten Web-Authoring-Prozess vernünftiger, wartungsfreundlicher und funktionsreicher zu gestalten.
Zusätzliche Ressourcen
- WhatWG-Spezifikation
- Einführung in Web Components
- <web>components</web> (Video) – eine fantastisch umfassende Präsentation von mir.