Deno Simple Server Side Rendering
I’ve finally cleared a bit of technical debt I had in the implementation of this blog. I don’t use a templating engine, and instead define all of the templates in code. JavaScript template literals (backtick strings) make this relatively nice:
return html`
<time ${cls ? `class="${cls}"` : ""} datetime="${machine}">
${human}
</time>`;
Still, given that Deno comes with JSX “out of the box”, an even better approach should theoretically be possible:
return <time class={className} datetime={machine}>{human}</time>;
In practice, this is needlessly fiddly, at least for someone like me, who hasn’t used JSX before. As far as I can tell, there isn’t anything built into Deno or Deno’s std, that would allow me to write code like this:
const html: string = render_to_string(<div>
Hello, world!
</div>);
The suggestion is to use some library, and that increases annoyance manyfold: which library? which cdn/registry should I be pulling it from? how can I vendor it without adding a hundred of auxiliary files?
While Deno in general is refreshingly out-of-the-box, the “I want to render an HTML tree into a string” part decidedly is not.
Fundamentally, the JSR library is easy — it is just some boilerplate
tree constructing code. There’s a dozen micro JSX libraries out there,
you can pick one. Alternatively, you can ask your local LLM to give
you a single-file no-dependencies thing, it’ll probably do a decent
job. My version is here, less than 100 lines of code:
tsx.ts
:
To use this file, I add
/** @jsx h */
/** @jsxFrag Fragment */
import { escapeHtml, h, Raw, render, VNode } from "./tsx.ts";
at the start of the templates.tsx
file with my templates.
No changes to deno.jsonc
are required.
One thing which you don’t get for free with this ad hoc implementation
is nice formatting of HTML. I’d love the source to be at least
somewhat readable. Luckily, deno
the command line tool
comes with html formatter out of the box, so you could use that to
prettify the results:
const t_fmt = performance.now();
const { success } = await new Deno.Command(Deno.execPath(), {
args: ["fmt", "./out/www"],
}).output();
if (!success) throw "deno fmt failed";
ctx.fmt_ms = performance.now() - t_fmt;
Running this on every file is much too slow, but reformatting everything at the end is fast enough!