タイトル: テンプレートエンジン
SEOタイトル: テンプレートエンジン完全ガイド
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li><strong>テンプレートエンジン</strong> = HTML のひな形に変数や制御構文を埋め込み、動的に最終 HTML を生成する仕組み</li><li>サーバ側: <strong>Blade (Laravel) / Twig (Symfony) / Smarty (PHP)</strong>、<strong>Jinja2 (Python) / Django Templates</strong>、<strong>Thymeleaf (Java) / FreeMarker</strong>、<strong>EJS / Pug / Handlebars / Mustache (Node.js)</strong></li><li>クライアント側: <strong>Vue / React JSX / Svelte</strong> のテンプレート系</li><li>静的サイト生成 (SSG): <strong>Eleventy / Hugo / Gatsby / Astro</strong></li><li>主な利点: <strong>HTML エスケープの自動化</strong>、ロジックとビューの分離、コンパイル キャッシュで高速化、レイアウト継承で DRY</li></ul></td></tr></table>
<h2>テンプレートエンジンとは</h2>
<p>テンプレートエンジン (Template Engine) は、<strong>HTML のひな形 (テンプレート)</strong> に動的なデータを差し込み、最終的な HTML を生成するソフトウェアです。例えば「ユーザー名」だけが違う 1000 通りのページを、1 個のテンプレートと 1000 件のデータから生成できます。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- テンプレート (Blade) -->
<h1>こんにちは、{{ $user->name }}さん</h1>
<p>あなたの注文は {{ $orders->count() }} 件です。</p>
@foreach ($orders as $order)
<li>{{ $order->title }} - {{ $order->price }}円</li>
@endforeach</code></pre>
<p>テンプレートエンジンを使わずに「素の PHP」で書くと <code><?php echo $user->name; ?></code> のように冗長になり、XSS 脆弱性を生みやすくなります。</p>
<h2>テンプレートエンジンの利点</h2>
<ul>
<li><strong>HTML エスケープの自動化</strong>: <code>{{ }}</code> で出力すると自動でエスケープされ XSS を防ぐ</li>
<li><strong>ロジックとビューの分離</strong>: コントローラはデータ取得、テンプレートは表示に集中</li>
<li><strong>レイアウト継承</strong>: 共通レイアウト (ヘッダ / フッタ) を 1 箇所で管理</li>
<li><strong>コンパイル キャッシュ</strong>: テンプレートを最適化された PHP/Python/Java コードに事前変換</li>
<li><strong>カスタムタグ / フィルタ</strong>: 日付フォーマット、通貨表示などを再利用可能に</li>
<li><strong>セキュリティ</strong>: 自動エスケープ + サンドボックスで安全</li>
</ul>
<h2>PHP のテンプレートエンジン</h2>
<h3>Blade (Laravel 標準)</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-blade">{{-- layouts/app.blade.php --}}
<!DOCTYPE html>
<html>
<head><title>@yield('title')</title></head>
<body>
<nav>@include('partials.nav')</nav>
<main>@yield('content')</main>
</body>
</html>
{{-- pages/article.blade.php --}}
@extends('layouts.app')
@section('title', $article->title)
@section('content')
<h1>{{ $article->title }}</h1> {{-- 自動エスケープ --}}
{!! $article->html_content !!} {{-- 生 HTML --}}
@if ($user)
<p>ようこそ {{ $user->name }} さん</p>
@else
<a href="/login">ログイン</a>
@endif
@foreach ($comments as $comment)
<article>{{ $comment->body }}</article>
@endforeach
@endsection</code></pre>
<h3>Twig (Symfony / 多言語)</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-twig">{# layout.html.twig #}
<!DOCTYPE html>
<html>
<head><title>{% block title %}{% endblock %}</title></head>
<body>{% block content %}{% endblock %}</body>
</html>
{# article.html.twig #}
{% extends "layout.html.twig" %}
{% block title %}{{ article.title }}{% endblock %}
{% block content %}
<h1>{{ article.title }}</h1>
{{ article.body|raw }} {# 生 HTML #}
{{ now|date('Y-m-d') }} {# フィルタ #}
{% if user %}
<p>{{ user.name }}</p>
{% endif %}
{% for comment in comments %}
<p>{{ comment.body }}</p>
{% endfor %}
{% endblock %}</code></pre>
<h3>Smarty (老舗 PHP)</h3>
<p>2001 年から続く老舗。EC サイト (EC-CUBE 等) で今も使われています。</p>
<h2>Python のテンプレートエンジン</h2>
<h3>Jinja2 (Flask 標準、Ansible でも使われる)</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{# base.html #}
<!DOCTYPE html>
<html>
<body>{% block content %}{% endblock %}</body>
</html>
{# article.html #}
{% extends "base.html" %}
{% block content %}
<h1>{{ article.title }}</h1>
{{ article.body | safe }} {# raw HTML #}
{% if user %}
<p>{{ user.name | upper }}</p>
{% endif %}
<ul>
{% for c in comments %}
<li>{{ c.body }}</li>
{% else %}
<li>コメント無し</li>
{% endfor %}
</ul>
{% endblock %}</code></pre>
<h3>Django Templates</h3>
<p>Django 標準。Jinja2 とほぼ同じ記法 (実は Jinja2 が Django Templates にインスパイアされた)。</p>
<h2>Java のテンプレートエンジン</h2>
<h3>Thymeleaf (Spring Boot 標準)</h3>
<p>HTML 属性として記述するので、テンプレート単体でブラウザで開いても表示が崩れないのが特徴。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h1 th:text="${article.title}">記事タイトル</h1>
<div th:if="${user != null}">
<p th:text="|ようこそ ${user.name} さん|"></p>
</div>
<ul>
<li th:each="comment : ${comments}" th:text="${comment.body}"></li>
</ul>
<a th:href="@{/article/{id}(id=${article.id})}">詳細</a>
</body>
</html></code></pre>
<h3>FreeMarker</h3>
<p>Apache 系。柔軟性が高く、メール本文生成などにも使われます。</p>
<h2>Node.js のテンプレートエンジン</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>エンジン</th><th>特徴</th><th>例</th></tr></thead>
<tbody>
<tr><td>EJS</td><td>HTML に近い、PHP に似た記法</td><td><code><%= title %></code></td></tr>
<tr><td>Pug (旧 Jade)</td><td>インデントベース、簡潔</td><td><code>h1= title</code></td></tr>
<tr><td>Handlebars</td><td>ロジックレス指向、Mustache 拡張</td><td><code>{{title}}</code></td></tr>
<tr><td>Mustache</td><td>言語非依存、ロジックレス</td><td><code>{{title}}</code></td></tr>
<tr><td>Nunjucks</td><td>Mozilla 製、Jinja2 ライク</td><td><code>{{ title }}</code></td></tr>
</tbody>
</table>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// EJS の例
const ejs = require('ejs');
const html = ejs.render(`
<h1><%= article.title %></h1>
<% if (user) { %>
<p><%= user.name %></p>
<% } %>
`, { article, user });
// Handlebars の例
const Handlebars = require('handlebars');
const template = Handlebars.compile(`
<h1>{{ article.title }}</h1>
{{#if user}}
<p>{{ user.name }}</p>
{{/if}}
{{#each comments}}
<p>{{ this.body }}</p>
{{/each}}
`);
const html2 = template({ article, user, comments });</code></pre>
<h2>クライアントサイド (Vue / React / Svelte)</h2>
<p>SPA (Single Page Application) ではフロント側にもテンプレート機構があります。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-vue"><!-- Vue.js -->
<template>
<h1>{{ article.title }}</h1>
<p v-if="user">ようこそ {{ user.name }}</p>
<ul>
<li v-for="c in comments" :key="c.id">{{ c.body }}</li>
</ul>
</template>
<script setup>
import { ref } from 'vue';
const article = ref({ title: '...' });
</script></code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-jsx">// React JSX
function Article({ article, user, comments }) {
return (
<>
<h1>{article.title}</h1>
{user && <p>ようこそ {user.name}</p>}
<ul>
{comments.map(c => <li key={c.id}>{c.body}</li>)}
</ul>
</>
);
}</code></pre>
<h2>静的サイトジェネレータ (SSG)</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>ツール</th><th>言語</th><th>テンプレート</th><th>用途</th></tr></thead>
<tbody>
<tr><td>Hugo</td><td>Go</td><td>Go templates</td><td>高速ブログ、ドキュメント</td></tr>
<tr><td>Eleventy (11ty)</td><td>Node.js</td><td>Nunjucks/Liquid/EJS/Markdown</td><td>軽量ブログ</td></tr>
<tr><td>Jekyll</td><td>Ruby</td><td>Liquid</td><td>GitHub Pages</td></tr>
<tr><td>Gatsby</td><td>React</td><td>JSX + GraphQL</td><td>React 派生</td></tr>
<tr><td>Next.js (SSG モード)</td><td>React</td><td>JSX</td><td>React + ISR</td></tr>
<tr><td>Astro</td><td>多言語</td><td>独自 + Vue/React/Svelte 混在</td><td>パフォーマンス重視ブログ</td></tr>
<tr><td>VitePress / Docusaurus</td><td>Vue / React</td><td>各々</td><td>ドキュメント</td></tr>
</tbody>
</table>
<h2>Plain PHP vs テンプレートエンジン</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-php"><?php // Plain PHP - 動くが推奨されない ?>
<h1><?= htmlspecialchars($title) ?></h1>
<?php if ($user): ?>
<p>こんにちは <?= htmlspecialchars($user->name) ?></p>
<?php endif; ?>
<ul>
<?php foreach ($items as $item): ?>
<li><?= htmlspecialchars($item) ?></li>
<?php endforeach; ?>
</ul>
<?php // Blade - 推奨 ?>
<h1>{{ $title }}</h1>
@if ($user)
<p>こんにちは {{ $user->name }}</p>
@endif
<ul>
@foreach ($items as $item)
<li>{{ $item }}</li>
@endforeach
</ul></code></pre>
<h2>コンパイル キャッシュの仕組み</h2>
<p>多くのテンプレートエンジンは、テンプレートを<strong>1 度だけパースして最適化された PHP/Java/JS コードに変換</strong>し、ファイルとしてキャッシュします。2 度目以降はキャッシュを読むだけなので高速です。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text">Laravel Blade のキャッシュ場所:
storage/framework/views/*.php
Twig のキャッシュ:
var/cache/dev/twig/
Thymeleaf:
メモリ上にコンパイル済テンプレート保持
# 開発時はキャッシュ無効化、本番では有効化
# Laravel: APP_DEBUG=false で自動キャッシュ</code></pre>
<h2>選定ガイド</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e8f5e9"><th>状況</th><th>推奨</th></tr></thead>
<tbody>
<tr><td>Laravel プロジェクト</td><td>Blade (標準で十分強力)</td></tr>
<tr><td>Symfony / 純 PHP</td><td>Twig</td></tr>
<tr><td>Spring Boot</td><td>Thymeleaf (公式チュートリアル基準)</td></tr>
<tr><td>Django</td><td>Django Templates (標準)</td></tr>
<tr><td>Flask</td><td>Jinja2 (標準)</td></tr>
<tr><td>Express.js</td><td>EJS / Pug、両方人気</td></tr>
<tr><td>静的サイト (高速重視)</td><td>Hugo</td></tr>
<tr><td>静的サイト (柔軟性重視)</td><td>Astro / Eleventy</td></tr>
<tr><td>SPA</td><td>React JSX / Vue Template / Svelte</td></tr>
</tbody>
</table>
<h2>FAQ</h2>
<p><strong>Q: テンプレートエンジンを使わず素の PHP/Node でも書けるが、なぜ使う?</strong><br />
A: 自動エスケープ、レイアウト継承、可読性、保守性、IDE 補完、コンパイルキャッシュ、開発者間の共通認識。長期保守を考えれば導入する価値があります。</p>
<p><strong>Q: ロジックをテンプレート側に書いてもよい?</strong><br />
A: 表示に関する分岐 (if/for) は OK。ビジネスロジック (DB 操作、計算) はコントローラやサービス層に置くべき。<strong>「テンプレートは表示専用」</strong>を意識します。</p>
<p><strong>Q: パフォーマンスはどれが速い?</strong><br />
A: コンパイルキャッシュが効けば差は僅かです。Hugo (Go) はビルド速度が非常に速いことで有名。サーバ側は <strong>適切なキャッシュ設定が最重要</strong>で、エンジン選定の差は通常無視できます。</p>