Marko versus Dust

This guide aims to compare Marko and Dust to help guide developers deciding between the two. However, even if you are considering other templating languages, such as Handlebars or Mustache, this guide should still be helpful.

Why Marko and Dust? Dust represents a well-established text-based templating language and Marko represents a well-established HTML-based templating language. Marko was created as an alternative to Dust when Dust was first starting to be used at eBay.

Disclaimer: This guide is my opinion, and as the author of Marko it is very difficult for me to be unbiased. Also, I've used Dust enough to be proficient in it, but I would not claim to be a Dust expert. If you feel that anything is misleading or wrong then please let me know and I will correct it. Also, my goal was to be as objective and as fair as possible, but some things are inherently subjective.

Table of Contents

Design Philosophies

Before we go into the features of each templating language, let's take a look at the design philosophies that helped shape each language.

Marko

  • Readable: Templates should be as close to the output HTML as possible to keep templates readable. Cryptic syntax and symbols should be avoided.
  • Simple: The number of new concepts should be minimized and complexity should be avoided.
  • Extensible: The template engine should be easily extensible at both compile-time and runtime.
  • High Performance: Runtime and compiled output should be optimized for low CPU and memory usage and have a small footprint. All expressions should be native JavaScript to avoid runtime interpretation.
  • Not Restrictive: Whether or not to go less logic or more logic is up to the developer.
  • Asynchronous and Streaming Output: It should be possible to render HTML out-of-order, but the output HTML should be streamed out in the correct order. This minimizes idle time and reduces the time to first byte.
  • Intuitive: The templating engine should introduce as few surprises as possible.
  • Browser and Server Compatibility: Templates should compile down to JavaScript that can be executed on both the server and the client.
  • Debuggable: Compiled JavaScript should be debuggable and readable.
  • Compile-Time Checks: Syntax, custom tags and custom attributes should be validated at compile-time.
  • Tools Support: Tools should be enabled to offer auto-completion and validation for improved productivity and safety.
  • Modular: Runtime and compiled templates should be based on CommonJS modules for improved dependency management. Template dependencies (such as custom tags) should be resolved based on a template's file system path instead of relying on a shared registry.

Dust

From the original author of Dust:

  • Markup-like: A templating syntax should not encompass operations that are better left to a programming language. Templates should make it easy to format content for presentation while keeping application logic where it belongs: in the application.
  • Asynchronous: Template helpers should be callable asynchronously and in parallel so that expensive operations (caching, deferred loading, filtering) can run as the template is being rendered.
  • Streaming: Templates should allow (but not require) data to be flushed to the output in user-defined chunks.
  • Browser and server compatible: Templates should render in both server and browser environments without hacks or extensive configuration.
  • Storage agnostic: The templating engine should not impose a particular loading scheme. Developers should be free to load templates via the filesystem, a database or an army of carrier pigeons.
  • Composable: Designers should be able to break presentation markup into manageable components and combine these components at runtime. It should not be necessary to statically link templates or manually assemble 'layouts' inside application code.
  • Format agnostic: While HTML generation and DOM manipulation are useful in specific instances, a general-purpose template system should not be tied to a particular output format.
  • Precise: The parser should be accurate enough that designers rarely have to use escape sequences to achieve the desired result. Similarly, templates shouldn't mysteriously generate or eliminate whitespace.
  • Safe(r): At the very least, the engine should be configurable such that it is reasonably safe to render untrusted templates in a server environment.
  • Fast: Server-side templates cannot always be cached. Browser-side templates may be rendered on devices with limited system resources. A template engine should be fast enough to render in real time without bottlenecking its environment.

Feature Comparison

Language

Marko Dust
Syntax HTML syntax Proprietary symbol-based syntax
Data referencing JavaScript expressions Simple paths
Data passing Simple JavaScript objects Custom Dust wrapper objects with hierarchical data lookup methods
Variable scoping JavaScript closures and local variables Push/pop/rebase data contexts
Conditionals JavaScript expressions Truthy/falsey test with alternate negated test
if…else-if…else if…else only
Simple ternary conditionals
Loop iteration data Named JavaScript variable Pushed data context
Loop status variable index, length, isLast and isFirst index and length
Loop separator
Custom loop iterators
Declare variables in template
Embed JavaScript
Simple function calls ✔ (JavaScript expressions)
Auto-escaping
Template inheritance ✔ (layout taglib)
Macros ✔ (inline partials)
Includes/Partials

