HTML の新しいテンプレートタグ

クライアントサイド テンプレートの標準化

はじめに

テンプレートという概念はウェブ開発では新しいものではありません。実際、Django(Python)、ERB/Haml(Ruby)、Smarty(PHP)などのサーバーサイドのテンプレート言語/エンジンは、以前から存在しています。しかし ここ数年で MVC フレームワークは 爆発的に増加していますこれらはすべて若干異なりますが、プレゼンテーション レイヤ(別名 da ビュー)をレンダリングするための共通のメカニズムであるテンプレートを共有しています。

実際のところ、テンプレートは非常に便利です。いろいろと聞いてみてください。この定義でさえも、暖かく心地よく感じることができます。

「...毎回作り直す必要はありません...」では、ウェブ プラットフォームには、デベロッパーが明らかに重視しているものに対するネイティブ サポートがないのはなぜでしょうか。

その答えとなるのが WhatWG HTML テンプレート仕様です。ここでは、クライアントサイドのテンプレートの標準的な DOM ベースのアプローチを記述する新しい <template> 要素が定義されています。テンプレートを使用すると、HTML として解析されるマークアップのフラグメントを宣言できます。このフラグメントは、ページの読み込み時には使用されず、後でランタイム時にインスタンス化できます。ラファエル ワインスタインの言葉を引用すると、次のようになります。

ブラウザには何もかも処理してほしくない大きな HTML の集まりである場所です

Rafael Weinstein(仕様作成者)

特徴検出

<template> を機能検出するには、DOM 要素を作成し、.content プロパティが存在することを確認します。

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

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

テンプレートのコンテンツを宣言する

HTML <template> 要素は、マークアップのテンプレートを表します。これには「テンプレート コンテンツ」、つまり基本的にはクローン可能な DOM の不活性なチャンクが含まれます。テンプレートは、アプリの全期間を通じて使用(および再利用)できる足場の要素と考えることができます。

テンプレート化されたコンテンツを作成するには、マークアップを宣言して <template> 要素でラップします。

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

<template> でコンテンツをラップすると、いくつかの重要なプロパティが得られます。

  1. そのコンテンツは、有効化されるまで実質的に不活性です。基本的に、マークアップは非表示の DOM であり、レンダリングされません。

  2. テンプレート内のコンテンツに副作用が生じることはありません。テンプレートが使用されるまでは、スクリプトが実行されない、画像が読み込まれない、音声が再生されないといったケースが考えられます。

  3. コンテンツがドキュメントに含まれていないとみなされる。メインページで document.getElementById() または querySelector() を使用しても、テンプレートの子ノードは返されません。

  4. テンプレートは、<head><body><frameset> 内の任意の場所に配置でき、これらの要素で許可されているすべてのタイプのコンテンツを含めることができます。「anywhere」は、<template> が HTML パーサーによって許可されない場所で、コンテンツ モデル以外のすべての場所で安全に使用できることを意味します。<table> または <select> の子として配置することもできます。

<table>
  <tr>
    <template id="cells-to-repeat">
      <td>some content</td>
    </template>
  </tr>
</table>

テンプレートの有効化

テンプレートを使用するには、テンプレートを有効にする必要があります。そうしないと、コンテンツが表示されません。 最も簡単な方法は、document.importNode() を使用して .content のディープコピーを作成することです。.content プロパティは、テンプレートの要点を含む読み取り専用の DocumentFragment です。

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

テンプレートを打ち出すと、そのコンテンツが「公開」されます。この例では、コンテンツのクローンを作成し、画像リクエストを行って、最終的なマークアップをレンダリングしています。

デモ

例: Inert スクリプト

この例では、テンプレート コンテンツの不活性を示しています。<script> はボタンが押されたときにのみ実行され、テンプレートがスタンプアウトされます。

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

例: テンプレートから Shadow DOM を作成する

ほとんどの場合、マークアップの文字列を .innerHTML に設定することで、Shadow DOM をホストに接続します。

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

