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)* |
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}
- Escaped:
- 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) :
AsyncWriterrequire('marko/compiler').compile(src, path, options)
require('marko/compiler').compileFile(path, options, callback)
Template
renderSync(data) : String
render(data, callback)
render(data, stream) :
AsyncWriterstream(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:
- Emacs using web-mode
- 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:
- HTML Syntax: Utilizing the HTML syntax is a much more natural fit for producing HTML output.
- Simpler: Utilizing JavaScript as the expression language, a simpler API and favoring simple JavaScript objects enables better performance and makes Marko easier to learn.
- Performance: Marko has significantly better performance, produces smaller compiled templates and has a lighter runtime.
- Editor Support and Validation: The HTML syntax and declarative taglibs allow for better editor support and validation.
- 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: