Marko is a high performance UI library from eBay. Marko began life as a templating language, so let's begin there.
Most templating languages are pretty simplistic, they understand their own constructs, but don't understand the HTML structure itself. Marko is different. Marko fully understands the HTML language, in fact, the first version of Marko was built on top of an off-the-shelf HTML parser. We've since developed our own parser that extends the language by adding typed attributes, argument expressions and even a concise syntax. It's quite beautiful.
<h2>Hello ${data.name}!</h2>
<ul if(data.colors.length)>
<li class="color" for(color in data.colors)>
${color}
</li>
</ul>
<div else>
No colors!
</div>
<div string="Hello" />
<div number=1 />
<div template-string=`Hello ${name}` />
<div boolean=true />
<div array=[1, 2, 3] />
<div object={ hello:'world' } />
<div variable=name />
<div function-call=data.foo() />
Marko is extremely extensible as well. Marko allows you to create custom tags which componentize your application. It's really easy. Just create a directory with a template under a components/
directory:
components/
my-component/
index.marko
Then use it in another template:
<my-component/>
But not only does Marko allow you to create custom tags, it allows you full access to the parsed AST at compile time. One way to think about it is this: what babel does for js, marko does for html. And it all compiles to very efficient code that writes strings to a stream:
out.w("<h2>Hello World</h2>");
if (data.colors.length) {
out.w("<ul>");
marko_forEach(data.colors, function(color) {
out.w("<li class=\"color\">" +
marko_escapeXml(color) +
"</li>");
});
out.w("</ul>");
} else {
out.w("<div>No colors!</div>");
}
Speaking of streams, Marko has a unique focus on performance, specifically on the first render from the server. With almost every other templating language, you first make calls to your database or backend service, wait for the data, render the template, and finally send the rendered HTML to the browser. There's no streaming. In fact, the express view engine doesn't even support streaming. But Marko takes a progressive approach: while you'll still create your database/service calls in a route handler or middleware, you'll not wait for the data to return, and instead pass the promise to the template and start rendering immediately (we support callbacks too). The template begins rendering immediately and once it hits a portion that relies on a promise to data, it flushes out what has been rendered and waits for the data to return. This progressive rendering can drastically reduce the time to first byte (TTFB).
Image from Web Fundamentals: Critical Rendering Path
Even without taking streaming into account, Marko is faster than all the popular templating engines (handlebars, pug, etc.) and an order of magnitude faster than other UI libraries that build up complex structures on the server (React, Angular, Vue, etc.)
✓ marko » 237,885 op/s (fastest) pug » 232,903 op/s (2.09% slower) dot » 209,090 op/s (12.10% slower) handlebars » 120,581 op/s (49.31% slower) dust » 38,494 op/s (83.82% slower) jade » 37,203 op/s (84.36% slower) nunjucks » 27,635 op/s (88.38% slower) vue » 10,082 op/s (95.76% slower) swig » 6,853 op/s (97.12% slower) react » 5,497 op/s (97.69% slower)Numbers from marko-js/templating-benchmarks
Graph from patrick-steele-idem/marko-vs-react
So templating is great, but this is 2017 and we need to build interactive web apps with robust client-side logic. Marko has you covered there as well. Simply take a template and add some component logic to it:
<script>
module.exports = {
onInput(input) {
this.state = {
count: input.count
}
this.initialCount = input.count
},
incrementCount() {
this.state.count++
},
resetCount() {
this.state.count = this.initialCount
}
}
</script>
<div>${state.count}</div>
<button on-click('incrementCount')>+</button>
<button on-click('resetCount')>reset</button>
For those of you cringing at JavaScript co-existing with the template, don't worry, you can pull it out into a component.js
file that lives next to the template. You can put your style.css
(or less, sass, etc.) there as well and Marko will find it.
click-counter/
component.js
index.marko
style.css
Components provide stateful rerendering powered by a virtual dom. When the state changes the template is re-run and the document is efficiently updated accordingly. Interestingly, while templates get compiled to write to a stream on the server, when compiled for the browser they asynchronously build a virtual dom tree that can be used to incrementally update the document.
Another interesting fact about Marko is that it doesn't need to re-render in order to pick up where server-rendering left off. It simply attaches to the existing dom.
If you're not into stateful rendering, that's okay too. Marko allows creating split components where the widget (client-side logic) is defined in a widget.js
. This looks almost identical to our previous component except the widget will directly manipulate the dom to provide UI updates. Because we don't need to re-render, we can actually exclude the template logic from the code that is sent down to the browser (assuming an initial server render).
In fact, if all your components use the split widget, not only will the templates not get sent down, neither will the template runtime, nor the virtual dom implementation. You see, Marko components can determine what parts of the runtime they need and only pull those parts in. They're able to share code with other templates/components but don't pull in anything more than they require.
- Check out our demo which showcases the simplicity of the next version of Marko (to be released very soon).
- Join our gitter channel, we'd love to talk!