Type-safe HTML templating in Zig
lalinsky.com
lalinsky.com
This is kind of funny remark, given that the author started in 2000 in Web development, and with PHP 3.0.
I started in 1997, our distributed systems class had exercises to write CGIs in Perl and C.
Between 1999 and 2001, most Web development at my employer was in Tcl and C, ASP was naturally a mix of VB and C++ COM components, and there was also the fun to write all those mod_something Apache extensions, and IIS ISAPI modules, naturally in C and C++.
Ah, Microsoft was also shipping ATLServer, because who wouldn't want to use ATL for Web development, while Borland/Inprise was shipping similar stuff for Delphi and C++ Builder.
PHP itself, in 3.0, it was still the time that it hardly had much functionality builtin, so it was quite common to write extension modules in C.
So it is kind of ironic to read coming back to the way things were, before the slow scripting languages takevover in the 2010's, is something no one thought about.
Most of my work was on an intranet page used for tracking internal servers, apps and databases. One of cool (at the time) features I implemented was a server-rendered ping of the servers shown on the dashboard. We're talking usually a few hundred at a time. The site ran on IIS & NT 4, written in VBS & ASP. (I think my first server was actually a dual Alpha chip, wasn't even x86, sure as hell was way before x64).
To implement the ping functionality (yes, we're talking just a straight ICMP ping), I ended up doing a COM server, written in C++, called into it from the VBS side. Eventually, .Net came out, and once .Net Framework 1.1 was released, I was able to convince my boss to let me rewrite the whole site in ASP.Net. I ditched the C++ COM object and was able to do it in C# with raw sockets (and appropriate permissions, which was a bit of a pain to get right at the time, before google and stackoverflow were around to help). Getting the C++ COM object working was actually easier because at least the MSDN library (the entire stack of CDs & DVDs it was on) had extensive documentation. The .Net side was good initially, but it just did not have the same depth as the C++ side already had at that point.
The typesafety you get is what you already have in Kotlin, and there a little extra in that you cannot nest certain HTML-elements in certain other elements (adhering to the HTML spec). You can come up with your own elements and attributes, but the ones that are in the HMTL-spec are usually available in kotlinx.html as well (and thus can be auto-completed).
A large part of this library is generated from the actual HTML spec.
Claude Code seems to have not problem writing it (I barely write templates myself these days).
https://github.com/kotlin/kotlinx.html
https://dzone.com/articles/modern-type-safe-template-engines...
Looking forward to seeing some benchmarks!
<!-- #include file="header.html" -->
https://en.wikipedia.org/wiki/Server_Side_Includes(but yes- I used these a lot like 25 years ago)
Why is there no direct support for templating in HTML itself?
This might be the worst argument for anything I've read this week, there's got to be a name for this logical fallacy
It's a repeated observation that keeps coming true. DSLs have a tendency to become turing complete over time.
- It happened to wikipedia's templating language
- It happened to PHP
- It happened to mod_rewrite
- It happened to server-side includes
- etc...
It just keeps happening.
In fact it's such a problem with infrastructure teams that use yaml/json configs that you see a lack of turing-completeness being advertised as a feature (see Dhall configuration language).
But "we can't give something to users because otherwise they'll ask for other stuff" is not an argument. They're _already_ asking for stuff (templating) anyway. No one is suggesting that shutting users up is a reason to implement templating, it's the trade-offs of this feature that are debated. And if it gets implement then yes, users will certainly ask for more features, the trade-offs of which can be debated.
If you think that the line for HTML features is where we already are, you can present your arguments for why (tbh I'll probably agree). But "if you enable templating devs are then going to ask for more" isn't one.
If the other 10% of features are implemented, then any further change makes the DSL turing complete.
It's not even really `if` by itself, conditionals are good enough. Templates with for loops that can check if an index is the first or last can implement if statements. Also templates that can nest other templates recursively with a max recursion depth can also implement if.
You have to go out of your way to avoid turing completeness if you want to avoid a complexity explosion.
Eg some template feature creates a security issue and has to be disabled, which invalidates all the old HTML that used it.
There’s also just not a super great reason to do it. It’s not all that different from pre-templating like Hugo or doing it server-side like Django. You get to offload some compute, but HTML templating isn’t usually the bottleneck.
No offense but this is a bit of a non-argument that could be applied to just about any development in HTML.
> There’s also just not a super great reason to do it.
Most web dev frameworks exist in large part to solve the problem of templating. (yes, they do other stuff, but templating is the central problem that they are solving)
HTML is actually shockingly backwards compatible. I very rarely see pages so old they won’t render. The main exception is basically exactly what I’m talking about: Flash and ActiveX, that had to be abandoned for security (and some poorly advised iframe stuff).
> Most web dev frameworks exist in large part to solve the problem of templating.
This might be an agree to disagree area. Most web dev frameworks I’ve seen exist to make dealing with state easier. They do templating because you have to put the state somewhere, but the magic is all in the state.
There are some that only do templating, but they’re pretty rare. Hugo or Jekyll come to mind, though “web dev framework” is probably overselling them.
There are also web components, which sound vaguely like what you’re talking about. They’re not crazy popular, but they exist and work. The security boundaries make it kind of a nightmare, I remember having to do something weird to inject Tailwind into them because they’re isolated from global CSS. The kind of things you have to do when securing templates at runtime instead of compile time.
https://web.dev/articles/imports
Been there, tried that.
Introduced in 2014 (Chrome 36) Deprecated 2018 (Chrome 70) Retired 2019 (Chrome 73)
Hence HTML as a mere vocabulary making use of SGML's facilities didn't need to define its own ad-hoc mechanism.
Btw the <?php ...> construct is also derived from SGML. In SGML terms it's a "processing instruction" rather than an "entity reference", the latter being the basic means for content inclusion.
I say tragically, because XSLT was indeed the only sort-of workaround that was available for templating in the browser, even if it was limited to v1 rather than the much more useful v2 and v3.
i.e. MyTemplate.tpl.html
{if $foo}hello world, {$myname}!{else}goodbye, world.{/if}
which creates a kotlin class that can be used like this: MyTemplate(
foo = true,
myname = "bar"
).toString()
the biggest upside for me is that this way there's full, free IDE support, i don't need to write extra view models if the mapping isn't 1:1 and performance is pretty good because in the end there's no runtime parsing/compiling/interpreting except for the baseline JDK stuff (though there are even faster engines).https://codeberg.org/h34tnet/tempolin
warning: so far i'm the only user though, so don't use it in production. also, i'm working on some improvements right now which haven't been pushed yet.
It never occurred to me, either! This is really neat! How does it work?
EDIT: Never mind, seems like it's a custom file format that gets compiled to Zig code. Still quite neat but unfortunately you lose all the IDE support.
I think there is a way to do syntax extensions cleanly, with a special block type like `'{ }` that gets tokenized and passed to a comptime function skipping the parser. But one of Zig's main selling points is no hidden control flow and they want to keep it that way.
Sure I hack a 'Sup World in C every 100 years just to get that feeling you get when you stumble into semi-accomplishment but to consider a low-level lang executable as a product that I produce, maintain and develop expertise/taste with?
Regarding web dev in any low-level lang: once you get that there's a parenthetical "services" between the words "web" and "development", the quest for the languages that offer speed, reliability, etc becomes inevitable
Is there maybe a Zig wrapper for Google soy around?
For examples, see http://www.impredicative.com/ur/demo/
As for Django... well.. Django only has text formatting, you can write any garbage and broken html and lots of invalid templates with silent failures there unfortunately.
It looks like you'd have to implement something equivalent to provider/context functionality yourself if you want it, as well as a simple render()-like helper to render a component tree. I've not tried their VS Code plugin, but it's a good sign that they have one.
The design point "deterministic, hostable, with enough to make hooks from the templates" is what makes it special: https://lucasdicioccio.github.io/tramaj/ .
A much better exercise I’d like to see is using C++26 reflection to accomplish the same thing. Because that will actually be used while Zig will not.
Using a template engine means you systematically ensure that values are escaped (at least, that's one of the reasons i would choose an engine over raw dogging the html).
me% cat index.m4
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Better user history</title>
<link rel="stylesheet" href="local.css">
</head>
<body>
<h3>Users</h3>
<ul>
<li><em>By username</em></li>
<ul>
sinclude(`byuser.m4')dnl
</ul>
<li><em>By last login date</em></li>
<ul>
sinclude(`bydate.m4')dnl
</ul>
</ul>
sinclude(`footer.m4')dnl
</body>
</html>
me% cat byuser.m4
<li><a href="byuser/A-L">A-L</a></li>
<li><a href="byuser/M-Z">M-Z</a></li>
me% cat bydate.m4
<li><a href="bydate/2025">2025</a></li>
<li><a href="bydate/2026">2026</a></li>
me% cat footer.m4
<p><em>Last update: syscmd(`date | tr -d "\n"')</em>
me% m4 index.m4 > index.htm
me% cat index.htm
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Better user history</title>
<link rel="stylesheet" href="local.css">
</head>
<body>
<h3>Users</h3>
<ul>
<li><em>By username</em></li>
<ul>
<li><a href="byuser/A-L">A-L</a></li>
<li><a href="byuser/M-Z">M-Z</a></li>
</ul>
<li><em>By last login date</em></li>
<ul>
<li><a href="bydate/2025">2025</a></li>
<li><a href="bydate/2026">2026</a></li>
</ul>
</ul>
<p><em>Last update: Mon Oct 12 03:08:32 EDT 2026</em>
</body>
</html>1. https://ziggit.dev/t/making-it-harder-to-swallow-error-cance...
The fact that error handling suffers when combining two of the base interfaces of the standard library is pretty rough though? I don't think rust has that particular problem.
There is a similar issue in kotlin where a cancellation is marked via a CancellationException but that extends IllegalStateException and thus Exception. Because of that it gets caught and swallowed all the time. This is a constant source of bugs for me at work and google has resorted to a bunch of linting to get around this design mistake.
I get the pain, but the answer from Scott Redig totally makes sense. I am not sure what is worse: Libraries swallowing cancelled() or libraries lying that they support cancelled.
> Libraries swallowing cancelled() or libraries lying that they support cancelled.
For me former is obviously worse.
#[component]
pub fn HomePage(user: Option<User>) -> Element {
rsx! {
Layout {
title: "Home",
if let Some(u) = user {
h1 { "Welcome back, {u.name}!" }
a { href: "/users/{u.id}", "Your profile" }
} else {
h1 { "Welcome, guest!" }
a { href: "/login", "Log in" }
}
}
}
}this is rsx, but there are other macros that look just like html. i prefer this though, it's much more compact and readable imo
That would be great, I really like openness and ~plugin interfaces of jai compiler internals ...
That's not HTML though, you have to learn a whole new dsl that must conform to rust parser rules. So, not the same thing at all.
#[component]
fn App() -> impl IntoView {
let (count, set_count) = signal(0);
view! {
<button
on:click=move |_| set_count.set(3) >
"Click me”</button>
<p>{move || count.get() * 2}</p>
}
} on:click=move |_| set_count.set(3) >eg
html! {
<div onclick=|_ev: web_sys::MouseEvent| {}>
You can type text right into the elements
{ component }
{ text_var }
</div>
};