クライアントサイド テンプレートの標準化
はじめに
テンプレートのコンセプトは、ウェブ開発では新しいものではありません。実際、Django(Python)、ERB / Haml(Ruby)、Smarty(PHP)などのサーバーサイドのテンプレート言語 / エンジンは長い間存在しています。しかし、ここ数年で MVC フレームワークが爆発的に増えています。これらはすべて若干異なりますが、ほとんどはプレゼンテーション レイヤ(da ビュー)をレンダリングするための共通のメカニズム(テンプレート)を共有しています。
実際のところ、テンプレートは素晴らしいものです。ぜひ、周囲に聞いてみてください。定義だけでも、心が温かくなります。
「…毎回再作成する必要がない…」どうでしょうか?余分な作業を避けるのは誰でも好きですよね。なぜなら デベロッパーが重要視していることを ウェブプラットフォームがネイティブにサポートしていない
答えは WhatWG HTML テンプレート仕様です。クライアントサイド テンプレート用の標準の DOM ベースのアプローチを記述する新しい <template>
要素を定義します。テンプレートを使用すると、HTML として解析され、ページの読み込み時には使用されませんが、実行時にインスタンス化できるマークアップのフラグメントを宣言できます。Rafael Weinstein 氏の言葉を引用すると、次のようになります。
ブラウザが何らかの理由で決して変更しないようにしたい 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>
でラップすると、いくつかの重要なプロパティが得られます。
コンテンツは、有効にされるまで実質的に不活性です。基本的に、マークアップは非表示の DOM であり、レンダリングされません。
テンプレート内のコンテンツには副作用はありません。テンプレートが使用されるまで、スクリプトが実行されない、画像が読み込まれない、音声が再生されない。
コンテンツはドキュメントに含まれていないものと見なされます。メインページで
document.getElementById()
やquerySelector()
を使用しても、テンプレートの子ノードは返されません。テンプレートは、
<head>
、<body>
、<frameset>
内の任意の場所に配置でき、これらの要素で許可されている任意のタイプのコンテンツを含めることができます。「どこでも」とは、HTML パーサーが許可しない場所でも<template>
を安全に使用できることを意味します。コンテンツモデルの子要素を除くすべての場所で使用できます。また、<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);
テンプレートをスタンプすると、そのコンテンツが「公開」されます。この例では、コンテンツがクローン化され、画像リクエストが送信され、最終的なマークアップがレンダリングされます。
デモ
例: 無効なスクリプト
この例は、テンプレート コンテンツの不活性を示しています。<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 テンプレートへの道のりは長い道のりでした。長年にわたり、再利用可能なテンプレートを作成するための巧妙な方法をいくつか考案してきました。よく遭遇した問題を 2 つ紹介します。 この記事では、比較のためにそれらも記載しています。
方法 1: オフスクリーン DOM
これまでユーザーが長い間使用されてきた手法の 1 つは、「画面外」の 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>
をオーバーロードして、そのコンテンツを文字列として操作することもできます。2008 年に John Resig 氏が Micro Templating ユーティリティで初めてこのことを示したようです。現在では、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 年のハックが不要になることです。ウェブ作成プロセス全体をより健全で、メンテナンスしやすく、機能豊かにすることは、常に良いことです。
参考情報
- WhatWG の仕様
- ウェブ コンポーネントの概要
- <web>コンポーネント</web>(動画) - 私自身による非常に包括的なプレゼンテーションです。