Mikado is the webs fastest template engine for building user interfaces. Carefully crafted to get the most out of the browser. Also providing the fastest Express Render Engine of today. Super-lightweight, outstanding performance, no dependencies.
Getting Started • Options • API • Benchmark • Template Compiler • Server-Side-Rendering • Express Render Engine • Reactive • Hydration • Web Components (Shadow DOM) • Changelog
When you are coming from any previous version: Migration Guide 0.8.x
Benchmark:
Demo:
Mikado was getting so much positive feedback and also feature requests. Help keeping Mikado active by a personal donation.
Rendering has great impact on application performance, especially on mobile devices. Mikado takes templating performance to a new level and provides you keyed, non-keyed recycling and also reactive paradigm switchable out of the box. Additionally, Mikado provides a server-side-rendering approach on a top-notch performance level along full support for hydration to inject templates progressively within the client's runtime. Server and client are sharing the same template definitions simply written in HTML-like markup. The server side approach will also come with the fastest middleware render engine for Express you can get today. Packed with a smart routing feature for event delegation and full support for web components by using the Shadow DOM, Mikado gives you everything you'll need to build realtime applications on a cutting edge performance level.
- Get Latest
- Feature Comparison: Mikado Light
- Benchmark Ranking (Rendering Performance)
- API Overview
- Mikado Options
- Getting Started (Basic Example)
- Rules and Conventions
- Advanced Example
- Template Compiler
- Template Expressions
- Routing & Event Delegation
- Recycling Modes:
- Views:
- DOM State Caching
- View State
- Custom Callbacks
- Static Templates
- Server-Side Rendering (SSR)
- Express Render Engine
- Template Features:
- Reactive Features:
- Template Pools
- Hydration
- Web Components (Shadow DOM)
- Full Template Example
- Best Practices
- Concept of Shared Components
- Custom Builds
Do not use the "src" folder of this repo. It isn't meant to be used directly, instead it needs compilation. You can easily perform a custom build, but don't use the source folder for production. You will need at least any kind of compiler which resolve the compiler flags within the code. The "dist" folder is containing every version which you probably need including unminified modules.
All debug versions are providing debug information through the console and gives you helpful advices on certain situations.
Bundles export all their features as static functions to the public class namespace "Mikado" e.g.
Mikado.register().
The abbreviations used at the end of the filenames indicates:
bundleAll features included, Mikado is available onwindow.MikadolightOnly basic features are included, Mikado is available onwindow.Mikadoes5bundle has support for EcmaScript5, Mikado is available onwindow.Mikadomodulebundle is a Javascript module, Mikado is available byimport Mikado from "./mikado.bundle.module.min.js"minbundle is minifieddebugbundle has enabled debug mode (only for development purposes, do not use for production)
When using modules you can choose from 2 variants: mikado.xxx.module.min.js has all features bundled on the public class namespace e.g. Mikado.register(), whereas the folder /dist/module/ export most of the features as functions which needs to be imported explicitly by import { register } from "./dist/module/mikado.js".
Also, for each variant there exist:
- A debug version for the development
- A pre-compiled minified version for production
Load the bundle by a script tag:
<script src="dist/mikado.bundle.min.js"></script>
<script>
// ... access Mikado
</script>Install Mikado via NPM:
npm install mikado
The dist folder are located in node_modules/mikado/dist/.
Use the bundled version exported as a module:
<script type="module">
import Mikado from "./dist/mikado.bundle.module.min.js";
// bundled access by e.g. Mikado.register()
</script>Also, pre-compiled non-bundled production-ready modules are located in dist/module-min/.
<script type="module">
import Mikado, { register } from "./dist/module-min/mikado.js";
// bundled access by Mikado.register isn't available
// requires direct access by e.g. register()
</script>You can also load modules via CDN:
<script type="module">
import Mikado from "https://unpkg.com/mikado@0.8.404/dist/module/mikado.js";
</script>Loading modules via CDN commonly expects to build/bundle your app properly before distribution. Do not load them via CDN in production.
| Feature | mikado.bundle.js | mikado.light.js |
| Template Render Engine | ✓ | ✓ |
| DOM State Caching | ✓ | ✓ |
| Shared Pools / Live Pools | ✓ | ✓ |
| Keyed Recycle | ✓ | ✓ |
| Non-keyed Recycle | ✓ | ✓ |
| Reconcile (Diffing) | ✓ | ✓ |
| Hydration | ✓ | ✓ |
| Template Expressions | ✓ | ✓ |
| Conditional Template Structures | ✓ | ✓ |
| Includes/Partials/Loops | ✓ | ✓ |
| Shadow DOM | ✓ | ✓ |
| Web Components | ✓ | - |
| Runtime Compiler | ✓ | - |
| Event Delegation + Routes | ✓ | - |
| Reactive (Proxy, Observer) | ✓ | - |
| Asynchronous Render | ✓ | - |
| View Manipulation Helpers | ✓ | - |
| DOM Cache Helpers | ✓ | - |
| File Size (gzip) | 9.3 kb | 3.7 kb |
Run the benchmark (non-keyed recycle):
https://raw.githack.com/nextapps-de/mikado/bench/
Run the benchmark (keyed recycle):
https://raw.githack.com/nextapps-de/mikado/bench/#keyed
Run the benchmark (internal/data-driven):
https://raw.githack.com/nextapps-de/mikado/bench/#internal
The values represent operations per second, each benchmark task has to process a data array of 100 items. Higher values are better, except for memory (the sum of allocated memory during the whole test).
| Library | RAM | Create | Replace | Update | Order | Repaint | Add | Remove | Toggle | Clear | Score | Index |
| mikado | 55 | 3589 | 2780 | 199816 | 134262 | 536052 | 93062 | 93058 | 92151 | 51219 | 3202 | 92 |
| solid | 44 | 2438 | 2230 | 20362 | 13101 | 34436 | 4595 | 24891 | 7858 | 70825 | 312 | 37 |
| inferno | 46 | 2551 | 2151 | 14722 | 13900 | 16796 | 4780 | 20727 | 7724 | 54176 | 238 | 34 |
| mithril | 45 | 1672 | 1505 | 15406 | 13868 | 16638 | 3599 | 21109 | 5653 | 41525 | 223 | 28 |
| stage0 | 56 | 2030 | 2446 | 11213 | 9749 | 11033 | 4427 | 18083 | 7209 | 90434 | 199 | 35 |
| redom | 81 | 1517 | 1421 | 10242 | 9614 | 10870 | 2857 | 16062 | 4875 | 28075 | 160 | 22 |
| domc | 124 | 3600 | 3424 | 3437 | 3472 | 3512 | 3592 | 6670 | 4540 | 100302 | 120 | 38 |
| innerhtml | 67 | 2791 | 2676 | 2471 | 2823 | 2799 | 2943 | 5752 | 3901 | 103405 | 105 | 35 |
| surplus | 92 | 2969 | 2577 | 2281 | 2386 | 2379 | 2285 | 4197 | 3023 | 86916 | 91 | 32 |
| doohtml | 71 | 2397 | 2308 | 2208 | 2208 | 2229 | 2275 | 4285 | 2945 | 63162 | 82 | 29 |
| sinuous | 151 | 2038 | 2112 | 2454 | 2459 | 2461 | 2506 | 4820 | 3276 | 59556 | 81 | 25 |
| jquery | 103 | 2195 | 1919 | 1893 | 2092 | 2093 | 2084 | 3903 | 2594 | 19220 | 66 | 21 |
| lit-html | 199 | 1410 | 1329 | 1349 | 1351 | 1333 | 1393 | 2415 | 1764 | 20837 | 46 | 15 |
| ractive | 1870 | 739 | 672 | 690 | 686 | 691 | 725 | 1247 | 917 | 7394 | 22 | 7 |
| knockout | 1081 | 399 | 289 | 291 | 291 | 280 | 355 | 524 | 429 | 3424 | 12 | 4 |
The index is a statistic rank having a maximum possible value of 100, this requires a library to be the best in each test category (regardless how much better). The score value is based on median factorization, here a score of 100 represents the statistical midfield.
Most of these methods are optional, you can just use view.render(data) to apply all changes automatically.
Constructor:
- new Mikado(template, <options>) : view
- new Mikado.Array(array) : observable
Instance properties:
Static properties (not included in mikado.light.js):
- Mikado.eventCache : boolean
- Mikado.eventBubble : boolean
Static methods:
- Mikado.once(root, template, <data>, <state>, <callback>)
- Mikado.register(template, <options>)
- Mikado.unregister(template)
Static methods (not included in mikado.light.js):
- Mikado.route(name, function, <options>)
- Mikado.listen(event, <options>)
- Mikado.unlisten(event)
- Mikado.dispatch(route, <target>, <event>)
- Mikado.escape(text) : string
- Mikado.sanitize(text) : string
Instance methods:
- view.mount(root, <hydrate>)
- view.render(<data>, <state>, <callback>)
- view.create(data, <state>, <index>) : node
- view.add(data, <state>, <position>)
- view.append(data, <state>, <position>)
- view.update(node | index, data, <state>)
- view.replace(node | index, data, <state>)
- view.remove(node, <count>)
- view.clear()
- view.node(index) : node
- view.index(node) : number
- view.flush()
- view.destroy()
Instance methods (not included in mikado.light.js):
- view.route(name, function, <options>)
- view.listen(event, <options>)
- view.unlisten(event)
- view.dispatch(name, <target>, <event>)
View manipulation helpers (optional, not included in mikado.light.js):
- view.move(node | index, position)
- view.shift(node | index, offset)
- view.up(node | index)
- view.down(node | index)
- view.first(node | index)
- view.last(node | index)
- view.before(node | index, node | index)
- view.after(node | index, node | index)
- view.swap(node | index, node | index)
Static DOM Cache helpers (optional, not included in mikado.light.js):
-
Mikado.setText(node, text)
-
Mikado.getText(node) : string
-
Mikado.setHtml(node, html)
-
Mikado.getHtml(node) : string
-
Mikado.setClass(node, [classnames])
-
Mikado.getClass(node) : [string]
-
Mikado.addClass(node, classname)
-
Mikado.addClass(node, [classnames])
-
Mikado.hasClass(node, classname) : boolean
-
Mikado.removeClass(node, classname)
-
Mikado.getStyle(node, property) : string
-
Mikado.setCss(node, css)
-
Mikado.getCss(node) : string
-
Mikado.getAttribute(node, attribute) : string | null
-
Mikado.hasAttribute(node, attribute) : boolean
Observable constructor (optional, not included in mikado.light.js):
- new Mikado.Array(<array>) : observable
Observable array-like methods (optional, not included in mikado.light.js):
- observable.length : number
- observable.mount(view)
- observable.concat([object])
- observable.push(object)
- observable.splice : [Object]
- observable.pop : Object
- observable.shift : Object
- observable.unshift(object)
- observable.slice(<index>, <count>)
- observable.set([object])
- observable.sort(fn)
- observable.reverse()
- observable.swap(idx_a, idx_b)
- observable.map(fn)
- observable.filter(fn)
- observable.indexOf(object) : number
- observable.lastIndexOf(object) : number
- observable.includes(object) : boolean
- observable.forEach(fn)
- observable.transaction(fn)
Each Mikado instance, also named includes/partials can have their own options. Except inline partials always inherits the same options from its parent. For this reason you should prefer named includes over inlining in certain situations.
| Option | Description | Default |
| root mount |
The destination root element on where the template should be rendered. | null |
| template | You will need to assign a template to the Mikado instance (or the name of the template when already registered/loaded). | |
| async | Perform the .render(data) task asynchronously and return a Promise. |
false |
| cache | Enable/disable DOM state caching which can greatly increase performance by a factor up to 25. When enabled make sure to use the DOM Cache Helpers when manipulating the DOM directly on properties which are also covered by template expressions. | false |
| observe | When using Mikado.Array() for reactive approach you will need to pass this array instance to this property. |
null |
| recycle | When enabled all dom elements which are already rendered will be re-used (recycled) for the next render task. This performs better, but it may produce issues when manual dom manipulations was made which are not fully covered by the template. Alternatively use the keyed strategy, which limits recycling of components by matching the same data key (e.g. ID). |
false |
| state | Pass an extern object which should be referenced as the state used within template expressions. |
{ } |
| pool | Pooling can greatly enhance both the keyed and non-keyed recycle strategy. | false |
| hydrate | Progressively enables hydration of already existing DOM structures when mounted. Make sure the existing DOM structure is based on the same template. When something differs from the given template schema, the hydration will stop and silently falls back into the default build strategy. | false |
The Mikado Compiler requires Node.js to be installed. This is probably the simplest step in this guide.
Install Mikado from NPM (this will also install the compiler):
npm install mikado
Assume there is an array of data items to render (or just one item as an object):
const data = [{
username: "User A",
tweets: ["foo", "bar", "foobar"]
},{
username: "User B",
tweets: ["foo", "bar", "foobar"]
},{
username: "User C",
tweets: ["foo", "bar", "foobar"]
}];Accordingly, a template tpl/partial/user.html might look like:
<table>
<tr>
<td>User:</td>
<td>{{ data.username }}</td>
</tr>
<tr>
<td>Tweets:</td>
<td>{{ data.tweets.length }}</td>
</tr>
</table>In your console type this command line:
npx mikado-compile ./tpl/<script type="module">
import Mikado from "mikado.bundle.module.min.js";
import template from "tpl/partial/user.js";
const view = new Mikado(template, {/* options */});
</script><script src="mikado.bundle.min.js"></script>
<script src="tpl/partial/user.es5.js"></script>
<script>
var view = new Mikado("user/list", {/* options */});
</script>The name of a template inherits from its corresponding filename starting by the folder you've passed through the
--srcflag when calling the compiler.
After creation, you need mount the Mikado view instance to an HTML element as a destination for your render tasks:
view.mount(HTMLelement);
view.render(data);You can also chain methods:
Mikado(template).mount(HTMLelement).render(data);There is just a single convention you always need to keep in mind:
Every template has to provide one single root element as the outer boundary.
Instead of doing this in a template:
<header>
<nav></nav>
</header>
<section>
<p></p>
</section>
<footer>
<nav></nav>
</footer>Wrapping everything into a single outer root element by doing this:
<main>
<header>
<nav></nav>
</header>
<section>
<p></p>
</section>
<footer>
<nav></nav>
</footer>
</main>You can also use a <div> or any other element as a template root (also custom elements). The root element can also hold two special attributes key and cache. We will come later to it.
A bit more complex template:
<section id="{{ data.id }}" class="{{ this.state.theme }}" data-index="{{ index }}">
{{@ var is_today = data.date === state.today }}
<div class="{{ data.class }} {{ is_today ? 'on' : 'off' }}">
<div class="title" style="font-size: 2em">{{ data.title.toUpperCase() }}</div>
<div class="content {{ index % 2 ? 'odd' : 'even' }}">{{# data.content }}</div>
<div class="footer">{{ state.parseFooter(data) }}</div>
</div>
</section>You can use any Javascript within the {{ ... }} curly bracket notation. The scope is limited by the template, so variables from one template can't be accessed within another template (use state for this purpose).
To pass HTML markup as a string, the curly brackets needs to be followed by # e.g.
{{# ... }}. For better performance, relevant tasks avoid passing HTML contents as a string.
To use Javascript outside an element's context you need to prevent concatenation of the returned value. For this purpose, the curly brackets need to be followed by @ e.g.
{{@ ... }}.
Within a template there are several reserved keywords you can use as an identifier:
| Identifier | Description |
| data | A full reference to the passed data item. Within loops the keyword data points to each of the looped data items. |
| state | An optional payload used to manually pass in custom specific values or helper functions. The state will be delegated through all nested templates. |
| index | Represents the index of the currently rendered data item (starting by 0 for the first item). |
| this | Provides you access to the Mikado view instance (e.g. this.state). |
| window | Gives access to the global namespace. |
| _p _v _x _o _f _inc |
private identifiers, used by internal processing |
You cannot change the names of those preserved keywords, also make sure you didn't override them.
It is recommended to pass custom functions via the state object (see example above state.parseFooter = function(str){ return str; }). Alternatively you can also nest more complex computations inline as an IIFE and return the result.
<div class="date">{{
(function(){
var date = new Date();
// perform some code ...
return date.toLocaleString();
}())
}}</div>To finish the example from above you need one single data object or an array of data items:
var data = [{
"id": "230BA161-675A-2288-3B15-C343DB3A1DFC",
"date": "2019-01-11",
"class": "yellow, green",
"title": "Sed congue, egestas lacinia.",
"content": "<p>Vivamus non lorem <b>vitae</b> odio sagittis amet ante.</p>",
"footer": "Pellentesque tincidunt tempus vehicula."
}];Provide the optional state payload which includes specific values and helper methods used within template expressions:
const state = {
today: "2019-01-11",
theme: "custom",
parseFooter: function(data) {
return data.footer;
}
};Mount the view to a target element as a destination for all the render tasks:
view.mount(HTMLelement);Render a mounted template:
view.render(data, state);Render asynchronously automatically by just providing a callback function:
view.render(data, state, function() {
console.log("finished.");
});To render asynchronously by using promises you need to set the callback value to true:
// callback "true" will use Promises
view.render(data, state, true).then(function() {
console.log("finished.");
});
// same, but uses async/await:
await view.render(data, state, true);
console.log("finished.");When async should be the default strategy for all render tasks then you can also set the async option flag:
const view = new Mikado(template, { async: true });
await view.render(data, state);
console.log("finished.");Define an HTML-like template and use double curly brackets to markup dynamic expressions which should be calculated and replaced during runtime:
<table>
<tr>
<td>User:</td>
<td>{{ data.username }}</td>
</tr>
<tr>
<td>Tweets:</td>
<td>{{ data.tweets.length }}</td>
</tr>
</table>Save this template e.g. to tpl/partial/user.html
The preserved keyword data is a reference to the passed data item. You can access the whole nested object.
Mikado comes with a builtin template compiler you can simply access by typing npx mikado-compile into your console. The compiler uses a very simple command-line interface (CLI) running on Node.js to perform compilation tasks. The template compiles into a native javascript file which needs to be passed during creation of a Mikado instance. The same markup is also used for the server-side rendering part, so you can share the same template sources for client and server rendering.
Show help to list all available commands:
npx mikado-compile --helpCompile the template through the command line by:
npx mikado-compile tpl/partial/user.htmlBasic Notation:
npx mikado-compile source <destination>
When no destination folder was set, the compiled files will be saved to the source folder. After compilation, you will have 3 different files:
- list.js the template compiled as a ES6 module (which needs to be imported)
- list.es5.js the template compiled as ES5 compatible Javascript (which automatically register when loaded by script tag)
- list.html the source template you have implemented (do not delete it)
Extended Notation:
npx mikado-compile --src { source } --dest { destination } --extension html --type module --compact
--type module,-t moduleexport as javascript modules (recommended)--type es5,-t es5export as ES5-compatible package--extension html,--ext html,-e htmlthe file extension which should be compiled--inline,-ior--compact,-cswitch the build strategy to optimize either the performance (inline) or size (compact)--force,-fforce overwriting existing files--pretty,-pdo not minify the compiled result--watch,-wstart the watcher for automatically compile when files has changed (just for development purposes)
Supported flags as attributes on the template root:
<!-- switch the build strategy to prebuilt enabled cache -->
<table cache="true"></table><!-- switch the build strategy to prebuilt disabled cache -->
<table cache="false"></table>Using the flag attributes are the most performant variants but also cost you some flexibility, because the cache strategy couldn't be changed in runtime, it needs to change in markup before compilation.
There is a new naming system which will apply by default. The name of your html files will be used as unique identifiers of your templates.
Because several folders can include same filenames, the template name inherits from the full path you pass in as --src.
Assuming the following file structure:
tpl/view/start.html
tpl/view/user.html
tpl/view/cart.html
tpl/partial/start.html
tpl/partial/user.html
tpl/partial/cart.html
The command should define the path /tpl/ as the source root because it is the most inner folder which covers all files:
npx mikado-compile ./tpl/The template names then becomes view/start, view/user, view/cart and partial/start, partial/user, partial/cart for the partials. So when including just use this name in your expression <table include="partial/user">
The wrong way is to compile the folder /view/ and /partial/ separately, because their template names will be same.
npx mikado-compile ./tpl/view/
npx mikado-compile ./tpl/partial/This might also work, but it is better not to do.
The option { cache: true/false } when creating a Mikado instance could be better declared withing templates on their root element, let the compiler produce more optimized code for this strategy.