Skip to content

Instantly share code, notes, and snippets.

@jed
Created October 19, 2012 05:07
Show Gist options
  • Save jed/3916350 to your computer and use it in GitHub Desktop.
Save jed/3916350 to your computer and use it in GitHub Desktop.
Rendering templates obsolete

(tl;dr DOM builders like domo trump HTML templates on the client.)

Like all web developers, I've used a lot of template engines. Like most, I've also written a few of them, some of which even fit in a tweet.

The first open-source code I ever wrote was also one of the the first template engines for node.js, [a port][node-tmpl] of the mother of all JavaScript template engines, [John Resig][jresig]'s [micro-templates][tmpl]. Of course, these days you can't swing a dead cat without hitting a template engine; one in eight packages on npm ([2,220][npm templates] of 16,226 as of 10/19) involve templates.

John's implementation has since evolved and [lives on in Underscore.js][underscore], which means it's the default choice for templating in Backbone.js. And for a while, it's all I would ever use when building a client-side app.

But I can't really see the value in client-side HTML templates anymore.

I recently built a small (under 1k minizipped) DOM library called domo. All it really does is pull the semantics of CSS and HTML into the syntax of JavaScript, which means you can write your entire client app in one language. It looks like this:

// this code returns an HTML DOM element
HTML({lang: "en"},
  HEAD(
    TITLE("domo"),
    STYLE({type: "text/css"},
      CSS("#container", {backgroundColor: "#eee"})
    )
  ),

  BODY(
    DIV({id: "container"},
      "For more details about domo, see the source: ",
      A({href: "//github.com/jed/domo/blob/master/domo.js"}, "View source")
    )
  )
)

It doesn't replace CSS or HTML at all; it just lets you write them both in JavaScript. So instead of relying on the arbitrary design choices of SASS/LESS or HAML/Mustache for making your CSS and HTML more dynamic, you rely only on the design choices of JavaScript, which while subjectively less arbitrary, are objectively more known.

I think there's so much win in this approach that I'm surprised it's not more popular. Or to turn it around, I'm surprised most web developers tolerate the usual workflow for templating:

  1. Write HTML templates written in some arbitrary frankenlanguage,
  2. put them in several DOM script tags with a non-standard type,
  3. compile it into a function that takes data, HTML-escapes it, and returns HTML, and
  4. use jQuery to turn the html string into a proper DOM element.

when all we really need is a friendlier way to wrap the overly imperative and verbose DOM API into JavaScript code that mimics the resulting DOM.

Sure, using a DOM builder like domo instead of both a CSS preprocessor and HTML template engine means you lose the superficial syntax of both CSS and HTML, but you still preserve the underlying semantics and gain the following in return:

  • Reduce your exposure to XSS attacks. You don't need to rely on a library to sanitize your rendered data because the browser does it for you automatically with createTextNode and setAttribute.

  • Eliminate a separate compile step. With DOM builders, "compilation" is done in the same JavaScript process as your code, which means that any syntax errors are thrown with line numbers in the same JavaScript process.

  • Write your DOM code where it makes sense. If you're writing a view that renders a DOM node, you can write it directly within the rendering code for convenience, and then pull it out into its own file when the size of your app requires it. You don't need to let implentations drive architectural decisions.

  • Use JavaScript syntax everywhere. Instead of remembering which symbols map to which behaviors like looping or escaping or conditionals or negation, just use JavaScript.

  • Decouple your syntax sugar. Instead of choosing a template engine with the ideal syntax, just use JavaScript, and do your sweetening on a more reliable level. CoffeeScript can go a long way in [making DOM building code look like HAML][browserver], for example.

  • Reduce the number of moving parts. Instead of shipping HTML containing strings that compile into JavaScript functions that return HTML that's used to create DOM nodes, just use JavaScript to create DOM nodes, eliminating underscore/jQuery dependencies at the same time.

  • Reuse existing infrastructure. Any tools you use in your JavaScript workflow, such as minification or packaging, can now be used for your styles and markup too. You can even use something like [browserify][browserify] to easily discover all app dependencies and maintain code modularity.

  • Lessen the burden of context switching. Whether using JavaScript on both the client and server enables code reuse is debatable, but that it prevents the overhead of switching contexts between languages is less so. It may be subjective, but I think using one language everywhere reduces the congitive overhead for web app development.

As more and more logic moves to the browser, I really think this approach makes a lot of sense and would love to see more developers give it a try. I'd love to [hear your feedback][hn].

[node-tmpl]: https://github.com/jed/tmpl-node/commit/33cfc11f54b0e6c33b9de7589c62057dae82848b) [jresig]: http://ejohn.org/blog [tmpl]: http://ejohn.org/blog/javascript-micro-templating/ [underscore]: http://documentcloud.github.com/underscore/#template [npm templates]: https://encrypted.google.com/search?q=template+OR+templating+site:npmjs.org [browserver]: https://github.com/jed/browserver.org/blob/master/app.coffee [browserify]: https://github.com/substack/node-browserify [hn]: http://news.ycombinator.com/item?id=4672353

