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 haben wir jedoch eine Explosion von MVC-Frameworks erlebt. Sie unterscheiden sich alle ein wenig, aber die meisten haben eine gemeinsame Mechanik für die 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, beim Laden der Seite aber nicht verwendet werden. Sie können aber später zur Laufzeit instanziiert werden. 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)

Feature-Erkennung

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. Das Script wird nicht ausgeführt, Bilder werden nicht geladen, Audio wird nicht wiedergegeben, bis die Vorlage verwendet wird.

  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 Stellen verwendet werden kann, die vom HTML-Parser nicht zulässig sind, mit Ausnahme von untergeordneten Elementen des Inhaltsmodells. 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 erstellen Sie dazu mit document.importNode() eine Deepcopy der .content. Das Attribut .content ist ein schreibgeschütztes DocumentFragment, das den Hauptteil 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 Script

Dieses Beispiel zeigt die Inertie von Vorlageninhalten. Die <script> wird nur ausgeführt, wenn die Schaltfläche gedrückt wird, wodurch die Vorlage gestempelt 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 lässt sich nicht skalieren, es wird schnell unordentlich und die Babys fangen an zu weinen. So entstand auch XSS. <template> zur Rettung.

Es ist sinnvoller, direkt mit dem DOM zu arbeiten, indem Sie dem Shadow-Root Inhalt der Vorlage 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, 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.
  • Bei verschachtelten Vorlagen ist Vorsicht geboten. Sie verhalten sich nicht wie erwartet. 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 bei verschachtelten Vorlagen auch die untergeordneten Vorlagen 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 für die Erstellung wiederverwendbarer Vorlagen entwickelt. Im Folgenden finden Sie zwei häufige Beispiele, die mir begegnet sind. Ich füge sie in diesem Artikel zum Vergleich hinzu.

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. Das ist gut. 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 trotzdem eine Netzwerkanfrage für das Bild gesendet.
  • Umständliches Styling und Design: Eine Einbettungsseite muss allen CSS-Regeln das Präfix #mytemplate vorangestellt haben, um die 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: Script überladen

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-Dienstprogramm demonstrierte. 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:

  • Nichts wird gerendert: Der Browser rendert diesen Block nicht, da <script> standardmäßig display:none 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? Das Ergebnis war, dass querySelector()/querySelectorAll() der Plattform hinzugefügt wurde. Eine klare Entscheidung, 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, finde ich das toll.

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