Neues HTML-Vorlagen-Tag

Clientseitige Vorlagen standardisieren

Einführung

Das Konzept der Vorlagen ist in der Webentwicklung nicht neu. Tatsächlich gibt es serverseitige Template-Sprachen/-Engines wie Django (Python), ERB/Haml (Ruby) und Smarty (PHP) schon seit langem. In den letzten Jahren hat sich jedoch eine explosionsartige Zunahme von MVC-Frameworks entwickelt. Sie unterscheiden sich alle ein wenig, aber die meisten haben eine gemeinsame Funktion zum Rendern der Darstellungsebene (auch Ansicht genannt): Vorlagen.

Seien wir ehrlich. Vorlagen sind fantastisch. Fragen Sie ruhig herum. Selbst die Definition klingt gemütlich:

„…muss nicht jedes Mal neu erstellt werden…“ Ich weiß nicht, wie es Ihnen geht, aber ich mag es, unnötige Arbeit zu vermeiden. Warum gibt es dann keine native Unterstützung für etwas, das für Entwickler offensichtlich wichtig ist?

Die Antwort lautet WhatWG HTML Templates specification. 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 und beim Seitenaufbau nicht verwendet werden, aber später während der Laufzeit instanziiert werden können. So zitieren Sie Rafael Weinstein:

Sie sind ein Ort, an dem Sie eine große Menge HTML-Code platzieren können, mit dem der Browser aus irgendeinem Grund nicht in Berührung kommen soll.

Rafael Weinstein (Autor des Skripts)

Funktionserkennung

Wenn Sie <template> erkennen möchten, erstellen Sie das DOM-Element und prüfen Sie, ob die Property .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. Sie enthält „Vorlageninhalte“, also im Wesentlichen inerte DOM-Chunks, die geklont werden können. Stellen Sie sich Vorlagen als Gerüst vor, das Sie während der gesamten Lebensdauer Ihrer App verwenden und wiederverwenden können.

Wenn Sie einen Vorlageninhalt erstellen möchten, deklarieren Sie ein Markup und schließen Sie es in das Element <template> ein:

<template id="mytemplate">
    <img src="" alt="great image">
    <div class="comment"></div>
</template>

Die Säulen

Wenn wir Inhalte in <template> einschließen, erhalten wir einige wichtige Eigenschaften.

  1. Der Inhalt ist bis zur Aktivierung inaktiv. Im Grunde ist Ihr Markup ein ausgeblendetes DOM und wird nicht gerendert.

  2. Inhalte in einer Vorlage haben keine Nebenwirkungen. Script wird erst ausgeführt, wenn die Vorlage verwendet wird, Bilder werden nicht geladen oder Audioinhalte werden nicht abgespielt.

  3. Die Inhalte gelten als nicht im Dokument enthalten. Wenn Sie document.getElementById() oder querySelector() auf der Hauptseite verwenden, werden keine untergeordneten Knoten einer Vorlage zurückgegeben.

  4. Vorlagen können überall in <head>, <body> oder <frameset> platziert werden und beliebige Inhalte enthalten, die in diesen Elementen zulässig sind. „Überall“ bedeutet, dass <template> an Orten sicher verwendet werden kann, die der HTML-Parser nicht zulässt – mit Ausnahme von Content Model-Untergeordneten. 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>

Vorlagen aktivieren

Wenn Sie eine Vorlage verwenden möchten, müssen Sie sie aktivieren. Andernfalls werden die Inhalte nicht gerendert. Am einfachsten ist es, mit document.importNode() eine Deep-Kopie von .content zu erstellen. Das Attribut .content ist eine schreibgeschützte DocumentFragment, die die Eingeweide 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 ihre Inhalte veröffentlicht. In diesem Beispiel werden die Inhalte geklont, die Bildanfrage gestellt und das endgültige Markup gerendert.

Demos

Beispiel: inaktives Skript

Dieses Beispiel zeigt die Inertie von Vorlageninhalten. <script> wird nur ausgeführt, wenn die Schaltfläche gedrückt wird, wodurch die Vorlage ausgestempelt wird.

<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 binden 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 Stringkonkatenationen werden ausgeführt. Es ist nicht skalierbar, es wird schnell unübersichtlich und die Babys fangen an zu weinen. So entstand auch XSS. <template> zur Rettung.

Besser wäre es, direkt mit DOM zu arbeiten und Vorlageninhalte an einen Schattenstamm anzuhä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, beachten Sie diesen Fehler. Vorlagen, die Inline-<style scoped> definieren, können mit den CSS-Umschreibungsregeln von PageSpeed in den Head-Bereich verschoben werden.
  • Es gibt keine Möglichkeit, eine Vorlage zu „vorab zu rendern“, d. h., Sie können keine Assets vorab laden, JS verarbeiten, anfängliches CSS herunterladen usw. Das gilt sowohl für den Server als auch für den Client. Eine Vorlage wird nur dann gerendert, wenn sie veröffentlicht wird.
  • Seien Sie bei verschachtelten Vorlagen vorsichtig. Sie verhalten sich nicht wie erwartet. Beispiel:

    <template>
      <ul>
        <template>
          <li>Stuff</li>
        </template>
      </ul>
    </template>
    

    Wenn Sie die äußere Vorlage aktivieren, werden die inneren Vorlagen nicht aktiviert. Das bedeutet, dass verschachtelte Vorlagen auch manuell aktiviert werden müssen.

Der Weg zu einem Standard

Lasst uns nicht vergessen, woher wir kommen. Der Weg zu standardbasierten HTML-Vorlagen war lang. Im Laufe der Jahre haben wir einige ziemlich clevere Tricks zum Erstellen wiederverwendbarer Vorlagen entwickelt. Im Folgenden sind zwei häufige Probleme aufgeführt, die mir begegnet sind. Ich füge sie zum Vergleich in diesen Artikel ein.

Methode 1: Offscreen-DOM

Eine seit langem verwendete Methode besteht darin, ein „nicht sichtbares“ 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 Methode funktioniert zwar, hat aber einige Nachteile. So funktioniert diese Technik:

  • DOM verwenden: Der Browser kennt das DOM. Sie ist gut darin. Wir können es ganz einfach klonen.
  • Nichts wird gerendert: Wenn Sie hidden hinzufügen, wird der Block nicht angezeigt.
  • Nicht inaktiv: Auch wenn unsere Inhalte ausgeblendet sind, wird weiterhin eine Netzwerkanfrage für das Bild gestellt.
  • Aufwendiges Styling und Design: Eine Einbettungsseite muss allen CSS-Regeln das Präfix #mytemplate vorangestellt haben, um Stile auf die Vorlage einzugrenzen. Das ist jedoch nicht sehr robust und es gibt keine Garantie dafür, dass es in Zukunft nicht zu Namenskollisionen kommt. Es ist beispielsweise nicht möglich, wenn die Seite, auf der eingebettet werden soll, bereits ein Element mit dieser ID enthält.

Methode 2: Skript wird überlastet

Eine weitere Methode besteht darin, <script> zu überladen und den Inhalt als String zu manipulieren. John Resig war wahrscheinlich der Erste, der dies 2008 mit seinem Micro Templating Utility zeigte. Inzwischen gibt es viele andere, 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 Technik:

  • Es wird nichts gerendert: Der Browser rendert diesen Block nicht, da <script> standardmäßig auf display:none festgelegt ist.
  • Inert: Der Browser analysiert den Script-Inhalt nicht als JS, da sein Typ nicht auf „text/javascript“ festgelegt ist.
  • Sicherheitsprobleme: Es wird empfohlen, .innerHTML zu verwenden. Das Laufzeit-String-Parsing von von Nutzern bereitgestellten Daten kann leicht zu XSS-Sicherheitslücken führen.

Fazit

Erinnern Sie sich noch, als jQuery die Arbeit mit dem DOM kinderleicht gemacht hat? Als Ergebnis wurden der Plattform querySelector()/querySelectorAll() hinzugefügt. Eine klare Sache, oder? Eine Bibliothek, die das Abrufen des DOM mit CSS-Selektoren populär gemacht hat und später in Standards übernommen wurde. Das funktioniert nicht immer, aber wenn es funktioniert, ist es super.

Ich denke, <template> ist ein ähnlicher Fall. Dadurch wird die clientseitige Vorlagenerstellung standardisiert. Vor allem aber sind unsere Hacks von 2008 nicht mehr erforderlich. Der gesamte Web-Autorisierungsprozess ist in meinen Augen immer dann sinnvoll, wenn er nutzerfreundlicher, wartungsfreundlicher und funktionsreicher ist.

Zusätzliche Ressourcen