The JavaScript Templating Landscape

Choosing a template may seem inconsequential at first, but it can have a big impact on a project. Many templating languages have a certain design philosophy that may put restrictions on how you build your app. In addition, there can be significant performance implications that can increase rendering times or add to the weight of your page. It is also important to consider maintainability, usability and debuggability of a templating language, which can have a big impact on developer productivity.

This guide hopes to help you navigate the JavaScript templating landscape. This document is not, however, meant to be comprehensive guide that compares all possible JavaScript templating languages. Instead, I have picked a few templating languages that I believe highlight the different types of templating languages.

Table of Contents

A Sea of Syntax

Text-based

A text-based templating language relies on a custom parser to recognize template directives inside of any text document. A text-based templating language does not understand, nor does it care about, the structure of the document being templated. For this reason, a text-based templating language can be used to produce any type of text output (whether it be HTML source code, Java source code, XML, etc.).

Examples of text-based templating languages:

A sample Dust template is shown below:

Hello {name}!

{?colors}
<ul>
    {#colors}
    <li class="color">{.}</li>
    {/colors}
</ul>
{/colors}

{^colors}
<div>
    No colors!
</div>
{/colors}

HTML-based

An HTML-based templating language relies on an HTML parser to recognize templating directives inside of HTML documents. Templating directives are given as either HTML elements or HTML attributes on top of the standard HTML grammar. An HTML-based templating language is only appropriate for producing HTML output.

Examples of HTML-based templating languages:

A sample Marko template is shown below:

Hello ${data.name}!

<ul if="notEmpty(data.colors)">
    <li class="color" for="color in data.colors">
        ${color}
    </li>
</ul>

<div else>
    No colors!
</div>

DOM-based

A DOM-based templating language relies on a DOM tree to recognize templating directives. A DOM-based templating will walk the DOM tree to discover templating directives and then manipulate the DOM tree to produce the final DOM tree that is then rendered in the browser. A DOM-based templating language only works in the browser unless a virtual DOM is used on the server. DOM-based templating languages are inherently much slower because the templates cannot be precompiled since the input is the DOM tree that is to be manipulated and the output is the manipulated DOM tree. In comparison, text-based and HTML-based templates can be precompiled into a JavaScript function that is a result of the compiler analyzing a template to find all of the interesting dynamic parts.

Examples of DOM-based templating languages:

A sample Knockout template is shown below:

<span data-bind="text: name"></span>

<ul data-bind="if: hasColors">
    <li class="color" data-bind="foreach: { data: colors, as: 'color' }">
        <span data-bind="text: color"></span>
    </li>
</ul>

<div data-bind="ifnot: hasColors">
    No colors!
</div>

Proprietary Markup

Templating languages in this category introduce a new markup language that is translatable to HTML.

Examples of templating languages with a proprietary markup language:

Both Jade and Haml rely on whitespace for determining the output HTML structure and are less verbose than raw HTML since they drop the ending tags and angle brackets.

A sample Jade template is shown below:

= 'Hello ' + name + '!'
if colors && colors.length
    ul
        each color in colors
            li.color= color
if !colors || !colors.length
    div No colors!

Pure JavaScript

Templating languages in this category utilize only inline JavaScript (or CoffeeScript) for producing their output (instead of a separate template file).

Examples of templating languages that use the JavaScript syntax:

An example dom.js template is shown below:

var mytemplate = function () {
  header(
    h1('Heading'),
    h2('Subheading'));

  nav(
    ul({ 'class': 'breadcrumbs' },
      li(a({ href: '/' }, 'Home')),
      li(a({ href: '/section/'}, 'Section')),
      li(a('Subject'))));

  article(
    p('Lorem ipsum...'));

  footer('Footer stuff');
};

JavaScript Hybrid

Templating languages in this category introduce a pre-processor on top of JavaScript to allow embedded templates that blend more nicely with pure JavaScript.

Examples that use a hybrid JavaScript syntax:

Example React JSX template:

/** @jsx React.DOM */
function render() {
    return <div>Hello {this.props.name}</div>;
}

A Logical Choice

Some templating languages allow no logic (not even structural logic), some templating languages allow only structural logic and other templating languages allow complex expressions and embedded JavaScript code. These are compared below.

Logic-less

Despite the common claim, Mustache is not a logic-less language. It puts restrictions on what logic is allowed, but as a developer you are still putting in special templating directives to control repeating blocks and to control conditional blocks and to also add dynamic text. For a truly logic-less template you have to invert the control and have outside code control how the template renders. One way to do this is to assign IDs to elements and then to have outside code manipulate the elements of interest using a separate interface. Two examples of truly logic-less templating languages are the following:

While a truly logic-less template might seem appealing, here are some things to consider:

  • It is often helpful to convey logic in a template
  • The logic has to exist somewhere and moving the logic into external JavaScript will often result in harder to maintain code
  • Separating out logic from a template can result in a disconnect and potential problems

Less Logic

Templates that enforce less logic often have a simplified expression language for referencing data and do not allow complex expressions. In addition, you definitely aren't allowed to embed JavaScript code when using a "less logic" templating language.

For example, with a less-logic templating languages you would be prevented from having the following expression in your template: account.closed && balance > 0

Instead, the controller would need to build a view model with the computed property (or provide a function that returns the computed property).

In addition, logic less templates will also prevent you from embedding arbitrary code into a template.

Examples of "less logic" templates:

More Logic

Templating languages in the "more logic" category allow complex expressions for conditionals and may also allow arbitrary JavaScript code to be embedded (although they don't encourage it).

Examples of "more logic" templates:

Examples of "more logic" templates for Marko and Dust are shown below:

Marko:

<div if="listing.ended || listing.removed">
    This listing is no longer available
</div>

Dust:

{@if condition="{ended} || {removed}"}
<div>
    This listing is no longer available
</div>
{@/if}

Extreme Logic

Templating languages in the "extreme logic" category, not only allow embedded JavaScript to be included in a template, they encourage it.

Examples of "extreme logic" templating languages:

An example EJS template is shown below:

<ul>
<% for(var i=0; i<supplies.length; i++) {%>
   <li><%= supplies[i] %></li>
<% } %>
</ul>

Asynchronous FTW

An emerging feature in newer templating languages is "asynchronous rendering". What this means is that after template rendering has begun, additional data can be asynchronously loaded and used to render parts of the output. While parts of the output may be rendered out of order, the output will be pieced together in the correct order. This might not seem helpful at first, but it does provide some significant advantages:

  • Lower time to first byte
    • Template rendering can begin streaming out before any dependent data is received from a remote service
  • Less idle time
    • Parts of the page can be rendered as soon as the remote data becomes available
  • Template rendering can drive which data is requested
    • Pull model instead of push model

Examples of templating languages that support asynchronous rendering:

Marko gets asynchronous rendering for almost free as a result of wrapping the output stream with an AsyncWriter.

Full Stream Ahead

As mentioned earlier, a streaming templating language will offer better performance on the server. This is for two reasons:

  1. The entirety of the output does not need to be kept in memory at the same time
  2. The time-to-first-byte is reduced since it can be sent out immediately

For these reasons, it is beneficial to select a templating language that supports streaming to reduce the time to first byte and to reduce the memory footprint.

Examples of templating languages that support streaming:

Other Considerations

Can I Use You?

Just because you may like a templating language doesn't mean you can use it. Some templating engines come with a lot of baggage (perhaps an entire framework) and other templating languages might have an implementation that is not compatible with your JavaScript runtime. While other templating languages might have picked a JavaScript module system that is not compatible with your application (e.g. AMD versus CommonJS).

When deciding to use a templating language you should ask yourself the following questions:

  • Which package manager(s) does the templating engine use? (npm, bower, none, etc.)
  • Can the templating language be used standalone from a framework/library?
  • Can the templating engine be used to produce HTML on the both the server and client? Just the server? Just the client?
  • Does the templating engine have dependencies and how are those dependencies managed?
  • Does the templating engine depend on a module system? (CommonJS, AMD, globals, etc.)

Supercharged or Fast Enough?

Most templating languages will perform well enough in most situations, but if your application is expected to drive a lot traffic to your front-end servers or if you are targeting mobile or low-end devices you will want to strongly consider the performance of a templating engine. Performance should be evaluated based on the following criteria:

  • CPU Usage: How fast can the template be rendered to HTML?
  • Memory Usage: How much memory does the template renderer consume during rendering?
  • Compiled Template Size: How much code does the template compiler produce?
  • Runtime Size: How large is the execution engine?
  • Streaming vs. Non-streaming: Can the output be streamed out as it is produced?

Benchmarks are a great place to start when comparing templating engines, but it is important to remember that every use case is different. Also, avoid giving too much weight to a microbenchmark that only compares a single, small template. How large a template is, how much output it produces or what features are utilized can have a big impact on performance.

Below are links to benchmarking projects that can be used to compare performance:

  1. https://github.com/marko-js/templating-benchmarks
    • NOTE: Created to compare the performance of Marko
  2. http://linkedin.github.io/dustjs/benchmark/index.html
    • NOTE: Created to compare the performance of Dust
  3. https://github.com/baryshev/template-benchmark
    • NOTE: Created to compare the performance of ECT
  4. http://paularmstrong.github.io/node-templates/benchmarks.html

Make It Your Own

Some templating languages are designed to be extended while with others you have to accept what is given to you. In addition, some templating languages only allow the runtime to be extended while others allow both the runtime and the compiler to be extended. An extensible templating language allows a language to be extended in ways that may not have been intended by the original author.

Below are examples of extensibilities features that a templating language might offer:

  • Custom helper functions
  • Custom helper tags
  • Custom code generators at compile time
  • Custom transforms at compile time (e.g. remove whitespace, add/remove nodes, etc.)
  • Custom filters
  • Import modules for use as helpers
  • Custom delimiters for tokens

If you feel that you might need to extend a templating language you should also consider the complexity of the JavaScript API. If your extensions to a templating language are very tied to a particular templating solution then you might be forced to throw away a lot of code in the future.

Marko is an example of templating language that is both extensible at compile-time and runtime with very few restrictions.

Editor Support

If you are considering a new templating language you should also strongly consider how well your templating language works with your existing tools, as well as the tools used by others on your team. At a minimum you will want to ensure that you have proper syntax highlighting. However, auto-completion, tag matching and validation will also provide significant improvements in productivity.

Additional Resources

For a more in-depth comparison between Dust and Marko, please see the related Marko versus Dust post.

Summary

Hopefully it is clear that not all templating languages are created equal and a templating language is more than just "syntax". Below is a more complete list of things you should consider when deciding on a templating language:

  • Readability: How easy is it to discern the HTML structure of a template and understand what it will produce?
  • Ramp-up Time: What does the learning curve look like?
  • JavaScript API Usability: How easy is it to use the JavaScript API to compile and render templates and also extend the language?
  • DRY (Don't Repeat Yourself): How DRY is the templating technology? Is there support for code reuse and partials?
  • Asynchronous and Streaming Support: How well is asynchronous rendering and streaming supported?
  • Interoperability: How easy is the templating language to integrate into an existing stack?
  • Extensibility: How easy can the language be extended at runtime and compile-time? What are the restrictions?
  • Rendering Performance: What is the CPU and memory overhead?
  • Page Weight: What is the weight of the runtime and the compiled templates?
  • Ease of Debugging: Is it possible to step through the code while it's running to track down errors?
  • Security: How helpful is the templating language in preventing security holes such as XSS attacks?
  • Validation: What validations can be done at compile-time to prevent errors at runtime?
  • Editor Support: Is there an editor with auto-complete, syntax highlighting, error checking, etc.?
  • Community: Is there an active community using this project? Can questions be answered via a search engine?
  • Maturity: Is this a relatively stable project or still experimenting and churning?
  • Documentation: How is the documentation?
  • Future Relevancy: Is the templating language aligned with trends in the industry?

My biased, but personal favorite is the templating language that I authored, Marko. Marko utilizes the HTML syntax and it also supports asynchronous and streaming rendering. The language was designed such that it could be easily extended at both compile-time and runtime. All Marko templates compile to CommonJS modules for improved ease of use with Node.js or a Node.js-compatible module loader (e.g. Browserify). When developing Marko, a lot of emphasis was put on performance and this is reflected in the performance benchmarks. Marko is a production-ready templating language being used at eBay and the project is being actively maintained and supported by developers at eBay and the community.

Hopefully you found this guide helpful when considering a templating language.

If you feel that I missed anything or if I made any mistakes then please let me know.

Comments

comments powered by Disqus