Runtime

Marko Dust
Asynchronous fragments
Async fragment timeouts
Streaming
Output format Text Text

Compiler

Marko Dust
Compiled output format CommonJS module Self-executing function that registers a named template (depends on a global dust variable)
Compile-time checks Validate allowed attributes Syntax check only
Compilation error reporting Filename, column and line number and error message Column and line number and error message
Compiled conditionals Native JavaScript if Interpreted
Compiled looping Native JavaScript forEach or for Interpreted
Compiled body blocks JavaScript function JavaScript function
Command-line compiler
Precompile templates for server Custom loader code required
Precompile templates for client
Whitespace Suppression ✔ (based on HTML structure) ✔ (based on text)

Performance

Marko Dust
CPU usage Fastest* Between 25% and 77% slower*
Compiled template size Smallest* Between 2% and 36% larger*
Runtime size Smallest (~2.33KB)* Larger (~3.41KB minimum, or ~4.7KB with optional helpers)*

*See templating benchmarks

Debugging

Marko Dust
Stack traces on server Valid stack traces and file paths Source code not written to disk
Readable compiled output?

Extensibility

Marko Dust
Custom tags ✔ (HTML tags) ✔ (Dust helper tags)
Custom HTML attributes N/A
Custom tag signature function(input, out) function(chunk, context, bodies, params)
Custom compile-time transformations ✔ (pseudo DOM API for transforming AST) ✖ (not well-supported)
Custom compile-time code generation ✔ (custom AST nodes) ✖ (not well-supported)
Import vanilla Node.js modules as helpers?
Custom helper registration Auto discovered (including taglibs installed using npm) Application code required
Example: require('dustjs-helpers')
Declarative taglibs

Interoperability

Marko Dust
Package manager npm npm or pre-built distribution
Node.js support
Node.js-compatible module loader required? Yes No
Client-side support
Browserify support ✔ (see markoify) Maybe (see dust-browserify)
Lasso.js support

Community

Marko Dust
Original author Patrick Steele-Idem (eBay) Aleksander Williams (no longer active)
Primary maintainers eBay eBay and LinkedIn
Source code Github Github
Bugs and Feature Requests Github Issues Github Issues
npm marko dustjs-linkedin
Support Forums Gitter Stack Overflow

Code Comparison

Dynamic Text

Marko

Hello $name!

<!-- Unescaped: -->
$!profile

Dust:

Hello {name}!

{! Unescaped: !}
{profile|s}

Looping

Marko

<ul>
    <li for="friend in friends">
        $friend.name
    </li>
</ul>

Dust:

<ul>
    {#friends}
    <li>
        {name}
    </li>
    {/friends}
</ul>

Loop Status Variable

Marko

<ul>
    <li for="friend in friends; status-var=loop">
        ${loop.getIndex() + 1} of ${loop.getLength()}) $friend.name
    </li>
</ul>

Dust:

<ul>
    {#friends}
    <li>
        {@math key="{$idx}" method="add" operand="1" /} of {$len}) {name}
    </li>
    {/friends}
</ul>

Output:

<ul>
    <li>
        1 of 3) Frank
    </li>
    <li>
        2 of 3) John
    </li>
    <li>
        3 of 3) Jane
    </li>
</ul>

Conditionals

Marko

<div if="isLoggedIn">
    Welcome back, ${name}!
</div>

<span class="balance {?balance<0;negative;positive}">
    ${formattedBalance}
</span>

Dust:

