Skip to content

nextapps-de/mikado

Repository files navigation

Mikado - Webs fastest templating engine

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:

Support this Project

Mikado was getting so much positive feedback and also feature requests. Help keeping Mikado active by a personal donation.

Donate using Open Collective Donate using Github Sponsors Donate using Liberapay Donate using Patreon Donate using Bountysource Donate using PayPal

Table of contents

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. On top, it also 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.

  1. Get Latest
  2. Feature Comparison: Mikado Light
  3. Benchmark Ranking (Rendering Performance)
  4. API Overview
  5. Mikado Options
  6. Getting Started (Basic Example)
  7. Rules and Conventions
  8. Advanced Example
  9. Template Compiler
  10. Template Expressions
  11. Routing & Event Delegation
  12. Recycling Modes:
  13. Views:
  14. DOM State Caching
  15. View State
  16. Custom Callbacks
  17. Static Templates
  18. Server-Side Rendering (SSR)
  19. Express Render Engine
  20. Template Features:
  21. Reactive Features:
  22. Template Pools
  23. Hydration
  24. Web Components (Shadow DOM)
  25. Full Template Example
  26. Best Practices
  27. Concept of Shared Components
  28. Custom Builds

Get Latest

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.

Build File CDN
mikado.bundle.debug.js Download https://rawcdn.githack.com/nextapps-de/mikado/0.8.3/dist/mikado.bundle.debug.js
mikado.bundle.min.js Download https://rawcdn.githack.com/nextapps-de/mikado/0.8.3/dist/mikado.bundle.min.js
mikado.bundle.module.debug.js Download https://rawcdn.githack.com/nextapps-de/mikado/0.8.3/dist/mikado.bundle.module.debug.js
mikado.bundle.module.min.js Download https://rawcdn.githack.com/nextapps-de/mikado/0.8.3/dist/mikado.bundle.module.min.js
mikado.es5.debug.js Download https://rawcdn.githack.com/nextapps-de/mikado/0.8.3/dist/mikado.es5.debug.js
mikado.es5.min.js Download https://rawcdn.githack.com/nextapps-de/mikado/0.8.3/dist/mikado.es5.min.js
mikado.light.debug.js Download https://rawcdn.githack.com/nextapps-de/mikado/0.8.3/dist/mikado.light.debug.js
mikado.light.min.js Download https://rawcdn.githack.com/nextapps-de/mikado/0.8.3/dist/mikado.light.min.js
mikado.light.module.debug.js Download https://rawcdn.githack.com/nextapps-de/mikado/0.8.3/dist/mikado.light.module.debug.js
mikado.light.module.min.js Download https://rawcdn.githack.com/nextapps-de/mikado/0.8.3/dist/mikado.light.module.min.js
Javascript Modules Download https://github.com/nextapps-de/mikado/tree/0.8.3/dist/module
Javascript Modules (Minified) Download https://github.com/nextapps-de/mikado/tree/0.8.3/dist/module-min
Javascript Modules (Debug) Download https://github.com/nextapps-de/mikado/tree/0.8.3/dist/module-debug
mikado.custom.js Read more about "Custom Build"

All debug versions are providing debug information through the console and gives you helpful advices on certain situations.

Bundles

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:

  • bundle All features included, Mikado is available on window.Mikado
  • light Only basic features are included, Mikado is available on window.Mikado
  • es5 bundle has support for EcmaScript5, Mikado is available on window.Mikado
  • module bundle is a Javascript module, Mikado is available by import Mikado from "./mikado.bundle.module.min.js"
  • min bundle is minified
  • debug bundle has enabled debug mode (only for development purposes, do not use for production)

Module

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:

  1. A debug version for the development
  2. A pre-compiled minified version for production

Browser

Load the bundle by a script tag:

<script src="dist/mikado.bundle.min.js"></script>
<script>
  // ... access Mikado
</script>

NPM

Install Mikado via NPM:

npm install mikado

The dist folder are located in node_modules/mikado/dist/.

Javascript Modules

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.304/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 Comparison "Bundle vs. Light"

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

Benchmark Ranking (Rendering Performance)

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).

Keyed Test Results

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.

API Overview

Most of these methods are optional, you can just use view.render(data) to apply all changes automatically.

Constructor:

Instance properties:

Static properties (not included in mikado.light.js):

Static methods:

Static methods (not included in mikado.light.js):

Instance methods:

Instance methods (not included in mikado.light.js):

View manipulation helpers (optional, not included in mikado.light.js):

Static DOM Cache helpers (optional, not included in mikado.light.js):

Observable constructor (optional, not included in mikado.light.js):

Observable array-like methods (optional, not included in mikado.light.js):

Mikado Options

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

Getting Started (Basic Example)

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>

Compile the template:

In your console type this command line:

npx mikado-compile ./tpl/

Load library and initialize template as ES6 modules:

<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>

Load library and initialize template as legacy ES5:

<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 --src flag 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);

Rules and Conventions

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.

Advanced Example

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.");

Compile Templates

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 --help

Compile the template through the command line by:

npx mikado-compile tpl/partial/user.html

Basic 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:

  1. list.js the template compiled as a ES6 module (which needs to be imported)
  2. list.es5.js the template compiled as ES5 compatible Javascript (which automatically register when loaded by script tag)
  3. 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

Compiler Flags:

  • --type module, -t module export as javascript modules (recommended)
  • --type es5, -t es5 export as ES5-compatible package
  • --extension html, --ext html, -e html the file extension which should be compiled
  • --inline, -i or --compact, -c switch the build strategy to optimize either the performance (inline) or size (compact)
  • --force, -f force overwriting existing files
  • --pretty, -p do not minify the compiled result
  • --watch, -w start 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.

Auto Naming

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.

Prebuilt Cache Strategy

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.

<table cache="true">
    <!-- ... -->
</table>

Also use this approach when set cache="false":

<table cache="false">
    <!-- ... -->
</table>

Watcher (Auto-Compile)

A perfect fit for your local development environment is spawning a watcher to automatically compile files when they get changed. Just use the same command line you would also use for a full compilation and append the flag --watch or -w to it:

npx mikado-compile ./tpl/ --watch

Don't close the console, otherwise the watcher will stop. You can stop the watcher explicitly by pressing CTRL + C.

Template Expressions

The template notation expects double curly brackets {{ ... }} for any kind of dynamic expressions.

Except when using {{@ ... }} for inline code notation, the returned value of every dynamic expression will be replaced to its position.

Value Insertion {{ ... }}

<div>{{ data.value }}</div>
view.render({ value: "test" });

You can also combine multiple expressions with non-expression contents:

<div>The title "{{ data.title }}" has the value: {{ data.value }}</div>
view.render({ title: "title", value: "test" });

You can also mix text nodes with elements on the same root element:

<div>Title: <b>{{ data.title }}</b><br>Value: {{ data.value }}</div>
view.render({ title: "title", value: "test" });

Also, you can use expressions within every attribute:

<div data-id="{{ data.title }}" class="{{ data.class }}">{{ data.value }}</div>
view.render({ id: 1, value: "test", class: "test" });

Every Javascript syntax is allowed withing expression:

<div style="color: {{ data.active ? 'green' : 'black' }}; {{ data.value ? '' : 'display: none;' }}"></div>
view.render({ active: true, value: "not empty" });

Since expressions just need to return a value you can also use IIFE:

<div>{{ 
    (function(){ 
        var date = new Date();
        // perform some code ...
        return date.toLocaleString();
    }())
}}</div>
view.render();

JS Inline Code {{@ ... }}

The inline code expression is the only one which doesn't return a value to be rendered in place, it just executes.