Neues HTML-Vorlagen-Tag

Clientseitige Vorlagen standardisieren

Einleitung

Das Konzept der Vorlagen ist in der Webentwicklung nicht neu. Tatsächlich gibt es schon seit Langem serverseitige Vorlagensprachen/Engines wie Django (Python), ERB/Haml (Ruby) und Smarty (PHP). In den letzten Jahren hat sich jedoch eine Explosion von MVC-Frameworks entwickelt. Sie unterscheiden sich geringfügig, haben aber in den meisten Fällen eine gemeinsame Methode zum Rendern der Präsentationsebene (auch da-Ansicht genannt): Vorlagen.

Seien wir ehrlich. Vorlagen sind fantastisch. Stell dir ein paar Fragen. Schon die Definition sorgt dafür, dass du dich warm und gemütlich fühlst:

„...muss nicht jedes Mal neu erstellt werden...“ Ich weiß zwar nichts über Sie, aber ich liebe es, Zusatzaufwand zu vermeiden. Warum bietet die Webplattform keine native Unterstützung für etwas, das für Entwickelnde wichtig ist?

Die WhatWG-HTML-Vorlagenspezifikation ist die Antwort. Darin wird ein neues <template>-Element definiert, das einen standardmäßigen DOM-basierten Ansatz für clientseitige Vorlagen beschreibt. Mit Vorlagen kannst du Markup-Fragmente deklarieren, die als HTML geparst werden, beim Seitenaufbau nicht verwendet werden, aber später zur Laufzeit instanziiert werden können. Um Rafael Weinstein zu zitieren:

Sie sind ein Ort, an dem Sie eine große Menge an HTML platzieren, an der sich der Browser überhaupt nicht ablenken soll ... aus irgendeinem Grund.

Rafael Weinstein (Verfasser der Spezifikation)

Funktionserkennung

Erstellen Sie zum Erkennen von <template> das DOM-Element und prüfen Sie, ob die Eigenschaft .content vorhanden ist:

function supportsTemplate() {
    return 'content' in document.createElement('template');
}

if (supportsTemplate()) {
    // Good to go!
} else {
    // Use old templating techniques or libraries.
}

Vorlageninhalte deklarieren

Das HTML-Element <template> steht für eine Vorlage in Ihrem Markup. Sie enthält „Vorlageninhalte“, also im Wesentlichen inerte Chunks eines klonbaren DOMs. Vorlagen sind ein Gerüst, das du während der gesamten Lebensdauer deiner App verwenden (und wiederverwenden) kannst.

Deklarieren Sie zum Erstellen von Inhalten aus Vorlagen Markup und binden Sie es in das <template>-Element ein:

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

Die Säulen

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

  1. Sein Inhalt ist so lange inaktiv, bis er aktiviert wird. Ihr Markup ist im Wesentlichen ein ausgeblendetes DOM und wird nicht gerendert.

  2. Inhalte in einer Vorlage haben keine Nebeneffekte. Skript wird nicht ausgeführt, Bilder werden nicht geladen, Audio wird nicht wiedergegeben, bis die Vorlage verwendet wird.

  3. Es wird davon ausgegangen, dass der Inhalt nicht im Dokument enthalten ist. Wenn Sie document.getElementById() oder querySelector() auf der Hauptseite verwenden, werden keine untergeordneten Knoten einer Vorlage zurückgegeben.

  4. Vorlagen können an beliebiger Stelle innerhalb von <head>, <body> oder <frameset> platziert werden und können beliebige Inhaltstypen enthalten, die in diesen Elementen zulässig sind. „Beliebig“ bedeutet, dass <template> sicher an Stellen verwendet werden kann, an denen der HTML-Parser dies nicht erlaubt – außer bei allen untergeordneten Inhaltsmodellen. 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 nicht gerendert. Am einfachsten ist es, eine tiefe Kopie der .content mit document.importNode() zu erstellen. Das Attribut .content ist ein schreibgeschütztes DocumentFragment-Objekt, das 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);

Nach dem Ausstreichen einer Vorlage wird ihr Inhalt veröffentlicht. In diesem speziellen Beispiel wird der Inhalt geklont, die Bildanfrage wird gestellt und das endgültige Markup wird gerendert.

Demos

Beispiel: Inaktives Skript

Dieses Beispiel zeigt die Trägheit von Vorlageninhalten. Das <script> wird nur ausgeführt, wenn die Schaltfläche angeklickt wird. Dadurch wird die Vorlage gestanzt.

<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 mit einem Host an, indem sie einen Markup-String auf .innerHTML setzen:

<div id="host"></div>
<script>
  var shadow = document.querySelector('#host').createShadowRoot();
  shadow.innerHTML = '<span>Host node</span>';
</script>

Das Problem bei diesem Ansatz besteht darin, dass je komplexer das Shadow DOM wird, desto mehr String-Verkettungen werden ausgeführt. Sie skaliert nicht, alles geht schnell chaotisch und die Babys fangen an zu weinen. Dieser Ansatz ist auch die Geburtsstunde von XSS. <template>.

Vereinfachter wäre, direkt mit DOM zu arbeiten, indem Vorlageninhalte an einen Schattenstamm anhängt:

<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>

Alles klar

Hier sind ein paar Probleme, die mir bei der Verwendung von <template> aufgefallen sind:

  • Wenn Sie modpagespeed verwenden, achten Sie auf diesen Fehler. Vorlagen, die Inline-<style scoped> definieren, werden mit den CSS-Umschreibregeln von PageSpeed in den Vordergrund verschoben.
  • Es gibt keine Möglichkeit, eine Vorlage vorab zu rendern. Das bedeutet, dass Sie weder Assets vorab laden, JS verarbeiten noch anfängliche CSS-Dateien herunterladen können. Das gilt sowohl für den Server als auch für den Client. Eine Vorlage wird erst dann gerendert, wenn sie live geht.
  • Seien Sie bei verschachtelten Vorlagen vorsichtig. Sie verhalten sich nicht erwartungsgemäß. Beispiel:

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

    Wenn Sie die äußere Vorlage aktivieren, werden die inneren Vorlagen nicht aktiviert. Das heißt, für verschachtelte Vorlagen müssen die untergeordneten Elemente auch manuell aktiviert werden.

Der Weg zu einem Standard

Vergessen wir nicht, woher wir kommen. Der Weg zu standardbasierten HTML-Vorlagen war lang. Im Laufe der Jahre haben wir einige raffinierte Tricks zur Erstellung wiederverwendbarer Vorlagen entwickelt. Nachfolgend sind zwei häufige Fragen aufgeführt, die mir begegnet sind. Ich füge sie zum Vergleich in diesen Artikel ein.

Methode 1: Offscreen-DOM

Ein Ansatz, den viele Menschen bereits seit Langem verwenden, besteht darin, ein „Offscreen“-DOM zu erstellen und es mithilfe des Attributs hidden oder display:none auszublenden.

<div id="mytemplate" hidden>
  <img src="logo.png">
  <div class="comment"></div>
</div>

Diese Methode funktioniert zwar, hat aber auch einige Nachteile. Das Verfahren im Überblick:

  • DOM wird verwendet: der Browser kennt das DOM. Das ist gut. Wir können es ganz einfach klonen.
  • Es wird nichts gerendert: Durch das Hinzufügen von hidden wird verhindert, dass der Block angezeigt wird.
  • Nicht inaktiv: Auch wenn der Inhalt verborgen ist, wird trotzdem eine Netzwerkanfrage für das Bild gestellt.
  • Mühelose Gestaltung und Gestaltung – Eine Einbettungsseite muss allen CSS-Regeln das Präfix #mytemplate voranstellen, damit Stile bis zur Vorlage angewendet werden können. Dies ist zerbrechlich und es gibt keine Garantien, dass zukünftige Namenskonflikte nicht auftreten werden. Ein Beispiel: Sie wird entfernt, wenn die eingebettete Seite bereits ein Element mit dieser ID enthält.

Methode 2: Überlastung des Skripts

Bei einer anderen Methode wird <script> überlastet und der Inhalt als String bearbeitet. John Resig hat dies 2008 wahrscheinlich als erstes mit seinem Micro Templating-Dienstprogramm beobachtet. Jetzt gibt es viele andere, darunter auch einige neue Kinder wie handlebars.js.

Beispiel:

<script id="mytemplate" type="text/x-handlebars-template">
  <img src="logo.png">
  <div class="comment"></div>
</script>

Das Verfahren im Überblick:

  • Es wird nichts gerendert: Der Browser rendert diesen Block nicht, da <script> standardmäßig display:none ist.
  • Inert: Der Browser parst den Skriptinhalt nicht als JS, weil sein Typ auf einen anderen Wert als "text/javascript" festgelegt ist.
  • Sicherheitsprobleme: Wir empfehlen, .innerHTML zu verwenden. Das Parsen von vom Nutzer bereitgestellten Daten während der Laufzeit kann leicht zu XSS-Sicherheitslücken führen.

Fazit

Erinnern Sie sich, als jQuery die Arbeit mit DOM wirklich einfach gemacht hat? Dadurch wurden querySelector()/querySelectorAll() der Plattform hinzugefügt. Offensichtlicher Sieg, oder? Eine Bibliothek machte das Abrufen von DOM mit CSS-Selektoren bekannt. Später wurde sie von Standards übernommen. Es funktioniert nicht immer so, aber ich freu mich, wenn das so ist.

Ich denke, bei <template> handelt es sich um einen ähnlichen Fall. Sie standardisiert die Art und Weise, wie wir clientseitige Vorlagen erstellen, aber noch wichtiger ist, dass unsere Hacks von 2008 nicht mehr erforderlich sind. Es ist immer eine gute Sache, den gesamten Prozess des Web-Authoring-Prozesses verständlicher, wartungsfreundlicher und umfassender zu gestalten.

Zusätzliche Ressourcen