@orochi235
Copy link

Wait, so this isn't some arbitrary Frankenlanguage?

DIV({id: "container"},
  "For more details about dom-o, see the source: ",
  A({href: "//github.com/jed/dom-o/blob/master/dom-o.js"}, "View source")
)

@DarrellBrogdon
Copy link

I've seen this attempted for 15 years in Perl, PHP, and now JS and they never gain any traction.

@jacobrask
Copy link

👍

"Sugared DOM" is similar but feels even more JavaScripty to me. I also consider the approach with child elements as an array to be more powerful.

@amasad
Copy link

amasad commented Oct 19, 2012

Reminds me of DOMQL ;)

INSERT INTO (CREATE ELEMENT HTML (
          lang 'en'
        )) VALUES (
          CREATE ELEMENT TITLE (
            innerText 'dom-o'
          ),
          CREATE ELEMENT STYLE (
            type 'text/css'
          )
      )

@jacobrask
Copy link

@thegrubbsian
Copy link

My two cents is that this approach doesn't improve upon external templates (in their own files), but it does improve upon generating HTML from within JavaScript. The current methods for this (I'm looking at you jQuery) are not great.

@mlanza
Copy link

mlanza commented Oct 19, 2012

I'm no fan of templates, but obsessive with reuse. I crafted buildr for this kind of thing. It uses jQuery.

http://mlanza.github.com/buildr/

@rsdoiel
Copy link

rsdoiel commented Oct 19, 2012

I like this approach and used it in a personal library I have called TBone. I originally wrote it to explore some ideas from the NPR.org tech blog. Originally I tried to keep all the dom like parts separate. In my recent rewrite I abandoned this approach use extended string. In my rewrite (e.g. T.div("inner content here") and an attribute function (T.attr()). It's chainable and all the tags support variable number of arguments. This let me do things like-

   var TBone = require("tbone"),
         T = new Tbone.HTML();

    console.log(T.div(
         T.h1("Hello World),
         T.p("blah blah blah").attr({"class": "sensless", "id":"do-i-care"})
    ).attr({"class", "my-stuff"}).toString());

The nice thing is no dependencies. Words in NodeJS, Works in the Mongo DB shell, works in the browser (though I don't have a real reason to use it there). Loops are just a JS loop or a map and sticking the results in another container. Server side I think you can use modules like memoize (https://github.com/medikoo/memoize.git) to improve performance and cache functional results.

I would encourage you to keep exploring this approach.

All the best.

@jussiry
Copy link

jussiry commented Oct 20, 2012

Happy to see that the idea of writing everything with one language is starting to spread. For about a year now, I'v been using only CoffeeScript to write my templates and CSS (with CoffeeKup and CCSS). I have a system where i have a single file for a template, style definitions- and functionality (event bindings, etc.) of that template. It gives you a nice modularity where you can take these template-components and throw them around from one project to another and everything just works, since all you need for that component is a single file. Here's one template: https://github.com/jussiry/houCe/blob/master/client/templates/header.templ

@csuwildcat
Copy link

I'll reserve any personal investment of time into new templating systems until the W3 Web Components spec is implemented (Firefox will be landing Custom Elements shortly). I can nearly guarantee that our collective perception of, and approach to, the DOM and Templates will change _dramatically_ over the next 18 months. Let's tee it up Milton Friedman-style: "Underlying most arguments against the DOM is a lack of belief in the DOM itself"

@tiendq
Copy link

tiendq commented Oct 22, 2012

I don't expect it does many things with template in just 1K of code. dom-o is an intuitive way to create small HTML snippets in code, better than chaining jQuery objects. Thanks.

@unwiredben
Copy link

Enyo JS does something similar. It's focused on building a reusable component tree, but the rendering of any component results in a JS object hierarchy being turned into HTML. At the moment, our main rendering approach is to generate a string then use innerHtml to set a large part of the DOM at one time, but I could see us switching to use document fragment creation if that was shown to be faster in the environments we care about.

@carywreams
Copy link

Appreciate the point/counter-point dialogue going on here. Dont have much to offer except the encouragement to keep it up as I'm evaluating templating options for a pretty plain-vanilla application. I'm attempting to enforce a standard template for "show" and "index" views and have been fairly successful so far. I'm still early enough to adopt this approach though, if I can get my head around it. Thanks, all.

@xeoncross
Copy link

The problem is designers and tools. They handle the layouts and mockups and they are not interested in building layouts from javascript arrays or objects. Works for us, but not them.

Basically, it would be better for many artists if they all drew on a computer tablet with vectors and unlimited "undo" history - but some of them still like painting on canvas, sidewalks, buildings, etc.. and we just need to work around that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment