HTML Composition

... or how I spent two hours on the S-Bahn every work day for over six months


who dat?

Thom Bradford


This story begins with me asking myself a question

What's wrong with this picture?



{{ ... }}


Syntactically Superfluous

Escaping code only makes sense in a general-purpose templating system

Visually Hideous

HTML tags are already noisy

Braces just add to it

and draw the eye away from what matters

2 out of 10 scientists agree!

Semantically Ambiguous

What is the output of this?

  <li>name is {{name}}</li>

Could be a list of names

Could be "Wrong, dumbass!"

Doesn't "mean" anything

Logically Promiscuous

if it's all about separation of concerns...

why are you writing so much presentation code in your controllers?

Socially Injurious

Mustaches?  Really?
That's your metaphor?

Not picking on Mustache

You see the same problems elsewhere

<!-- Old School JSP -->
<% for ( int i = 0; i < items.length; i++ ) { %>
<% } %>
<!-- JSTL not a great improvement -->
<jstl:forEach items="${items}" var="item">
<!-- Genshi Template -->
  <li py:for="item in items">${item}</li>


 mainly that Dust.js sucks

So what was I thinking when I designed this thing?

No braces for escaping

Braces allow you to 'escape' into 'dynamic' mode

This assumes you mostly
create static content

In all but the most trivial apps,
this is never the case



Tried to reintroduce 'meaning'

Code should mean something,
even to a casual observer

Terse syntax needs to justify itself

If you can't understand your code,
how can others?

for item in items

Kept one pain source: HTML tags

Because we've already
gone through that pain

Because even your 'not-so-bright'
cousin knows some HTML

Because 'tools' still crank out HTML

Anatomy of an Interpol Template

page(title, content)

def page(title, content)
  from string import title as titleCase
    <head><title> title | titleCase </title></head>
    <body> content() </body>

def content
  people | 'There are %length people'
  for person in people, sibling in person.siblings
    renderPerson(, sibling)

def renderPerson(name, sibling)
  <li>"%sibling is the sibling of %name"</li>

All about Partials

Interpol partials are pretty much what you'd expect, except they're awesome

They can be nested

They produce closures

They are  first-class

and exportable

Partials and Guard Clauses

This will render 'items' as a list
def renderList(items)
  items | 'there are %length items'
  for item in items

What if 'items' is empty?

def renderList(items) when not items
  <b>"there are no items!"</b>

Partials and Inline Guards

Explicit guards can make your partial definitions a bit 'wordy'
def renderItem(type, name)
when type == "developer"
  <b>"Developers rock! Especially %name"</b>

Argument pattern-matching cuts out the middle-man

def renderItem("developer", name)
  <b>"Developers rock! Especially %name"</b>

Looping over data

looping over collections is
performed using a  for statement

It can iterate recursively
over multiple ranges

It does not coerce atomic values
into single-element collections

More About Looping

for statements can have 'else' clauses and its ranges can have guards
let c = ['red', 'green', 'blue']
for color in c when color != 'green'
# outputs <b>red</b><b>blue</b>
for color in c when color == 'orange'
  <b>"no color found"</b>
# outputs <b>no color found</b>

Importing Stuff

Importing partials and variables
works similar to Python

Import entire modules
import myModule, theirModule

Cherry-pick items
from myModule import mainLayout

Alias those items
from myModule import mainLayout as main


No mysteries here

if / else if / else / unless

Conditional operator:

<t> if <cond> else <f>
<t> unless <cond> else <f>

Local Variables

Local variables are really, REALLY local

Can't reassign from an inner scope *

Intentional to discourage 'programming' ...

... and thus Interpol becoming PHP

String Formatting

Interpolate by implicit index
['World'] | 'Hello, %!'

by explicit index
['World', 'Hello'] | '%1, %0!'

by name
person | 'Hello, %name!'

by local variable
"Hello, %name!"

Formatting (cont.)

Piped Calls in Expressions
from string import title
"My name is" name | title 

Piped Calls in Strings
from string import title
"My name is %name|title" 

Pipe as many as you like
from list import join
from string import title
"My name is %name_words|join|title"

And Much, Much More!

A partial call can be pre-bound
from partials import renderList
let renderPeople = @renderList(people)

It can then be passed around
from layouts import mainLayout
mainLayout("People", renderPeople)

and called later, as if in-place
def mainLayout(title, content)
  <div class="title">title</div>
  <div class="content">

Well, Not So Much More

You can pass a literal list into a literal string
"My name is %name and I am %age years old" [
  name = 'David Bowie',
  age = 68

You can perform List Comprehensions
let classes = [c + '_class' for c in person.attrs]
<div class="person %classes">
  "My name is"

And Finally

You can create a partial
def renderList(items, render)
  for item in items
    <li> render(item) </li>

and pass a block of statements to it
renderList(people) with |item|
  item | "Name is %name and age is %age"


Hammer Time!

Current Status

Currently at Version 1.5

Needs more documentation

... a tiny bit of optimization

Oh, and there are probably bugs

for the compiler and runtime

for express and hapi integration

npm install interpol

npm install interpol-views

for sublime text plugin

package:install -> Interpol



 100,000 iterations of a simple title + unordered list combination

 7192ms total - 1 iter @ 71.92µs -  14 per 1ms - interpol 0.1.0
 3560ms total - 1 iter @ 35.60µs -  28 per 1ms - interpol 0.4.2

 1759ms total - 1 iter @ 17.59µs -  57 per 1ms - mustache 2.1.2

 1726ms total - 1 iter @ 17.26µs -  58 per 1ms - interpol 1.0.0
 1514ms total - 1 iter @ 15.14µs -  66 per 1ms - interpol 0.9.0

 1140ms total - 1 iter @ 11.40µs -  88 per 1ms - jade 1.11.0
  781ms total - 1 iter @  7.81µs - 128 per 1ms - dust.js 2.7.2
  530ms total - 1 iter @  5.30µs - 189 per 1ms - handlebars 3.0.3
  327ms total - 1 iter @  3.27µs - 306 per 1ms - hogan.js 3.0.2

  248ms total - 1 iter @  2.48µs - 403 per 1ms - interpol 1.5.7

Glad you asked!

Interpol Intro for Berlin NodeJS

By Thom Bradford

Interpol Intro for Berlin NodeJS

  • 773

More from Thom Bradford