タグ付きテンプレート実践 — HTML整形・翻訳(i18n)・スタイル生成
タグ付きテンプレートは 文字列分解 → ロジックを挟む のに最適で、HTML生成の安全化、簡易翻訳、CSS-in-JS の軽い実装などにぴったり使えます。以下に実用的でコピーして使えるサンプルを 3つ 用意します。各サンプルは説明 → 実装 → 使い方(例) → 注意点の順で短く示します。
1. HTML整形(自動エスケープ & 危険な挿入の防止)
目的:ユーザー入力などを埋め込む際に XSS を防ぎつつ、テンプレートで分かりやすく HTML を組み立てる。
// --- helper: HTML エスケープ ---
function escapeHTML(str) {
if (str == null) return '';
return String(str).replace(/[&<>"']/g, c => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[c]));
}
// --- tagged template: html ---
// strings: テンプレート文字列のリテラル部分の配列
// values: 埋め込み値の配列
function html(strings, ...values) {
// 値をエスケープして結合
let result = '';
for (let i = 0; i < strings.length; i++) {
result += strings[i];
if (i < values.length) {
// 値が安全に埋め込まれる(デフォルトエスケープ)
result += escapeHTML(values[i]);
}
}
return result;
}
// --- 利用例 ---
const user = { name: "<script>alert('X')</script>", bio: "JS好き\n改行あり" };
const node = html`
<div class="user">
<h2>${user.name}</h2>
<pre>${user.bio}</pre>
</div>
`;
console.log(node);
// 生成 HTML は安全にエスケープされるので DOM に直接入れても安全性が高くなる
// document.body.innerHTML = node;
JavaScript注意:
- デフォルトで全てエスケープする実装。安全第一。
- 意図的に「この値は生HTML」としたい場合は
unsafe()のようなホワイトリスト関数を用意して別扱いにする(でも慎重に)。
例(unsafe を許すパターン):
function raw(v) { return { __raw: String(v) }; }
function htmlAllowRaw(strings, ...values) {
let r = '';
for (let i = 0; i < strings.length; i++) {
r += strings[i];
if (i < values.length) {
const v = values[i];
r += (v && v.__raw) ? v.__raw : escapeHTML(v);
}
}
return r;
}
JavaScript2. 翻訳(i18n)タグ付きテンプレート — 簡易版
目的:テンプレートの定型文をキー化して置換、かつ ${} 中の値をローカライズ対応で差し込む。実務では複雑な plural/locale が必要だが、まずは基本形。
// --- 簡易翻訳辞書(実運用は外部ファイル/JSON) ---
const messages = {
en: {
"greet": "Hello, ${name}! You have ${count} new messages.",
"items": "You have ${count} item(s)."
},
ja: {
"greet": "こんにちは、${name}さん! ${count} 件の新着があります。",
"items": "アイテムが ${count} 個あります。"
}
};
// --- translator tagged template ---
// 使い方: t`greet`({ name: 'Halu', count: 3 })
function t(strings, ..._vals) {
// このタグは `t\`message_key\`` のように使うことを想定(templates の中身は1要素)
const key = strings.join('');
return (params = {}, locale = 'en') => {
let template = (messages[locale] && messages[locale][key]) || messages['en'][key] || key;
// テンプレート中の ${...} を params によって置換(安全のためエスケープしても良い)
return template.replace(/\$\{([^}]+)\}/g, (_, expr) => {
// ここではシンプルに params[expr.trim()] を返す(式評価は避ける)
const name = expr.trim();
return params[name] == null ? '' : params[name];
});
};
}
// --- 利用例 ---
const greetEn = t`greet`({ name: 'Halu', count: 5 }, 'en');
const greetJa = t`greet`({ name: 'ハル', count: 2 }, 'ja');
console.log(greetEn); // Hello, Halu! You have 5 new messages.
console.log(greetJa); // こんにちは、ハルさん! 2 件の新着があります。
JavaScript注意・拡張案:
- 実務では plural(1個 vs 複数)の処理、日付・数値のローカライズが必要 →
IntlAPI や i18next 等のライブラリと組み合わせる。 - ここではテンプレート内の式を評価していない(安全)。式評価が必要なら、明示的に許可されたキーだけにする。
3. スタイル生成(簡易 CSS-in-JS)タグ付きテンプレート
目的:テンプレートで CSS を書き、ユニークなクラス名を返して <style> に注入する小さなユーティリティ。軽量なコンポーネントスタイルに使える。
// --- ユニークなハッシュ(簡易) ---
function simpleHash(s) {
let h = 2166136261 >>> 0;
for (let i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i);
h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
}
return ('c' + (h >>> 0).toString(36));
}
// --- style tag handler ---
// usage: const className = style`color: ${color}; padding: 8px;`
const styleSheet = (function() {
const head = document.head || document.getElementsByTagName('head')[0];
const styleEl = document.createElement('style');
head.appendChild(styleEl);
return styleEl.sheet;
})();
function style(strings, ...values) {
// 結合して CSS テキストを作る
let css = '';
for (let i = 0; i < strings.length; i++) {
css += strings[i];
if (i < values.length) css += values[i];
}
// クラス名生成
const className = simpleHash(css);
// 既に追加済みか確認(重複防止)
const rule = `.${className} { ${css} }`;
// 簡易チェック:既存ルールに同一文字列があるか
const exists = Array.from(styleSheet.cssRules).some(r => r.cssText === rule);
if (!exists) {
styleSheet.insertRule(rule, styleSheet.cssRules.length);
}
return className;
}
// --- 利用例 ---
const btnClass = style`background: #007bff; color: white; padding: 8px 12px; border-radius: 6px;`;
const el = document.createElement('button');
el.className = btnClass;
el.textContent = 'クリック';
document.body.appendChild(el);
JavaScript注意:
- この実装は非常に簡易的(scoping、メディアクエリ、擬似セレクタ、重ね順、同名の CSS 変化などは考慮していない)。実践では
styled-components/emotionのような成熟ライブラリを推奨。 - テンプレートの
${}にオブジェクトを入れるとそのままtoString()される。必要ならユーティリティで値のバリデーションを行う。
よくあるパターン & 実用上のアドバイス
- デフォルトは「エスケープして安全に出力」 を基本にする(特に HTML 関連)。
- タグ付きテンプレートは分離責務が強い:テンプレートはプレゼンテーション、タグ関数はロジック(エスケープ、翻訳、注入)にする。
- テンプレートの中で式評価(eval 的な処理)は危険)。可能な限り明示的なキー/パラメータ置換にとどめる。
- パフォーマンス:大量に生成する場合はキャッシュ(翻訳済みテンプレート、生成済み CSS 等)を用意する。
- テスト:出力のユニットテスト(期待される HTML / CSS / 翻訳文字列)を用意する。