{#isLoggedIn}
<div>
    Welcome back, {name}!
</div>
{/isLoggedIn}

<span class="balance {@if cond="{balance} < 0"}negative{:else}positive{/if}">
    ${formattedBalance}
</span>

Template Inheritance

Marko - base template:

<html>
    <head>
        <title><layout-placeholder name="title"/></title>
    </head>
    <body>
        <layout-placeholder name="body"/>
    </body>
</html>

Marko - child template:

<layout-use template="../layouts/base-template.marko">
    <layout-put into="title">
        Child Title
    </layout-put>
    <layout-put into="body">
        Child Content
    </layout-put>
</layout-use>

Dust - base template:

<html lang="en">
    <head>
        <title>{+title/}</title>
    </head>
    <body>
        {+body/}
    </body>
</html>

Dust - child template:

{>layouts/base-template}
{<title}
    Child Title
{/title}
{<body}
    Child Content
{/body}

Custom Tags

Marko

<my-hello name="Frank"/>
module.exports = function render(data, out) {
    out.write('Hello ' + data.name + '!');
}

Dust:

{@my-hello name="Frank" /}
dust.helpers.hello = function helperTag(chunk, context, bodies, params) {
    return chunk.write('Hello ' + params.name + '!');
}

Detailed Comparisons

Overview

In the following sections we will take a deep dive into various aspects of Marko and Dust. Specifically, we will evaluate each templating language based on the following criteria:

  • 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.?
  • Documentation: How is the documentation?
  • Future Relevancy: Is the templating language aligned with trends in the industry?

Readability

Marko

  • No wrapping of HTML is required for conditional and repeated blocks
  • HTML templates look like HTML
  • Final HTML structure is immediately obvious

Dust

  • Less logic due to restrictions
  • Simpler code for checking empty arrays
  • Template directives wrap HTML elements and make HTML structure harder to discern

Readability is largely subjective, but let's compare a template from each language:

Marko

Hello ${data.name}!

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

Dust:

Hello {name}!

{?colors}
<ul>
    {#colors}
    <li class="color">
        {.}
    </li>
    {/colors}
</ul>
{:else}
<div>
    No colors!
</div>
{/colors}

A few things to note for the Marko template:

  • Less lines of code
  • Less lines are "touched"
  • Only opening tags are modified for conditionals and looping

Ramp-up Time

Marko

  • Familiar HTML syntax is used as the input language
  • JavaScript is used as the expression language

Dust

  • Proprietary symbol-based syntax
  • Only simple references are allowed
  • Special loop variables have a fixed name (poor handling of nested loops)

Comparing ramp-up time is difficult, but below is a summary of the new concepts that must be understood to truly master each templating language.

Marko

  • Expressions
    • Escaped: $property.path, ${javaScriptExpression}
    • Unescaped: $!property.path, $!{javaScriptExpression}
  • Simple conditionals: ({?<javascript-expression>;<true-template>[;<false-template>]})
  • Custom tags and custom attributes
    • marko-taglib.json
    • marko-tag.json

NOTE: The core taglib which includes if, for, etc., were not included since those are implemented using custom tags and custom attributes.

Dust:

  • Partials: {>foo name="bar"/}
  • Blocks: {:blockname}, {:else}
  • Special blocks: {+blockname}…{/blockname}
  • Filters: {name|s|h|u}
  • Helper tags: {@somehelper}…{/somehelper}
  • Block handlers: {#somehandler}…{/somehandler}
  • Conditional blocks: {?somekey}…{/somekey}
  • Negated conditionals: {^somekey}…{/somekey}
  • Looped sections: {#somearray}{/somearray}
  • Looping separator: {@sep}, {/sep}
  • Loop variables: $idx, $len
  • Comments: {! … !}

Obsolete or rarely used Dust syntax:

  • Partial context: {>name:.context}
  • Recursion: >
  • Pragmas: %pragma
  • New line: {~n}
  • Escaping using pragmas: {%esc:s}…{/esc}
  • Looping index: {@idx}{.}{/idx}

While Dust has a lot in common with Handlebars and Mustache, it is still a unique language. In comparison, Marko uses the HTML syntax to introduce templating directives (except in the case of dynamic text tokens).

The JavaScript API also impacts ramp-up time and that is described in the next section.

JavaScript API Usability

Marko

  • Simple and intuitive API
  • Favors passing simple JavaScript objects
  • JavaScript is used as the expression language
  • Asynchronous rendering context is independent of Marko
  • The API for Marko is simpler and more intuitive.

Dust

  • More complicated API
  • Some esoteric method names (e.g. "tap", "shiftBlocks", "makeBase", etc.)
  • Hierarchical data lookup adds complexity and can be error-prone

Here's a comparison of both public APIs:

Marko

  • require('marko').load(path) : Template
  • require('marko').unload(path)
  • require('marko').createWriter(stream) :AsyncWriter
  • require('marko/compiler').compile(src, path, options)
  • require('marko/compiler').compileFile(path, options, callback)
  • Template
    • renderSync(data) : String
    • render(data, callback)
    • render(data, stream) :AsyncWriter
    • stream(data) : Stream
  • AsyncWriter
    • this.write(str)
    • this.beginAsync(timeout)
    • this.end(timeout)
    • this.on(event, callback)
    • this.once(event, callback)
    • this.emit(event, …args)
    • this.global = {}

Dust:

  • require('dustjs-linkedin').helpers = {}
  • require('dustjs-linkedin').cache = {}
  • require('dustjs-linkedin').register(name, tmpl)
  • require('dustjs-linkedin').render(name, context, callback)
  • require('dustjs-linkedin').stream(name, context)
  • require('dustjs-linkedin').renderSource(name, context, callback)
  • require('dustjs-linkedin').compileFn(source, name)
  • require('dustjs-linkedin').load(name, chunk, context)
  • require('dustjs-linkedin').loadSource(source, path)
  • require('dustjs-linkedin').makeBase(global)
  • Context
    • wrap(context, name)
    • this.get(key)
    • this.getPath(cur, down)
    • this.push(head, idx, len)
    • this.rebase(head)
    • this.current()
    • this.getBlock(key, chk, ctx)
    • this.shiftBlocks(locals)
  • Chunk
    • this.write(data)
    • this.end(data)
    • this.map(callback)
    • this.tap(tap)
    • this.untap()
    • this.render(body, context)
    • this.reference(elem, context, auto, filters)
    • this.section(elem, context, bodies, params)
    • this.exists(elem, context, bodies)
    • this.notexists(elem, context, bodies)
    • this.block(elem, context, bodies)
    • this.partial(elem, context, params)
    • this.helper(name, context, bodies, params)
    • this.capture(body, context, callback)
    • this.setError(err)

DRY

Marko

  • Custom HTML tags
  • Includes/partials
  • Template inheritance
  • Macros (inline parameterized partials)

Dust

  • Custom helper tags
  • Includes/partials
  • Template inheritance
  • Inline partials

Where Dust falls short in this category is that it requires you to repeat expressions used for sections and blocks. For example:

Marko


<ul if="notEmpty(person.friends)">
    <li class="friend" for="friend in person.friends">
        ${friend}
    </li>
</ul>

In the above Marko template, the person.friends expression is only repeated twice.

Dust:

{?person.friends}
<ul>
    {#person.friends}
    <li class="friend">{.}</li>
    {/person.friends}
</ul>
{/person.friends}

In the above Dust template, the person.friends expression is repeated for a total of four times.

Asynchronous and Streaming Support

Marko

  • Simple API
  • Uses native Node.js streams
  • Streams can be piped to an asynchronous render context
  • Supports timeouts for asynchronous writers

Dust

  • Asynchronous chunk created by "chunk.map" must be returned
  • Does not use native Node.js streams
  • Streams cannot be piped to chunks
  • Asynchronous chunks do not support timeouts

Examples of using the asynchronous rendering API for each templating engine is shown below:

Marko

module.exports = function render(data, out) {
    var asyncOut = out.beginAsync();
    setTimeout(function() {
        asyncOut.end('Hello ' + data.name + '!');
    }, 1000);
}

Dust:

dust.helpers.hello = function tagHandler(chunk, context, bodies, params) {
    return chunk.map(function(asyncChunk) {
        setTimeout(function() {
            chunk.end('Hello ' + params.name + '!');
        }, 1000);
    })
};

Marko has the advantage that the async-writer module used to support asynchronous rendering to a stream is a public module independent of Marko.

Interoperability

Marko

  • Works on client and server
  • Dependent on npm and a Node.js-compatible module loader
  • Marko works seamlessly with the Node.js ecosystem
    • Marko template files compile to CommonJS modules (no named templates or global template registry)
    • Any CommonJS module can be required and used inside a Marko template
    • Custom taglibs installed via npm are automatically discovered
  • Custom tag renderers are coded using a simple API that is independent of Marko
  • Runtime and compiled templates compatible with Browserify and the Lasso.js for client-side support

Dust

  • Works on client and server
  • Dependency free
  • Easy to use in any JavaScript environment

Neither Marko nor Dust will conflict with your existing code.

Adopting CommonJS modules as the output for compiled Marko Template files has some advantages. For example, the following code illustrates how a vanilla CommonJS module can be imported into a template for use as a helper:

<require module="./my-util" var="util" />
Hello ${util.reverse(name)}!

Extensibility

Marko

  • Custom HTML tags
  • Custom HTML attributes
  • Custom compile-time transformers (psuedo DOM API)
  • Custom compile-time code generators for AST nodes
  • Custom taglibs that can be installed using npm
  • CommonJS modules can be imported as helpers inside templates
  • Custom taglibs are automatically discovered based on template file path
  • Simple API to build custom tag renderers

Dust

  • Custom helper tags
  • Custom compile-time AST transforms (not well-documented)
  • Code required to register helper tags

Let's take a look at a sample custom tag for Marko and a sample custom helper for Dust:

Marko

module.exports = function render(data, out) {
    out.write('Hello ' + data.name + '!');
}

Dust:

dust.helpers.hello = function(chunk, context, bodies, params) {
    return chunk.write('Hello ' + params.name + '!');
};

Let's take a look at more complicated example borrowed from the Dust.js tutorial. With this example our goal is to achieve the following:

Marko

<util-substr str="xxx" begin="x" end="y" len="z" />

Dust:

{@substr str="xxx" begin="x" end="y" len="z" /}

NOTE: This example is for demonstration purposes only. The <util-substr> tag would be useless for Marko since you can use JavaScript expressions in your template. For example:

${str.slice(0, -2)}

Marko

module.exports = function render(data, out) {
    var str = data.str;
    var begin = data.begin || 0;
    var end = data.end;
    var len = data.len;

    if (len != null) {
        str = str.substr(begin, len);
    } else {
        str = str.slice(begin, end);
    }

    out.write(str);
}

Below is the implementation from the Dust documentation (cleaned up):

Dust:

dust.helpers.substr = function (chunk, ctx, bodies, params) {
    // Get the values of all the parameters. The tap function takes care of resolving
    // any variable references used in parameters (e.g. param="{name}")
    var str = dust.helpers.tap(params.str, chunk, ctx);
    var begin = dust.helpers.tap(params.begin, chunk, ctx) || 0;
    var end = dust.helpers.tap(params.end, chunk, ctx);
    var len = dust.helpers.tap(params.len, chunk, ctx);

    if (len != null) {
        str = str.substr(begin, len);
    } else {
        str = str.slice(begin, end);
    }

    return chunk.write(str);
}

Because Marko favors simple JavaScript objects, the tag implementation is much simpler for Marko.

Marko was designed to be extended via an API that is not tied to Marko. This allows custom tag renderers to be usable outside the context of template rendering (such as being called directly from JavaScript code).

With Marko, the data argument is just a standard JavaScript object with no tie-ins to Marko. The out argument is an AsyncWriter object that wraps a standard Node.js stream and it is independent of Marko.

In comparison, the chunk argument is a Dust Chunk object. The context is a Dust Context object and the bodies object is very specific to Dust as well. When you are building helpers for Dust, those helpers will only work with Dust (unless an abstraction is utilized).

Rendering Performance

Marko

  • Fastest streaming templating engine
  • Less method calls during rendering
  • Native JavaScript expressions, if conditions and loops

Dust

  • Slower
  • Less optimized compiled JavaScript
  • Hierarchical context lookups can be slower

Marko has significantly better rendering performance when compared to Dust. For benchmarks, please see Templating Benchmarks.

The memory utilization has not been compared, but I suspect that the memory usage is very similar.

Page Weight

Marko

  • Smaller runtime
  • Smallest compiled templates

Dust

  • Larger runtime
  • Larger compiled templates

Marko produces smaller compiled templates and has a smaller runtime. For benchmarks, please see Templating Benchmarks.

Ease of Debugging

Marko

  • Very clean and readable compiled JavaScript code
  • Stack traces accurate on the server (compiled source loaded from disk using require)

Dust

  • Less readable JavaScript output
  • Compiled JavaScript code is all on one line

Example compiled output for Marko:

module.exports = function create(__helpers) {
  var empty = __helpers.e,
      notEmpty = __helpers.ne,
      escapeXml = __helpers.x,
      forEach = __helpers.f;

  return function render(data, out) {
    out.w('Hello ' +
      escapeXml(data.name) +
      '! ');

    if (notEmpty(data.colors)) {
      out.w('<ul>');

      forEach(data.colors, function(color) {
        out.w('<li class="color">' +
          escapeXml(color) +
          '</li>');
      });

      out.w('</ul>');
    }
    else {
      out.w('<div>No colors!</div>');
    }
  };
}

Example compiled output for Dust:

(function(){dust.register("simple-1",body_0);function body_0(chk,ctx){return chk.write("Hello ").reference(ctx.get(["name"], false),ctx,"h").write("!").exists(ctx.get(["colors"], false),ctx,{"block":body_1},null).notexists(ctx.get(["colors"], false),ctx,{"block":body_3},null);}function body_1(chk,ctx){return chk.write(" <ul>").section(ctx.get(["colors"], false),ctx,{"block":body_2},null).write("</ul>");}function body_2(chk,ctx){return chk.write("<li class=\"color\">").reference(ctx.getPath(true, []),ctx,"h").write("</li>");}function body_3(chk,ctx){return chk.write("<div>No colors!</div>");}return body_0;})();

Security

Marko

  • Automatic HTML escaping of dynamic text (enabled by default)
  • Allows arbitrary JavaScript code to be embedded in a template

Dust

  • Automatic HTML escaping of dynamic text (enabled by default)
  • Does not allow arbitrary JavaScript code to be embedded in a template
  • Templates are guaranteed to be safe

Marko does allow arbitrary JavaScript code to be used inside templates (all expressions in Marko templates are JavaScript). For this reason, it would not be appropriate to render untrusted Marko templates provided by a third-party (at least without sandboxing at a higher level). In comparison, expressions in Dust are interpreted at runtime and this approach does not allow arbitrary JavaScript code to be embedded in a template.

Validation

Marko

  • Attributes passed to custom tags are validated at compile-time
  • Better type handling
  • Custom code can easily be used to validate template structure at compile-time
  • HTML parser can easily be used to analyze templates

Dust

  • Only syntax is checked at compile-time
  • Parameters passed to helper tags are not validated at compile-time
  • Silently discards helper tags that it does not recognize

As an example, the following code declares a simple custom tag for Marko, along with allowed attributes:

{
    "tags": {
        "my-hello": {
            "renderer": "./renderer",
            "attributes": {
                "name": "string",
                "age": "integer"
            }
        }
    }
}

The following template will successfully compile:

<my-hello name="Frank" age="20"/>

However, the following template will result in an error during compilation:

<my-hello name="Frank" gender="male"/>

Error message:

Error: Errors in template:

1) [test-project/custom-tag.marko:1:0] The tag "my-hello" in taglib "test-project/marko-taglib.json" does not support attribute "gender" (<test-hello>marko

NOTE: With Marko, declaring allowed attributes is an optional feature. As a custom tag developer, you can choose to allow all attributes by omitting the attributes property or by using the special "*": "string" attribute definition.

Editor Support

Marko

  • Custom language grammar for Atom: language-marko
  • Compatible with any HTML editor
  • Tag completion, HTML validation and syntax highlighting for free
  • Declarative custom tags and attributes enables plugin developers to provide better validation and auto-completion
  • Custom taglibs are automatically discovered (no code required)
  • Any HTML parser can be used to analyze a Marko template

Dust

  • Editor support:
  • Code required to register custom helper tags
  • No schema is attached to custom helper tags
  • Proprietary and evolving AST

Documentation

Marko

  • Fairly complete documentation
  • Documentation is in a README.md file
  • Legacy online testbed needs to be migrated to new version

Dust

  • Fairly complete documentation
  • Node.js and JavaScript API documentation lacking
  • Poor examples in online testbed
  • Documentation is on Wiki and not README file (no Pull Requests)

Documentation for both Marko and Dust could use some improvement in a few areas, but overall the documentation is pretty good for both.

Future Relevancy

Marko

  • Web Components and custom tags are becoming more popular
  • Marko custom tags can be swapped out with Web Component custom tags
  • Marko custom tag renderers can be used to render a shadow DOM
  • Very easy to extend at compile-time and runtime
  • Custom tags are not tied to Marko

Dust

  • Custom helper tags are very much tied to Dust
  • Does not embrace custom HTML tags

Custom tags are an exciting trend that allow authors to define their own HTML tags that encapsulate view and behavior. The emerging Web Components standard and Marko both support custom tags. In addition, the popular AngularJS framework also supports custom tags (called directives). The difference between a Marko custom tag and a Web Components custom tag is that a Marko custom tag is expanded to HTML when rendered on either the server or the client and a Web Components custom tag is expanded to a shadow DOM at time of insertion into the DOM. Custom elements are a very natural fit for web development and that appears to be an industry trend. In addition, it is possible to use the renderer for a Marko custom tag to produce the shadow DOM for a Web Components custom tag.

Because Marko also adopts custom tags, Marko Template custom tags can be seamlessly swapped out with Web Component custom tags.

Another thing to consider is that because the Marko compiler recognizes the HTML structure of a template it is feasible to produce a DOM (or virtual DOM) when rendering a Marko template. This, of course, would require a different version of the compiler to be used, but the language or templates would not need to change at all.

Lastly, Marko is very extensible at compile-time and render time. This allows almost any feature to be added to the language with no restrictions. For example, while Marko doesn't support data binding out-of-the-box, it is feasible to introduce a custom taglib to support data binding. For example:

<div data-bind-text="someProperty"></div>
<ul>
    <li data-bind-for="color in colors">$color</li>
</ul>

Summary

If you made it this far you are either skimming to get final thoughts or you really want to make a good decision when deciding to invest in a templating language (if not the former, thank you for reading!).

As the developer of Marko, my obvious choice for an HTML templating language is Marko. Below are what I believe are the top five advantages that Marko offers over Dust:

  1. HTML Syntax: Utilizing the HTML syntax is a much more natural fit for producing HTML output.
  2. Simpler: Utilizing JavaScript as the expression language, a simpler API and favoring simple JavaScript objects enables better performance and makes Marko easier to learn.
  3. Performance: Marko has significantly better performance, produces smaller compiled templates and has a lighter runtime.
  4. Editor Support and Validation: The HTML syntax and declarative taglibs allow for better editor support and validation.
  5. Extensibility: Marko is easily extensible at runtime and compile-time, which allows for endless possibilities and ensures you will never be forced to settle for a sub-optimal solution.

As with all newer technologies, I had the chance to look at the current landscape and find room for improvement. While templating languages might seem like a solved problem, I think you will find the innovation that is part of Marko refreshing. Moving from a text-based templating language, such as Dust, to Marko may require a shift in mindset, but I believe the gains in productivity will be rewarding.

Marko is being used at eBay and other companies and it is continuing to evolve. I encourage you to join the community and help make Marko the templating language of choice for more developers.

If you are interested in a broader view of JavaScript templating languages, please see the related The JavaScript Templating Landscape post.

Special thanks to the following folks for reviewing this post and providing valuable feedback:

Comments

comments powered by Disqus