このアプローチの問題は、Shadow DOM が複雑になるほど、実行する文字列の連結量が増えることです。体重は増減せず、物はすぐに散らかり、赤ちゃんは泣き始めます。このアプローチは、XSS が生まれた最初のきっかけでもあります。<template> で解決します。

より現実的な方法は、シャドウルートにテンプレート コンテンツを追加して、DOM を直接操作することです。

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

解決済み

ここでは、実際に <template> を使用するときに直面したいくつかの問題点を紹介します。

  • modpagespeed を使用している場合は、このバグに注意してください。インライン <style scoped> を定義するテンプレートの多くは、PageSpeed の CSS 書き換えルールによって置き換えられます。
  • テンプレートを「事前レンダリング」することはできません。アセットのプリロード、JS の処理、初期 CSS のダウンロードなどはできません。これはサーバーとクライアントの両方に該当します。テンプレートが表示されるのは、テンプレートが有効になったときのみです。
  • ネストされたテンプレートは慎重に使用してください。期待どおりに動作しません。次に例を示します。

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

    外部テンプレートを有効にしても、内部テンプレートは有効になりません。つまり、ネストされたテンプレートでは、子も手動で有効にする必要があります。

標準への道

どこから来たかを忘れてはいけません。標準ベースの HTML テンプレートへの道のりは長いものでしたGoogle は長年にわたり、再利用可能なテンプレートを作成するための 優れたコツを生み出してきました。私がこれまで遭遇した一般的なエラーを 2 つご紹介します。 比較のために、これらもこの記事に含めています。

方法 1: 画面外 DOM

「画面外」の DOM を作成し、hidden 属性や display:none を使用して非表示にするというアプローチが、長年にわたり使用されてきました。

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

この手法は有効ですが、いくつかの欠点があります。この手法の概要:

  • DOM の使用 - ブラウザは DOM を認識しています。上手です。簡単にクローンを作成できます
  • 何もレンダリングされませんhidden を追加すると、ブロックが表示されなくなります。
  • 不活性でない - コンテンツが非表示になっていても、画像に対するネットワーク リクエストは引き続き行われます。
  • 煩わしいスタイル設定とテーマ設定 - スタイルの適用範囲をテンプレートに限定するには、埋め込みページですべての CSS ルールの先頭に #mytemplate を付ける必要があります。これは脆弱であり、今後名前の競合が発生することがない保証はありません。たとえば、エンベディング ページにその ID の要素がすでに存在する場合は、ホースされます。

方法 2: スクリプトをオーバーロードする

また、<script> をオーバーロードし、そのコンテンツを文字列として操作する方法もあります。John Resig は、おそらく 2008 年にマイクロ テンプレート ユーティリティでこれを最初に示しました。このブロックには handlebars.js のような新しい子を含め、他にも多くの機能があります。

次に例を示します。

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

この手法の概要:

  • 何もレンダリングされません。デフォルトでは <script>display:none であるため、ブラウザはこのブロックをレンダリングしません。
  • 不活性 - タイプが「text/javascript」以外に設定されているため、ブラウザはスクリプトの内容を JS として解析しません。
  • セキュリティの問題 - .innerHTML の使用を推奨します。ユーザー提供データのランタイム文字列解析は、XSS 脆弱性につながる可能性があります。

おわりに

jQuery によって DOM の操作が非常にシンプルになったことを覚えていますか?その結果、querySelector()/querySelectorAll() がプラットフォームに追加されました。明らかに勝利だよね。ライブラリでは CSS セレクタを使用した DOM の取得が普及し その後標準に採用されました常にそうできるとは限りませんが、そうできればとても気に入っています。

<template> も同じようなケースだと思います。これにより、クライアントサイドのテンプレート化方法を標準化できますが、さらに重要な点として、2008 年のハッキングが不要になります。私の書籍では、ウェブ オーサリング プロセス全体をよりわかりやすく、メンテナンスしやすく、機能満載にすることは、常に良いことです。

参考情報