We are excited to announce the release of Marko! Earlier this year, we undertook the task of making substantial usability improvements to the Marko language and API based on the feedback received from the community. We also made significant performance improvements that resulted in Marko being far faster and leaner than all competitors (see benchmarks).
If you are not familiar with Marko, it is an extensible, streaming, asynchronous, high performance, HTML-based templating language that can be used in Node.js or in the browser. Marko was founded on the philosophy that an HTML-based templating language is more natural and intuitive for generating HTML.
In this release the language is fundamentally the same, but we have removed the friction that made it more difficult to adopt Marko templates in the past. Notably, we have switched from an XML parser to an HTML parser. In addition, the AMD-based modules were replaced with CommonJS-based modules for better compatibility with the Node.js module loader.
Table of Contents
- Marko at a Glance
- Summary of Changes
- Change Details
- Separated out Marko into a new top-level module
- Switched from XML to HTML
- Simplified the JavaScript API
- Improved streaming support
- Switched to CommonJS modules for compiled templates
- Added support for importing Node.js modules as template helpers
- Introduced a simple command line utility for compiling templates
- Improved debugging
- Simplified the asynchronous rendering API
- Introduced directory scanning for easier taglib maintenance
- Switched from XML to JSON for taglibs
- Further Reading
- Try out Marko Today!
Marko at a Glance
Design Philosophy
- 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.
Installation
To install the marko module into your Node.js project, the following command should be used:
npm install marko --save
Sample Code
A basic template with text replacement, looping and conditionals is shown below:
Hello ${data.name}!
<ul if="notEmpty(data.colors)">
<li style="color: $color" for="color in data.colors">
$color
</li>
</ul>
<div c-else>
No colors!
</div>
The template can then be rendered as shown in the following sample code:
var template = require('./hello.marko');
template.render({
name: 'World',
colors: ["red", "green", "blue"]
},
function(err, output) {
console.log(output);
});
Alternatively, you can choose to stream out the rendered HTML. For example:
var template = require('./hello.marko');
var out = require('fs').createWriteStream('hello.html', 'utf8');
template.stream(data)
.pipe(out);
With Marko you can start streaming rendered templates directly to an HTTP response stream. For example, with Express:
var template = require('./profile.marko');
app.get('/profile', function(req, res) {
template.stream({
name: 'Frank'
})
.pipe(res);
});
Summary of Changes
Here's a summary of the changes in this new major release:
- Code cleaned up and reorganized
- Switched from an XML parser to an HTML parser
- Dropped the root
<c:template>
tag - Taglibs no longer have to be imported (no more
xmlns
) - Dropped support for the
params
attribute - Dropped namespaces in favor of dash separated names
- Dropped the root
- Switched to CommonJS modules everywhere
- Switched from AMD to CommonJS for the runtime
- Templates now compile down to CommonJS modules for better integration with Node.js (no named templates)
- Designed to support Node.js and Node.js module bundlers that target the browser
- Simplified the JavaScript API
- Templates can now be easily loaded to get back an object with a
render
method - Improved streaming support
- Simplified the asynchronous rendering API
- Templates can now be easily loaded to get back an object with a
- Simplified taglibs
- Switched from XML to JSON for taglibs
- Custom tags and custom attributes must have a dash in the name
- Taglibs automatically discovered based on template location
- Taglibs installed via
npm
will automatically be discovered - Added support for directory scanning for automatic taglibs
- Custom tag renderers now referenced by relative path
- Custom tag attribute definitions are now optional
- Custom tag definition can be put into the renderer's JavaScript file instead of in a separate file
- Miscellaneous
- Node.js modules can be imported into templates for use as helpers
- Introduced a simple and powerful command-line compiler: markoc
- Improved performance now far exceeds the competition! Please see benchmarks.
- Improved debugging
Change Details
Separated out Marko into a new top-level module
The https://www.npmjs.org/package/marko module can now be installed independently of any other modules:
npm install marko
Project links:
Switched from XML to HTML
OLD:
<c:template xmlns:c="core"
xmlns:app="app"
params="name,colors">
Hello $name!
<app:button label="Hello World"/>
<ul c:if="notEmpty(colors)">
<li style="color: $color" c:for="color in colors">
$color
</li>
</ul>
<div c:else="">
No colors!
</div>
</c:template>
NEW:
Hello $data.name!
<app-button label="Hello World"/>
<ul if="notEmpty(data.colors)">
<li style="color: $color" for="color in data.colors">
$color
</li>
</ul>
<div c-else>
No colors!
</div>
Simplified the JavaScript API
var template = require('./hello.marko');
template.render({
name: 'World',
colors: ["red", "green", "blue"]
},
function(err, output) {
console.log(output);
});
Improved streaming support
var template = require('./hello.marko');
var out = require('fs').createWriteStream('hello.html', 'utf8');
template.render(data, out);
Switched to CommonJS modules for compiled templates
Sample compiled template:
module.exports = function create(__helpers) {
var empty = __helpers.e,
notEmpty = __helpers.ne,
escapeXml = __helpers.x,
forEach = __helpers.f;
return function render(data, context) {
context.w('Hello ' +
escapeXml(data.name) +
'! ');
if (notEmpty(data.colors)) {
context.w('<ul>');
forEach(data.colors, function(color) {
context.w('<li class="color">' +
escapeXml(color) +
'</li>');
});
context.w('</ul>');
}
else {
context.w('<div>No colors!</div>');
}
};
}
Added support for importing Node.js modules as template helpers
Since all templates are now compiled into CommonJS modules, you can easily import other Node.js modules into your template for use as helpers:
<require module="change-case" var="changeCase"/>
Hello ${changeCase.titleCase(name)}!
NOTE: This assumes that the change-case module was installed into the project using the following command: npm install change-case
Introduced a simple command line utility for compiling templates
Usage:
markoc hello.marko
Running the above command will generate a file named hello.marko.js
.
Please see Template Compilation for more details.
Improved debugging
With the new version of Marko, every compiled template is written to disk next to the original file. The CommonJS module that is written to disk is then loaded using the standard Node.js require
. By loading the compiled template from disk, you can easily step through the generated code and read through the generated code in your editor.
For example, running the following command:
markoc hello.marko
Will result in a new hello.marko.js
file in the same directory.
You will want to add *.marko.js
to your source code ignore file (e.g. .gitignore
).
Simplified the asynchronous rendering API
module.exports = function render(input, context) {
var asyncContext = context.beginAsync();
userService.loadUser(input.userId, function(err, userInfo) {
asyncContext.write('Hello ' + userInfo.name + '!');
asyncContext.end();
})
};
Introduced directory scanning for easier taglib maintenance
Please see Scanning for Tags
Switched from XML to JSON for taglibs
Old taglib:
rebootstrap.rtld
<raptor-taglib>
<uri>rebootstrap</uri>
<import-taglib path="rebootstrap/ui/components/buttons/Button/Button.rtld"/>
</raptor-taglib>
./ui-components/buttons/Button/Button.rtld
<raptor-taglib>
<tag name="button" renderer="rebootstrap/ui/components/buttons/Button/ButtonRenderer" dynamic-attributes="true">
<attribute name="id" type="string"/>
<attribute name="label" type="string"/>
<attribute name="href" type="string"/>
<attribute name="variant" type="string" description="primary | info | success | warning | danger | inverse"/>
<attribute name="size" type="string" description="large | small | mini"/>
<attribute name="toggle" type="boolean"/>
<attribute name="toggled" type="boolean"/>
<attribute name="dropdown" type="boolean"/>
<!-- Support widget attributes -->
<attribute name="id" uri="widgets"/>
<attribute name="event-click" uri="widgets"/>
</tag>
</raptor-taglib>
New taglib:
raptor-taglib.json
{
"tags": {
"app-button": "./components/app-button/raptor-tag.json",
"app-hello": {
"renderer": "./components/app-hello/renderer.js",
"attributes": {
"name": "string",
}
}
}
}
./components/app-button/raptor-tag.json
{
"renderer": "./renderer",
"attributes": {
"id": "string",
"label": "string",
"href": "string",
"variant": {
"type": "string",
"description": "primary | info | success | warning | danger | inverse"
},
"size": {
"type": "string",
"description": "large | small | mini"
},
"toggle": "boolean",
"toggled": "boolean",
"dropdown": "boolean",
"id": {
"namespace": "raptor-widgets"
},
"event-click": {
"namespace": "raptor-widgets"
}
}
}
For more details, please see marko » README.md » Custom Taglibs.
Further Reading
- Github » marko: Documentation and source code for Marko
- Marko versus Dust: An in-depth comparison between Marko and Dust
- The JavaScript Templating Landscape: A broad comparison of the different types of JavaScript templating languages that are compatible with Node.js
- templating-benchmarks: Performance comparison of popular templating languages (including CPU usage, runtime size and compiled template sizes)
Try out Marko Today!
Check out the latest Marko documentation and get started today. If you find any problems or have any feature requests please create a new issue on the Github Issues page.
Please share this post to expand the community and help make the new Marko the templating language of choice for more developers.