
2026/10/12 14:02
Zig を用いた型安全な HTML テンプレート処理
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
著者は、Go の
Templ を模倣した Zig 向けの新しいテンプレートライブラリ zt を紹介します。zig build 実行時にテンプレートは解析され、Zig コードへと変換(transpile)されます。これにより、アプリケーションの型をインポートし、完全な型の安全性を実現できます。すべての入力はコンパイル時にチェックされ、出力は割り当てや中間状態なしに std.Io.Writer へ直接書かれます。これにより、実行時のテンプレートエラーやメモリ問題を回避します。組み込みの XSS 保護機能により、テキストと属性がデフォルトでエスケープされ、href/src における危険なスキーマは明示的にチェックされ、生の HTML を使用するには {!value} シンタックスが必要です。パーサーは要素の適切に閉じることを強制し、テンプレートエラーを Zig コンパイラーエラーとして提示し、行ごとのコメントにより元ソースへの位置を示します。if、for、switch などの制御構造は Zig で動作し、ペイロードキャプチャも引き続きサポートされています。既知の制限として、if、for、switch から始まる行がコードと誤って解釈されるブロックレベルの解析曖昧さが存在します。同プロジェクトは一部利用が進んでいるものの未成熟であり、より広範な産業での採用を可能にするために、GitHub issue や Zig Discord へのフィードバックを歓迎しています。本文
Zig テンプレートエンジン「z」の紹介と活用方法
長年にわたり Web 開発に従事してきました。HTML生成のためのアプローチを多岐にわたって試行錯誤しました。
- 初期〜2000 年代: PHP、Python の文字列連結、Smarty、Template Toolkit、Jinja/Django など
- その後: SPA(シングルページアプリケーション)の潮流を受け、フロントエンド開発から離れることを選択
しかし、Zig という言語との出会いにより、Web 開発の展望が全く変わることを知らされました。
なぜ Zig で Web 開発が可能なのか?
当初は「低レベルな言語」で Web を扱うのは非現実的だと思っていましたが、実際には驚くほど管理可能です。
- メモリ管理: アリーナアローケーター(arena allocator)を活用することで、文字列の解放を手動で行う必要がありません。
- 高水準インターフェース:
を活用し、パフォーマンスを損なわずに高レベルな API を構築できます。comptime - SPA 構築:
やhtmx
を組み合わせることで、JavaScript/TypeScript を触らずに SPA を実装可能です。Datastar
テンプレートエンジンへの要件と発見
動的言語時代を経て、型安全性と安全性を最優先したテンプレートエンジンを求めました。
理想の要件
- 型安全性: コンパイル時に入力をチェックしたい(実行時エラーは嫌)。
- 高速性: メモリ割り当てなし、中間状態なし。
に直接出力する方式が望ましい。std.Io.Writer - 安全なデフォルト: XSS 対策として、すべての入力をデフォルトでエスケープする機能が必要。
- HTML の直感性: DSL と戦うことなく、HTML そのままを書く感覚を重視。
これらの要件を満たす既存ライブラリは見つからず、
Ziex や Templ(Go)のアイデアを得た結果、Zig 向けの z(旧称:Templ の適応版) というプロジェクトが発見されました。
「z」の特徴と仕組み
z は Templ を Zig で実装したものであり、以下の点が特徴です。
- トランパイル: テンプレートはビルド時に解析され、Zig コードへと変換(トランパイル)されます。
- 双方向インポート: アプリコードからテンプレートをインポートできるだけでなく、テンプレート側からアプリコードを直接インポートできます。これにより、すべてのパラメータを完全に型付け可能になります。
- コンパイラによる検証: 無効なデータや参照不能な変数は、レンダリング前に Zig コンパイラがエラーとして検知します。
- 出力制御: ソケット直出またはバッファ集積など、柔軟に
に出力できます。std.Io.Writer
テンプレート記法の実例
const User = @import("../models.zig").User; pub templ Layout(title: []const u8) { <!DOCTYPE html> <html> <head> <title>{title}</title> </head> <body> @children </body> </html> } pub templ HomePage(user: ?User) { @Layout("Home") { if (user) |u| { <h1>Welcome back, {u.name}!</h1> <a href="/users/{u.id}">Your profile</a> } else { <h1>Welcome, guest!</h1> <a href="/login">Log in</a> } } }
アプリケーション側での利用
Zig コードからテンプレートを呼び出す際、型安全な方法で実行できます。
const std = @import("std"); const User = @import("models.zig").User; const pages = @import("templates/pages.zig"); pub fn main(init: std.process.Init) !void { var buf: [4096]u8 = undefined; var stdout = std.Io.File.stdout().writer(init.io, &buf); const w = &stdout.interface; const user: User = .{ .id = 1, .name = "Lukas" }; try pages.HomePage.render(.{user}, w); try w.flush(); }
Dusty フレームワーク内での簡易レンダリング例:
try res.render(.html, pages.HomePage, .{user});
注意: 上記のコードスニペットには、
のキャンセル処理に関する未解決の問題(バグ)が含まれる可能性があります。詳細については後述します。std.Io
セキュリティとエラーハンドリング
このツールの最大の利点は、セキュリティとデバッグ体験にあります。
セキュリティ対策
- 自動エスケープ: 動的値はテキストおよび属性ともにデフォルトで HTML エスケープされます。
- URL 検証:
やhref
などの URL 属性には、危険なスキーマのチェックが組み込まれます。src - 生の HTML 出力:
という明示的な構文を使用する以外はすべてエスケープされるため、XSS の一般的な原因を防ぎます。{!value} - 構文エラー防止: パーサーは要素の閉じ方を厳密にチェックするため、壊れたマークアップを生成することを防ぎます。
コンパイル時の型チェック
テンプレート内の記述ミスがコンパイルエラーとして即座に検出されます。
誤記入力の例:
u.name を u.nmae と間違えた場合
src/templates/pages.zig:45:43: error: no field named 'nmae' in struct 'models.User' try zt.writeEscaped(writer, u.nmae); // pages.zt:18:31 ^~~~ src/models.zig:1:18: note: struct declared here pub const User = struct { ^~~~~~
- エラーメッセージには、生成されたコードの位置と、元のテンプレート内の行番号(コメント)が含まれています。
- 型不整合(例:文字列を
を期待する場所へ渡す)もコンパイルで阻止されます。?User
高度な機能
基本的な記法に加え、以下のような強力な機能が備わっています。
- コンポーネント: 再利用可能なコンポーネントを構築し、値として渡すことができます。
- レイアウト継承:
を用いて他のテンプレートをラップでき、他言語におけるテンプレート継承に相当します。@children - 制御フロー:
,if
,for
など、純粋な Zig の制御構造がそのまま利用可能です。switch - パフォーマンス: すべては割り当てなしの一連の書き出し命令としてコンパイルされます。
現在の課題と今後の展望
Dusty との連携
より複雑な Web アプリを構築する際は、
Dusty と組み合わせることで動的 SPA も構築できます(詳細は Dusty の TODO 例 を参照)。
構文に関する議論
ブロックレベルの制御フローについて、以下の選択肢が検討されています。
- 現状: Templ に倣って純粋な Zig で記述(
,if
,for
はコードとして解析される)。switch- 欠点: 「パスワードをお忘れの場合は…」といった単独のテキスト行はパーサーエラーになるため、要素で囲むか文字列式とする必要があります。
- 将来の提案: Razor や Twirl stile に倣い
,@if
,@for
を導入する検討中。@switch- メリット: 曖昧性が解消される。
- デメリット: テンプレートにノイズが発生するコストがある。
フィードバック募集: 構文に関するご意見は、GitHub の Issues でコメントを残していただけますと幸いです。
プロジェクトの成熟度
複数の利用者がいるものの、プロジェクトはまだ未成熟な段階です。
- 問題が見つかった場合は、GitHub Issue を作成するか、Zig Discord 上でご連絡ください。
- フィードバックを大歓迎します。