Introducing temple

Twenty years ago, servers used to send HTML to browsers, not just JavaScript.1 We wrote CSS and HTML and a sprinkling of JavaScript but the HTML was really the basis of the whole thing. And it’s possible I’m just an old fogey, but I miss it.2

Also, the idea of keeping up with whatever the JavaScript-first stack of the day is just feels exhausting to me, so I’m not going to be doing that.

But I still like to build things, and I still like to build things for the web. So I had been using Go’s html/template package to render HTML, like it was 2006 again.

I miss 2006.

But I found some things hard to use and lacking. I tried out other templating languages and packages for Go, and never really found one that I liked. I always ended up gravitating back to html/template, despite the problems I had using it. I liked the way it approached the problem, it made sense to me.3 The whole thing hinges on there being a template with a bundle of data, a bundle of functions, and a bundle of other templates. The template gets to use the data, the functions, and the other templates to construct some HTML.

But there’s a catch. The package isn’t so much a templating framework as much as it is a tool. Managing those bundles and making sure the template literal has the same bundles available to it every time it’s used and otherwise keeping track of all these things is outside its scope. It lets you handle them however you want.

Handling them turns out to be a pain if you don’t have some structure to it. So I made some structure for it, and I called it temple.4

Components for HTML & Go

temple attempts to bundle all these things up into components. A component, or, I guess, a Component, is at its core just a template literal. But you can also make some functions available to it and whatever you implement the Component interface on will be available as the bundle of data. Rather than tracking which templates a Component needs, it can declare that it uses other Components and their data and templates and functions will all be included, too.

Everything gets bundled up into a reusable Component, with the same functions and data being available to the same template literal, all accessed through a straightforward interface. The data the template expects to receive is documented through the type implementing the Component interface, which is usually a struct of the data you want to use in the template.

JavaScript and CSS

A component often needs some CSS or JavaScript to function properly, so a Component has some affordances for that.5 You can declare that a Component needs to embed some CSS or JavaScript directly into the HTML output, so it’s included in the initial response from the server. Or you can link to the CSS or JavaScript, instead, having the browser fetch them. (Browsers are pretty cool technology, we should let them do more things for us.)

Pages

Pages are just special Components. They tell temple which template to execute when rendering them and are meant to be rendered as whole documents instead of as a piece of a document. Other than that, they’re essentially just Components.

When they get rendered, they get the full bundle of information they need:

  • $.Page is set to the Page implementation being rendered. This is usually a struct, and any Components it uses are usually set as properties on the struct. It’s the Page’s template’s job to actually include its Components’ templates, though. Usually through the declaration of blocks.
  • $.Site is… you know what, we’ll come back to this one.
  • $.CSS holds the HTML to include all the CSS (both linked and embedded) needed by the Components on the page.
  • $.HeaderJS holds the HTML to include all the JavaScript (both linked and embedded) needed by the Components on the page but that doesn’t need to run after the rest of the document has loaded.
  • $.FooterJS holds the HTML to include all the JavaScript (both linked and embedded) needed by the Components on the page that needs to run after the rest of the document has loaded.

Technically your Page gets to decide where the $.HeaderJS and $.FooterJS are rendered (or if they’re rendered!) but the spirit is that the $.HeaderJS will be included in the <head> of your HTML and the $.FooterJS will be included as the last thing before the closing </body> of your HTML. Components control which one they land in by setting PlaceInFooter in JSLink or JSInline to true (will be included in $.FooterJS) or false (will be included in $.HeaderJS).

For each of the CSS, HeaderJS, and FooterJS blocks, temple builds a DAG and walks it, deduplicating as it goes. This allows Components to influence where their CSS and JavaScript get rendered in relation to other CSS or JavaScript. The properties of JSLink, JSInline, CSSLink, and CSSInline have some more information on the knobs you can turn there.

Sites

Passing absolutely everything around from Page to Component seems like a lot of busywork, so temple has an affordance called the Site.

Technically, a Site is anything that can tell temple where to find the templates the Components are referencing. But because it’s an interface, and because it’s passed to every Page as $.Site6, its main utility is to store the functions and data that every page should have access to. It can hold your common configuration data, your utility functions, whatever you think you’ll need on every page.

Try It Out

temple is released today with version v0.4.0. That pre-v1.0.0 version is very intentional. I might have gotten quite a bit of this wrong, and reserve the right to fiddle with the design as much as I want.

It’s available under the MIT license.

If you think it may scratch an itch you’ve got, feel free to try it out, and I’d love to hear from you. But I have a job, and I’m not looking for an unpaid one. So I reserve the right to only do things with it when I feel like it, and discard any feedback that doesn’t resonate with me. This is, first and foremost, solving a problem I have. If you have the same problem, I’m happy to share the solution I’ve come up with. But if you have a problem that looks similar if you squint and what me to change the solution to be for that problem instead, I’m going to politely decline and wish you great success with your fork.


Footnotes

  • 1 Back in my day… 
  • 2 Back in my day… intensifies. 
  • 3 Eventually. After like a decade of using it. Look, nobody has ever described me as a man of taste. 
  • 4 If you’re wondering why I named this “temple” instead of, say, “template” or “tmpl”, I need you to stop reading this post right now and go rename every Go package you’ve given the same name as a package in the standard library or a commonly-used identifier. Nobody should be making a package named err or errors, stop it. 
  • 5 It does not involve reinventing CSS and JavaScript to be Go code, no. 
  • 6 Told you we’d get back to this.