0.0.8 • Published 5 years ago

mikado-server v0.0.8

Weekly downloads
2
License
Apache-2.0
Repository
github
Last release
5 years ago

Rendering has by far the most impact on application performance. Mikado takes templating performance to a whole new level and provides you keyed, non-keyed and also reactive paradigm switchable out of the box. Let's start building the next generation of high-performance applications.

Getting Started Options API Concept Benchmark Custom Builds Template Compiler Template Server Changelog

Services:

Mikado Runtime (Render Templates)npm install mikado

Mikado Compiler (Compile Templates)npm install mikado-compile

Mikado Server (Serve Templates)npm install mikado-server

Benchmark:

Demo:

  1. Basic Example + Runtime Compiler (HTML5 Template)
  2. Basic Example + Runtime Compiler (String Template)
  3. Basic Example + Events (ES5)
  4. Basic Example + Events (ES6 Modules)
  5. Basic Example + Events (Development Sources)
  6. TodoMVC App: Source Code/Run Demo
  7. js-framework-benchmark: keyed/non-keyed/keyed (proxy)

Coming Soon

new webpack loader to bundle templates add file endings for templates are customizable (e.g use .shtml)

How to learn Mikado?

Thanks to the reversed engineering, Mikado provides you one of the most simple to learn idiomatic styles which are based on living standards. You do not have to learn a new language, you just need some basic skills you already have. It will take 3 minutes to become productive. Don't let that confuse you with the size of this documentation, because it will show you a lot of in-depth details, which are just missing in most of the other framework documentations. You will do not need these details to start with. But when you would like to know more you get a chance to go deeper.

Also, all compiled dist files will work out of the box, no TypeScript, no Webpack, no module loader, no external tools are required. You can begin seamlessly without the pain you might know from other frameworks.

Guide for new developers (the most simple example, just takes 3 minutes):

  • Load this bundle through a script tag resource
  • Provide a basic template as native HTML5 template
  • Compile the template, create Mikado instance by passing the root node and the compiled template
  • Just use .render(data) over and over for all jobs: add / remove / clear / update / reconcile / ...
  • Final Source Code

Table of contents

  1. Get Latest
  2. Feature Comparison: Mikado Light
  3. Benchmark Ranking (Rendering Performance)
  4. API Overview
  5. Options
  6. Template Compiler
    • XSS Security
    • Using Dedicated Compiler
    • Using HTML5 Templates
    • Using Template String
  7. Rules and Conventions
  8. Basic Example
  9. Advanced Example
  10. Event Bindings
    • Explicit Register/Unregister
  11. Keyed / Non-Keyed Modes
    • Non-Keyed
    • Explicit Keyed (Non-Shared)
    • Cross-Shared Keyed
    • Exclusive-Shared Keyed
    • Explicit Keyed (Shared)
  12. Non-Reusing
    • Render vs. Refresh vs. Reconcile
  13. Usage:
    • Create, Initialize, Destroy Views
    • Render Templates
    • Modify Views
    • Useful Helpers
    • Manipulate Views
    • Caching Helpers
  14. DOM State Caching
  15. Stores:
    • Internal Store
    • Loose Store
    • Extern/Custom Store
    • Reactive Store
    • Export / Import Stores
  16. State
  17. Callbacks
  18. Transport / Load Templates
  19. Static Templates
    • Once (One-time rendering)
  20. Compiler Service / Live Templates
    • Local Development
  21. Template Features:
    • Includes
    • Loop Partials
    • Inline Loops
    • Conditional Branches
  22. Reactive Proxy (Observer)
    • Limitations
    • Stealth Mode
    • Observable Array (Virtual NodeList)
  23. Best Practices
  24. Memory Optimizations
  25. About Reconcile (Diffing)
  26. Concept of Shared Pools
  27. Custom Builds

Get Latest

Bundle

Choose one of these bundles:

Recommended: To get a specific version just replace /master/ with one of the version numbers from the release e.g. /0.6.6/, or also a commit hash.

The es5-strict version is including all features. The debug version also but additionally provide debugging information through the console.

Example:

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

Node.js

Install Mikado via NPM:

npm install mikado

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

ES6 Modules

Production

The ES6 minified production modules are located in dist/module/.

<script>
    import Mikado from "./dist/module/mikado.js";
</script>

You can also load modules via CDN, e.g.:

<script>
    import Mikado from "https://unpkg.com/mikado@0.7.21/dist/module/mikado.js";
</script>

Development

Use the modules from the "src" folder for development/debugging. When using the "src" modules you have to load the "src/config.js" via a normal script tag before you load the modules.

<script src="src/config.js"></script>
<script>
    import Mikado from "./src/mikado.js";
</script>

Feature Comparison

Benchmark Ranking (Rendering Performance)

Run the benchmark (recycle): https://raw.githack.com/nextapps-de/mikado/master/bench/

Run the benchmark (keyed): https://raw.githack.com/nextapps-de/mikado/master/bench/#keyed

Run the benchmark (internal/data-driven): https://raw.githack.com/nextapps-de/mikado/master/bench/#internal

Sources and readme: https://github.com/nextapps-de/mikado/tree/master/bench

Values represents operations per second, each benchmark task has to process a data array of 100 items. Higher values are better, except file size (minified/gzip) and memory (sum of allocation during the whole test).

Keyed

The file size and memory gets less relevance. The maximum possible index is 1000, that requires a library to be best in each category. The score value is relational where a score of 1000 represents the statistical midfield.

Read more about this test and also show ranking table for "non-keyed" and "data-driven" here.

Worst Case Scenario

Mikado has no specific worst case scenario. The replace test could be named like this, but that is always the worst case scenario for every lib. This test simply couldn't use most of the runtime optimizations.

API Overview

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

Constructor:

  • new Mikado(\<root>, template, \<options>) : view

Global methods:

  • Mikado.new(\<root>, template, \<options>) : view
  • Mikado.once(root, template, \<data>, \<payload>, \<callback>)
  • Mikado.register(template)
  • Mikado.unregister(template)

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

  • Mikado.compile(\<template | string>)
  • Mikado.load(url, \<callback>)
  • Mikado.unload(template)
  • mikado.route(name, handler)
  • mikado.listen(event)
  • mikado.unlisten(event)
  • mikado.dispatch(name, \<target>, \<event>, \<self>)

Instance methods:

  • view.init(\<template>, \<options>)
  • view.mount(root)
  • view.render(\<data>, \<payload>, \<callback>)
  • view.reconcile(data)
  • view.create(data, \<payload>)
  • view.add(data, \<payload>, <index>)
  • view.append(data, \<payload>, <index>)
  • view.update(node | index, data, \<payload>)
  • view.replace(node | index, data, \<payload>)
  • view.remove(node, <count>)
  • view.clear()
  • view.data(index | node)
  • view.node(index)
  • view.index(node)
  • view.destroy(\<unload?>)
  • view.unload()

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

  • view.refresh(\<node | index>, \<payload>)
  • view.sync(\<uncache?>)
  • view.purge()
  • view.find(data)
  • view.search(data)
  • view.where(payload)
  • view.import()
  • view.export()
  • view.load(url, \<callback>)
  • view.route(name, handler)
  • view.listen(event)
  • view.unlisten(event)
  • view.dispatch(name, \<target>, \<event>, \<self>)

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

  • view.move(node | index, index)
  • view.shift(node | index, index)
  • 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)

Instance properties:

  • view.dom
  • view.length
  • view.store
  • view.state
  • view.config
  • view.template

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

  • Mikado.setText(node, text)
  • Mikado.getText(node)
  • Mikado.setHTML(node, html)
  • Mikado.getHTML(node)
  • Mikado.setClass(node, class)
  • Mikado.getClass(node)
  • Mikado.hasClass(node, class)
  • Mikado.removeClass(node, class)
  • Mikado.toggleClass(node, class)
  • Mikado.setStyle(node, property, value)
  • Mikado.getStyle(node, property
  • Mikado.setCSS(node, css)
  • Mikado.getCSS(node)
  • Mikado.setAttribute(node, attr, value)
  • Mikado.getAttribute(node, attr)
  • Mikado.hasAttribute(node, attr)
  • Mikado.removeAttribute(node, attr)

Options

Each Mikado instance can have its own options.

Compile Templates

XSS Security

Whenever you want to load and/or compile templates during runtime on client side keep this rule in mind:

Never load templates from an external resource you did not own (or thrust)!

To prevent XSS there are some options: 1. use templates provided by yourself (recommended) 2. load external templates from sources you are thrust 3. Coming Soon: bundle all external templates during build and pass an external sanitizer (e.g. a node module) to the compiler options 4. Coming Soon: use the mikado-server as a gateway which provides a built-in sanitizer for external templates 5. Coming Soon: use a sanitizer in the client side and pass this along the option field sanitize

Sanitize external resources comes with some big drawbacks accordingly to this specification:

  • Templates supports just a small subset of tags when using html as a content type
  • Templates could not include any javascript expressions (just supports basic template expression accordingly to mustache or handlebars)

Compiler Methods

Note: Choosing a specific compiler method has no impact on the render performance.

1. Variant: Using Dedicated Compiler (Recommended)

Define a HTML-like template and use double curly brackets to mark dynamic expressions which should be calculated during runtime:

<table>
    <tr>
        <td>User:</td>
        <td>{{data.user}}</td>
    </tr>
    <tr>
        <td>Tweets:</td>
        <td>{{data.tweets.length}}</td>
    </tr>
</table>

Save this template e.g. to user/list.html

The preserved keyword data is a reference to a passed data item. You can access the whole nested object.

Mikado comes up with a template compiler which has to be run through Node.js and provides a command line interface (CLI) to start compilation tasks. The template compiles into a fully compatible JSON format and could also be used for server-side rendering.

Install Mikado Compiler via NPM:

npm install mikado-compile

Compile the template through the command line by:

npx mikado-compile user/list.html

Notation: npx mikado-compile {{input}} {{destination}}

After compilation you will have 4 different files: 1. template.js the template compiled in ES5 compatible Javascript 2. template.es6.js the template compiled as an ES6 module 3. template.json the template compiled in JSON-compatible notation (to load via http request) 4. template.html the HTML-like template (reference, do not delete it)

2. Variant: Using HTML5 Templates

Define in HTML:

<template id="user-list">
    <table>
        <tr>
            <td>User:</td>
            <td>{{data.user}}</td>
        </tr>
        <tr>
            <td>Tweets:</td>
            <td>{{data.tweets.length}}</td>
        </tr>
    </table>
</template>

Use runtime compiler:

var tpl = Mikado.compile("user-list");
var tpl = Mikado.compile(document.getElementById("user-list"));

Create mikado view:

var view = Mikado.new(tpl);

3. Variant: Using Template String

Define HTML as string:

const template = ( 
    `<table>
        <tr>
            <td>User:</td>
            <td>{{data.user}}</td>
        </tr>
        <tr>
            <td>Tweets:</td>
            <td>{{data.tweets.length}}</td>
        </tr>
    </table>`
);

Use runtime compiler:

var tpl = Mikado.compile(template);

Create mikado view:

var view = Mikado.new(tpl);

Basic Example

Assume there is an array of data items to render (or just one item as an object):

var data = [{
    user: "User A",
    tweets: ["foo", "bar", "foobar"]
},{
    user: "User B",
    tweets: ["foo", "bar", "foobar"]
},{
    user: "User C",
    tweets: ["foo", "bar", "foobar"]
}]

Load library and initialize template (ES5):

<script src="mikado.min.js"></script>
<script src="user/list.js"></script>
<script>
    var view = Mikado.new("template");
</script>

The name of a template inherits from its corresponding filename.

Load library and initialize template (ES6):

<script type="module">
    import Mikado from "./mikado.js";
    import template from "./user/list.es6.js";
    var view = Mikado.new(template);
</script>

After creation you need mount to a DOM element initially as a destination root and render the template with populated data:

view.mount(document.body);
view.render(data);

You can also chain methods:

var view = Mikado.new(template).mount(document.body).render(data);

Rules and Conventions

Every template has to provide one single root as the outer bound. This is a convention based on the concept of Mikado.

Instead of doing this in a template:

<header>
    <nav></nav>
</header>
<section>
    <p></p>
</section>
<footer>
    <nav></nav>
</footer>

Provide one single root 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).

Mixing text nodes and child nodes within same root is actually not possible:

<main>
    {{ data.title }}
    <section>{{ data.content }}</section>
    {{ data.footer }}
</main>

This may provided in the future, in the meanwhile just wrap text nodes into its own child:

<main>
    <title>{{ data.title }}</title>
    <section>{{ data.content }}</section>
    <footer>{{ data.footer }}</footer>
</main>

This example has not this issue, because text nodes and child nodes are not mixed:

<main>
    <section>{{ data.title }} foobar {{ data.footer }}</section>
</main>

Advanced Example

A bit more complex template:

<section id="{{data.id}}" class="{{this.state.theme}}" data-index="{{index}}">
    {{@var is_today = data.date === view.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">{{view.parseFooter(data)}}</div>
    </div>
</section>

You can use any Javascript within the {{ ... }} curly bracket notation.

To pass html markup as a string, the curly brackets needs to be followed by # e.g. {{# ... }}. For performance relevant tasks avoid passing html contents as string.

To use Javascript outside an elements context you need to prevent concatenation of the returned value. For this purpose the curly brackets needs to be followed by @ e.g. {{@ ... }}.

Within a template you have access to the following indentifiers:

You cannot change the naming of those preserved keywords.

It is recommended to pass custom functions via the view object (see example above). You can also nest more complex computations inline as an IIFE and return the result.

<div class="date">{{ 
    (function(){ 
        var date = new Date();
        // ...
        return date.toLocaleString(); 
    }()) 
}}</div>

Alternatively of accessing data, view, index and this.state you can also access variables from the global namespace.

To finish the example above provide one single object or an array of data item:

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 view payload (non-data-item specific values and helper methods used by the template):

var payload = {
    page: 1,
    today: "2019-01-11",
    parseFooter: function(data){ return data.footer; }
}

Provide state data (application specific data and helper methods used by the template):

view.state.theme = "custom";

Create a new view instance or initialize a new template factory to an existing instance:

view.init(template);

Mount to a new target or just render the already mounted template:

view.render(data, payload);

Render asynchronously by providing a callback function:

view.render(data, payload, function(){
    console.log("finished.");
});

To render asynchronously by using promises you need to create the view instance with the async option flag:

view = Mikado.new(template, { async: true });

view.render(data, payload).then(function(){
    console.log("finished.");
});

Event Bindings

Lets take this example:

<table data-id="{{data.id}}" root>
    <tr>
        <td>User:</td>
        <td click="show-user">{{data.user}}</td>
        <td><a click="delete-user:root">Delete</a></td>
    </tr>
</table>

There are 2 click listeners. The attribute value represents the name of the route. The second listener has a route separated by ":", this will delegate the event from the route "delete-user" to the closest element which contains the attribute "root".

Define routes:

view.route("show-user", function(node, event){

    alert(node.textContent);

}).route("delete-user", function(node, event, self){

    alert(node.dataset.id); // delegated to "root"
    console.log("The element who fires the event: ", self);
})

Routes are stored globally, so you can share routes through all Mikado instances.

List of all supported events:

  • tap (synthetic touch-enabled "click" listener, see below)
  • change, input, select, toggle
  • click, dblclick
  • keydown, keyup, keypress
  • mousedown, mouseenter, mouseleave, mousemove, mouseout, mouseover, mouseup, mousewheel
  • touchstart, touchmove, touchend
  • submit, reset
  • focus, blur
  • load, error
  • resize
  • scroll

Synthetic events:

Explicit Register/Unregister

You can also use the event delegation along with "routes" outside a template. Just apply the event attribute like you would do in a template.

<body>
    <div click="handler">Click Me</div>
</body>
Mikado.route("handler", function(target, event){
    console.log("Clicked");
});

Then you have to explicit register these event once:

Mikado.listen("click");

Because the register of events basically happens when creating the template factory under the hood. When no template was created which includes the same type of event, then there exist is no global listener. For that reason you have to explicitly register the listener once.

Same way you could also unregister events:

Mikado.unlisten("click");

Dispatch Event Handler

Manually dispatch an event:

view.dispatch("handler");

Manually dispatch an event and pass parameters:

view.dispatch("handler", target, event, self);

Keyed/Non-Keyed Modes

Each template instance can run in its own mode independently.

Compare benchmark of all supported modes here: https://raw.githack.com/nextapps-de/mikado/master/bench/#modes

1. Non-Keyed

A non-keyed strategy will reuse all existing components and is basically faster than keyed but also has some side-effects when not used properly.

Just provide a template as normal:

<div>
    <div>User:</div>
    <div>{{data.name}}</div>
</div>

along with these options:

var view = Mikado.new(template, { pool: true });

This will switch Mikado into a "non-keyed" mode where already rendered components will be re-used. Using the pool is optional.

2. Explicit Keyed (Non-Pool)

A keyed strategy limits re-usability of components based on items with same ID. It just requires an unique identifier on each rendered item (e.g. the ID).

Add the attribute key to the root element of a template (or the root of an inline partial) and assign the namespace to the unique identifier:

<div key="data.id">
    <div>User:</div>
    <div>{{data.name}}</div>
</div>

To make them explicitly keyed also disable reusing:

var view = Mikado.new(template, { reuse: false, pool: false });

This will switch Mikado into a "explicit keyed" mode (non-shared).

3. Explicit Keyed (Shared Pool)

This is a special mode which uses the shared keyed index exclusively (without pooling). This will give you the absolute maximum performance, but it has a limit you should keep in mind when using this mode. The exclusive keyed mode is unbounded. Just use this mode on templates where the amount of incoming data is supposed to be limited (e.g. in a common scenario: pagination through a set of x items, like a todo list). Otherwise you will get no performance gain and also the memory allocation increases constantly (unbounded).

<div key="data.id">
    <div>User:</div>
    <div>{{data.name}}</div>
</div>

along with these options:

var view = Mikado.new(template, { reuse: false, pool: "key" });

This will switch Mikado into a "explicit keyed" mode (shared).

4. Cross-Shared (Hybrid)

The cross shared mode is a hybrid and takes the performance benefits of both shared pools and provides you an enhanced pooling of reusable components. This mode provide high performance as well as low memory allocation during runtime.

Add the attribute key to the root element of a template:

<div key="data.id">
    <div>User:</div>
    <div>{{data.name}}</div>
</div>

along with these options:

var view = Mikado.new(template, { pool: true } );

This will switch Mikado into a "cross-shared-keyed" mode.

5. Exclusive-Shared (Hybrid)

You can also use the same strategy from 3. for hybrid mode. But it has the same limits as 3., read above.

<div key="data.id">
    <div>User:</div>
    <div>{{data.name}}</div>
</div>

along with these options:

var view = Mikado.new(template, { pool: "key" });

This will switch Mikado into a "exclusive-shared-keyed" mode.

Non-/Reusing

Mikado is one of the very few libraries which provides you a 100% non-reusing paradigm out of the box.

Generally keyed libraries will fail in restoring the original state of a component when a data item of the new fetched list has the same key. As long you follow some restrictions this may not an issue. But whenever you get in situations where you have to force restoring, every keyed lib will fail and you may have to use quick fixes like randomize the ID of the component. Also keyed libs cannot fully integrated into every stack, especially when additional UI libs where used.

Mikado is able to restoring 100% of the original state. This helps in situations where:

  • external libraries changes components nodes
  • event listeners was bind directly to components nodes
  • external data/values was referenced to components nodes
  • components nodes where manually manipulated
  • the process workflow requires redrawing of the original state on new data (required by some MVC)
  • you need integration in a stack without side effects

Notice: An original state does not include an event listener which was directly bound to an element. The original state is the state before you apply anything manually (or by external).

Render vs. Refresh vs. Reconcile

Take advantage of Mikados 3 different render functions. Especially when reusing was disabled, this will gives you the full control.

Basically the render function is already trying to apply the minimum required changes to the DOM. But prediction is always limited, also nothing could never make a prediction better than the developer itself who is implementing the task. Most of the optional methods provided by Mikado are simply just there, to get the most out when it matters. Use them to manual control the flow of batch processes and optimize performance-heavy tasks.

Whenever you call .render() when also reusing was explicitly disabled all components will be recreated (restoring original state):

view.render(items);

Recreation has a significant cost and is often not strongly required by every single render loop. When using a store you can made changes to the data and just commit the changes when finished:

view.store[1]["title"] = "new title";
view.refresh(1);

The refresh method will just apply data changes to the view without restoring the original state by recreation of its components.

You can also refresh all components lazily when doing multiple changes:

view.store[1].title = "new title";
view.store[2].content = "new content";
view.store[3].footer = "new footer";
view.refresh();

It is pretty much the same when using stores in loose mode:

view.data(1).title = "new title";
view.data(2).content = "new content";
view.data(3).footer = "new footer";
view.refresh();

Passing a components root node or an index to the refresh method performs faster than passing no parameter.

Hint: You cannot use refresh when new items was added/removed, this requires .render(data).

When you just want to move items to its new order without updating its contents (also no add/remove) and you are in keyed mode you can call reconciliation directly:

view.reconcile(items);

Hint: The sum of .reconcile(data) and .refresh() is basically .render(data) under the hood. When you need both: adding/removing/moving together with updating contents than call .render(data) instead of calling the corresponding partial methods one by one.

Create, Initialize, Destroy Views

Create view from a template with options:

var view = Mikado.new(template, options);

Create view from a template with options and also mount it to a target element:

var view = Mikado.new(root, template, options);

Mount a view to a target element:

view.mount(element);

Initialize an existing view with new options:

view.init(options);

Initialize an existing view also with a new template:

view.init(template, options);

Unload/delete the template which is assigned to a view:

view.unload();

Destroy a view:

view.destroy();

Render Templates

When using an internal store (not external), every render task also updates the stored data.

Render a template incrementally through a set of data items:

view.render(data);

Render a template via data and also use view-specific data/handlers:

view.render(data, payload);

Schedule a render task asynchronously to the next animation frame:

view.render(data, payload, true);

Schedule a render task by using a callback:

view.render(data, payload, function(){
    // finished
});

Schedule a render task by using promises (requires option async to be enabled during initialization):

view.render(data, payload).then(function(){
    // finished
});

Render a static template (which uses no dynamic content):

view.render();

Repaint the current state of a dynamic template (which has data, requires store to be enabled):

view.refresh();

Repaint the current state on a specific index:

view.refresh(index);

Just create an template without adding/assigning/rendering them to the root ("orphan"):

var partial = view.create(data);

Orphans are not a part of the internal render tree. The construction of orphans is really fast. You could also use the light version of Mikado an apply your own stack on top of this method.

Modify Views

Add one data item to the end:

view.add(data);

Add one data item to a specific index (did not replace):

view.add(data, 0); // add to beginning

Append multiple data items to the end:

view.append(data);

Append multiple data before an index:

view.append(data, 0); // append to beginning

Remove a specific data item/node:

view.remove(node);

Remove a specific template node by its index:

view.remove(20);

Remove a range of nodes starting from a specific node/index:

view.remove(20, 10);
view.remove(node, 20);

Remove last 20 node items (supports reverse index):

view.remove(-20, 20);

Remove previous 20 node items starting of a given node/index (including):

view.remove(node, -20);

Remove all:

view.clear();

Replace a data item/node:

view.replace(old, new);

Update an single data item/node:

view.update(node, data);

Re-Sync DOM:

view.sync();

Re-Sync DOM + Release Cache:

view.sync(true);

Purge all shared pools (factory pool and template pool):

view.purge();

Useful Helpers

Get a template root node from the DOM by index:

var node = view.node(index);

Get a data item from the store by index:

var data = view.data(index);

Get a data item from the store by node:

var data = view.data(node);

Get the index from a given node:

var index = view.index(node);

Find a node which corresponds to a data item (same reference):

var node = view.find(data);

Find the first node which corresponds to a data item which has same content (that may requires each data item to be unique, otherwise use where):

var node = view.search(data);

Find all nodes which matches a given payload (will always return an array, empty if no results was found):

var node = view.where({
    title: "foo",
    active: true,
    content: "bar"
});
var node = view.where(data);

Get the length of all data items rendered (in store):

var length = view.length;

Get the current template name which is assigned to a Mikado instance:

var name = view.template;

Manipulate Views

Manual changes on the DOM may require re-syncing. To prevent re-syncing by applying manual changes Mikado provides you several optional helper functions to manipulate the dom and also keep them in sync. Using the helper function also grants you a maximum performance.

All helpers could be used by index or by node as passed parameters.

Move a data item/node to a specific index:

view.move(node, 15);  // 15 from start
view.move(node, -15); // 15 from end

Move a data item/node to the top or bottom:

view.first(node);
view.last(node);

Move a data item/node by 1 index:

view.up(node);
view.down(node);

Move a data item/node by a specific offset (pretty much the same as shift):

view.up(node, 3);
view.down(node, 3);

Shift a data item/node relatively by a specific offset (both directions):

view.shift(node, 3);
view.shift(node, -3);

Move a data item/node before or after another data item/node:

view.before(node_a, node_b);
view.after(node_a, node_b);

Swap two data items/nodes:

view.swap(node_a, node_b);

Some notes about helpers

Those helpers are just missing in most of other libs, although there are so useful. It is just a pain when you want to apply a simple transformation but the lib forces you to run through the whole roundtrip. Super-advanced-fine-grained reconciliation isn't the holy grail, it is just for your laziness. Luckily Mikado provides you the most effective reconcile today, so that shouldn't be a problem.

Let's come to the most absurd part. Especially data-driven reconciliation often becomes absolutely nonsense when you dealing with internal data and you start to apply changes by hand like data.push(item) instead of simply doing this view.add(item), or var data = store(); data[0] = item; store(data); instead of simply doing this view.replace(0, item), the latter does not need reconciliation at all and performs faster by a huge factor and also it says what it does. Now take the helper methods and imagine you would apply them via data-driven. You will end up by start coding creepy things like data.splice(index_a, 0, data.splice(index_b, 1)[0]). Please do me the favor, use that helpers and don't pain yourself as well as your applications performance. Your users will thank you.

DOM State Caching

Caching of DOM properties can greatly increase performance (up to 20x). There are just few situations where caching will not improve performance, it fully depends on your application.

Recommendation: enable caching when some of your data will stay unchanged from one to another render task. Disable caching when changes on data requires a fully re-render more often.

Caching is by default enabled, this may change in future, so best is to explicitly set this flag when initializing:

var view = new Mikado(template, { cache: true });

We strongly recommended to read the next section to understand how caching is working.

The Concept

Let's take a simple template as an example:

<root>
    <div class="active">{{ data.title }}</div>
</root>

The template above has just one dynamically expression. It could be rendered as follows:

view.render({ title: "foobar" });

Assume you get new data and wants to update the view, but the new data has still the same value for title:

view.render({ title: "foobar" });

This time, the property will not changed. That specific part now executes more than 10,000 times faster. Make maximum use of this strategy will speed up things amazingly.

When caching is enabled it automatically applies for all dynamic expressions within a template by default.

So whenever you like to change one of the nodes attributes or contents (e.g. style, class, properties, dataset, etc) you just wrap this as an expression within the template and it will apply automatically.

For example, when you would like to change the classname also, then just wrap in as an expression:

<root>
    <div class="{{ view.active }}">{{ data.title }}</div>
</root>

You do not have to use data only, you can also use a payload view or the state property. Using them right increases the flexibility of template re-using.

Now lets come to the most important part when using caching properly. Assume you have rendered the template above with caching enabled. Now you manually change DOM attributes:

var node = document.getElementsByClassName("active")[0];
node.textContent = "manual change";

The changes will apply to the DOM. Now you re-render the template with the "old" state:

view.render({ title: "foobar" });

This time the change will not apply. Because the internal cache assumes that the current value is still "foobar" and skips the change.

Basically you have 2 options in this situation:

  1. do not manually change dom properties or states (instead change all through rendering templates)
  2. using the caching helpers which Mikado provides you exactly to this purpose.

Please keep in mind that manual changes to the DOM has its limits:

Generally do not manually change dom properties or states which are not covered by the template. Changes that aren't covered by the template may gets lost when re-rendering (in few situations this will not being an issue).

Caching Helpers

Caching helpers will help you to bypass manual changes to the DOM without going out of sync.

You can also use these helpers for all changes to any DOM node independent of it is part of the template or not. Generally these helpers increase every DOM access.

Set attribute of a node (will not replaces old attributes):

Mikado.setAttribute(node, "href", "/foo");
Mikado.setAttribute(node, {
    id: "foo",
    href: "/foo"
});

Get attribute of a node:

var attr = Mikado.getAttribute(node, "href");

Set class name of a node (fully replaces old classes):

Mikado.setClass(node, "class_a class_b");
Mikado.setClass(node, ["class_a", "class_b"]);

Get class names of a node (returns an array):

var classlist = Mikado.getClass(node);

Set inline styles of a node (fully replaces old styles):

Mikado.setCSS(node, "top: 0; padding-right: 10px");
Mikado.setCSS(node, ["top: 0", "padding-right: 10px"]);

Get all inline styles of a node:

var css = Mikado.getCSS(node);

Set inline styles of a node (will not replaces old styles):

Mikado.setStyle(node, "padding-right", "10px");
Mikado.setStyle(node, {"top": 0, "padding-right": "10px"});

Get a specific inline style of a node:

var style = Mikado.getStyle(node, "padding-right");

Set text of a node:

Mikado.setText(node, "This is a title.");

Get text of a node:

var text = Mikado.getText(node);

Set inner html of a node:

Mikado.setHTML(node, "<b>This is a title.</b>");

Get inner html of a node:

var html = Mikado.getHTML(node);

Stores

Mikado provides 4 different type of stores. It is very useful to understand how they are handled.

1. Internal Store

An internal store gets updated automatically by Mikado. This comes with a small extra cost. Use this store when you need a reference to the data store as an array of items which are currently rendered.

When internal store is used, this store gets automatically updated by any of Mikados methods e.g. render/update/add/append/remove.

Enable internal store by passing the options during initialization:

var view = new Mikado(template, { store: true });

Whenever you call the .render() function along with passed data, this data will updated (add/remove/change) to the internal store.

view.render(data);

You can re-render/refresh the last/current state at any time without passing data again:

view.refresh();

Or force an update to a specific index:

view.refresh(index);

Or force an update to a specific node:

view.refresh(node);

Access to the store:

var store = view.store;

Do not de-reference the store, e.g.:

var store = view.store;
// ...
store = [];

Instead do this:

var store = view.store;
// ...
view.store = store = [];

2. Loose Store (Default)

When loose is enabled Mikado will use a data-to-dom binding strategy rather than keeping data separated from rendered elements/templates. This performs slightly faster and has lower memory footprint but you will also loose any data at the moment when the corresponding dom element was also removed from the screen (render stack). In most situation this is the expected behavior, but it depends on your application.

Initialize a loose store:

var view = new Mikado(template, { store: true, loose: true });

To get a data item back from a node you cannot access view.store[] when loose option is enabled. You have to get the item from node or by index:

var item = view.data(index);
var item = view.data(node);

3. External/Custom Store

External stores differs from the other ones. An external store assumes to get updated from the outside and will not changed by Mikado. That means that you have to apply all changes to the external store before rendering. Use this store when:

  • you like to use data-driven style
  • you need sharing the data store to your application functions or libs
  • you like to make the data store immutable for Mikado

When external store is used, this store gets not updated by any of Mikados methods e.g. render/update/add/append/remove.

There is one exception: when you use proxy (observable attributes), the external store will replaced by the proxyfied reference once (otherwise the proxy feature becomes useless).

You can also pass an reference to an external store. This store must be an Array-like type.

var MyStore = [ /* Item Data */ ];

Pass in the external store when initializing:

var view = new Mikado(root, template, {
    store: MyStore
});

4. Reactive Store (Observable Array)

This is also an external store with all its attributes described above. Additionally this store reacts when indices gets changed (applies changes to DOM automatically). That makes reconciliation unnecessary but also has a noticeable extra cost for all other kind of updates. The main reason why this store is slower in the benchmark by a large margin is, that this store cannot apply a bulk of updates through a loop. It reacts at the moment the data was assigned/removed from an index. Still, this store could perform faster than all other ones depending on your application / current view.

The reactive store could also be used in a combination with the proxy feature. Using both provides you a complete reactive store where you do not need calling any of Mikados methods anymore like render/reconcile/update/add/append/remove. All this methods gets redundant, because the view is completely synchronized along the whole state of your store. This combination and how they are integrated in Mikado are unique. The "repaint" test from the benchmark ist just an empty function call and performs astronomical.

Read the documentation about this kind of store here.

Export / Import Views

You can export the data of a view to the local store.

view.export();

You can import and render the stored data by:

view.import().render();

When exporting/importing templates, the ID is used as key. The template ID corresponds to its filename.

Actually you cannot export several instances of the same template which holds different data. Also the state is not included in the export.

State

State pretty much acts like passing a view payload when rendering templates. State also holds an object but instead used to keep data across runtime. State data are also shared across all Mikado instances. State is directly assigned to each Mikado instance and do not has to pass during rendering. This all differ from using view payloads.

Define state properties:

view.state.date = new Date();
view.state.today = function(){ return view.state.date === new Date() };

You can assign any value as state or function helpers. Do not de-reference the state from the Mikado instance. When using export() the state will just export non-object and non-functional values. You need to re-assign them when the application starts.

Using extern states:

var state = {
    date: new Date(),
    today: function(){ return view.state.date === new Date() }
};

Assign extern states during initialization:

var view = new Mikado(root, template, {
    state: state
});

Callbacks

Apply callbacks during initialization:

var view = new Mikado(root, template, {
    on: {
        create: function(node){ console.log("created:", node) },
        insert: function(node){ console.log("inserted:", node) },
        update: function(node){ console.log("updated:", node) },
        change: function(node){ console.log("changed:", node) },
        remove: function(node){ console.log("removed:", node) },
    }
});

Transport / Load Templates

Mikado fully supports server-side rendering. The template (including dynamic expressions) will compile to plain compatible JSON.

If your application has a lot of views, you can save memory and performance when loading them at the moment a user has requested this view.

Templates are shared across several Mikado instances.

Load template asynchronously into the global cache:

Mikado.load("https://my-site.com/tpl/template.json", function(error){
    if(error){
        console.error(error);
    }
    else{
        console.log("finished.");
    }
});

Load template asynchronously with Promises into the global cache:

Mikado.load("https://my-site.com/tpl/template.json", true).then(function(){

    console.log("finished.");

}).catch(function(error){

    console.error(error);
});

Load template synchronously by explicit setting the callback to false:

Mikado.load("https://my-site.com/templates/template.json", false);

Assign template to a new Mikado instance, mount and render:

var view = Mikado.new("template");
view.mount(document.body).render(data);

.load() loads and initialize a new template to an existing Mikado instance:

view.load("https://my-site.com/templates/template.json");

.init() assigns a new template to an instance:

view.init("template");

.mount() assigns a new root destination to an instance:

view.mount(document.getElementById("new-root"));

.unload() unloads a template by name (filename):

view.unload("template");

Chain methods:

view.mount(document.body).init("template").render(data);

Static Templates

When a template has no dynamic expressions (within curly brackets) which needs to be evaluated during runtime Mikado will handle those templates as static and skips the dynamic render part. Static views could be rendered without passing data.

Once (One-time rendering)

When a template just needs to be rendered once you can create, mount, render, unload and destroy (full cleanup) as follows:

Mikado.new(template)
      .mount(root)
      .render()
      .unload() // unload before destroy!
      .destroy();

Destroy has a parameter flag to automatically unload before destroy:

Mikado.new(root, template)
      .render()
      .destroy(true);

You can also simply use a shorthand function:

Mikado.once(root, template); // static view
Mikado.once(root, template, data); // dynamic view
Mikado.once(root, template, data, payload, callback);

When destroying a template, template definitions will still remain in the global cache. Maybe for later use or when another instances uses the same template (which is generally not recommended).

When unloading templates explicitly the template will also removes completely. The next time the same template is going to be re-used it has to be re-loaded and re-parsed again. In larger applications it might be useful to unload views to free memory when they was closed by the user.

Compiler Service / Live Templates

Mikado provides you a webserver to serve templates via a simple RESTful API. This allows you to send views live from a server. Also this can be used for live reloading templates in a local development environment.

Install Mikado Server via NPM:

npm install mikado-server

Start the compiler server:

npx mikado-server

The service is listening on localhost. The API has this specification:

{host}:{port}/:type/path/to/template.html

Examples:

  • localhost:3000/json/template/app.html
  • localhost:3000/json/template/app (WIP)
  • localhost:3000/template/app.json (WIP)

They all have same semantics, you can use different forms for the same request.

Types:

Local Development

The compiler service is also very useful to render templates ony the fly when modifying the source code. Use a flag to switch between development or production environment in your source code, e.g.:

// production:
import tpl_app from "./path/to/app.es6.js";
let app;

if(DEBUG){
    // development:
    Mikado.load("http://localhost:3000/json/path/to/app.html", false);
    app = Mikado.new("app");
}
else{
    app = Mikado.new(tpl_app);
}

// same code follows here ...

You can also import them as ES6 modules directly via an asynchronous IIFE:

let tpl_app;

(async function(){
    if(DEBUG){
        // development:
        tpl_app = await import('http://localhost:3000/es6/path/to/app.html');
    }
    else{
        // production:
        tpl_app = await import("./path/to/app.html");
    }
}());

// same code follows here ...
const app = Mikado.new(tpl_app);

Server-Side Rendering (SSR)

WIP

Use the json format to delegate view data from server to the client. Actually just static templates are supported. An express middleware is actually in progress to create templates with dynamic expressions.

Includes

Partials gets its own instance under the hood. This gains performance and also makes template factories re-usable when same partials are shared across different views.

Be aware of circular includes. A partial cannot include itself (or later in its own chain). Especially when your include-chain growths remember this rule.

Assume you've created a partial template. Make sure the template is providing one single root as the outer bound.

You have to register all partial templates once before you initialize the templates which will including them:

import tpl_header from "./tpl/header.es6.js";
import tpl_article from "./tpl/article.es6.js";
import tpl_footer from "./tpl/footer.es6.js";

Mikado.register(tpl_header);
Mikado.register(tpl_article);
Mikado.register(tpl_footer);

When using templates in ES5 compatible format, they are automatically registered by default. You can also use the runtime compiler and pass the returned template to the register method.

Now you can include partials with a pseudo element:

<section>
    <include>{{ header }}</include>
    <include>{{ article }}</include>
    <include>{{ footer }}</include>
</section>

Use the template name (filename) for includes.

The pseudo element \<include> will extract into place and is not a part of the component. You cannot use dynamic expressions within curly brackets, just provide the name of the template.

Equal to:

<section>
    <include from="header"></include>
    <include from="article"></include>
    <include from="footer"></include>
</section>

You can't use self-closing custom elements accordingly to the HTML5 specs e.g. <include from="title"/>.

You can also include to a root node which is part of the component by an attribute:

<section>
    <header include="header"></header>
    <article include="article"></article>
    <footer include="footer"></footer>
</section>

Loop Partials

Assume the template example from above is a tweet (title, article, footer).

<section>
    <title>{{ data.title }}</title>
    <tweets include="tweet" for="data.tweets">
        <!-- tweet -->
        <!-- tweet -->
        <!-- tweet -->
    </tweets>
</section>

This expression will render the template "tweet" through an array of data items/tweets. The template "tweet" is getting the array value data.tweets as data.

The max attribute could be used optionally to limit the partial loop:

<tweets include="tweet" for="data.tweets" max="5">

The max attribute could also be negative to reverse the boundary direction, e.g. loop through the last 5 items:

<tweets include="tweet" for="data.tweets" max="-5">

Inline Loops

You can also loop through an inline partial. Mikado will extracting and referencing this partial to its own instance under the hood.

<main>
    <title>{{ data.title }}</title>
    <tweets for="data.tweets">
        <section>
            <header include="header"></header>
            <article include="article"></article>
            <footer include="footer"></footer>
        </section>
    </tweets>
</main>

You can also nest loops:

<tweets for="data.tweets">
    <tweet>
        <h1>{{ data.title }}</h1>
        <title>Comments:</title>
        <div for="data.comments">
            <comment>
                <p>{{ data.content }}</p>
                <title>Replies:</title>
                <div for="data.replies">
                    <p>{{ data.content }}</p>
                </div>
            </comment>
        </div>
    </tweet>
</tweets>

Every looped partial has to provide one single root as the outer bound.

In this example every for-expression is wrong (you will find the right example above):

<tweets for="data.tweets">
    <h1>{{ data.title }}</h1>
    <title>Comments:</title>
    <div for="data.comments">
        <p>{{ data.content }}</p>
        <title>Replies:</title>
        <div for="data.replies">
            {{ data.content }}
        </div>
    </div>
</tweets>

Conditional Branches

<main if="data.tweet.length">
    <title>Tweets: {{ data.tweet.length }}</title>
</main>
<main if="!data.tweet.length">
    <title>No tweets found.</title>
</main>
<main>
    <title>{{ data.title }}</title>
    <tweets if="data.tweets.length" for="data.tweets">
        <section>{{ data.content }}</section>
    </tweets>
</main>
<main>
    <title>{{ data.title }}</title>
    <tweets for="data.tweets">
        <section if="data.content">{{ data.content }}</section>
    </tweets>
</main>

Think in real code branches, instead of doing this:

<main>
    {{@ var result = (function(){
        return "some big computation";
    }()) }}
    <section if="data.content">{{ result }}</section>
</main>

Doing this:

<main>
    <section if="data.content">
        {{ (function(){
            return "some big computation";
        }()) }}
    </section>
</main>

Conditional branches will skip its expressions when not taken.

As well as try to assign computations outside a loop:

<main>
    {{@ var result = data.tweets.length && (function(){
        return "some big computation";
    }()) }}
    <tweets for="data.tweets">
        <section>{{ result }}</section>
    </tweets>
</main>

Reactive Proxy (Observer)

Mikado provides you a reactive way to listen and apply changes of data to the DOM. It is based on the new ES6 proxy feature which gives awesome performance and fully falls back to classical observer when proxy is not available. Using an reactive strategy can additionally boost performance beyond a factor of 100 when updating data. It depends on you application / current view: this feature has an advantage when updating data has to process more often than creating new.

Template markup:

<table>
    <tr>
        <td>Name:</td>
        <td>{{= data.name }}</td>
    </tr>
    <tr>
        <td>Email:</td>
        <td>{{= data.email }}</td>
    </tr>
</table>

The expression for an observable property has to start with: {{=

Using proxy requires using one of the 3 store strategies.

1. Use with internal store:

var view = new Mikado(template, { store: true });
view.render([...]);

When data changes, the corresponding dom element will automatically change:

view.store[0].name = "New Name";

2. Use with external store:

var data = [...];
var view = new Mikado(template, { store: data });
view.render(data);

When data changes, the corresponding dom element will automatically change:

data[0].name = "New Name";
view.store[0].name = "New Name";

3. Use with loose store:

var view = new Mikado(template, { store: true, loose: true });
view.render([...]);

When data changes, the corresponding dom element will automatically change:

view.data(0).name = "New Name";

Limitations

Proxy actually comes with some limitations on template expressions. Improving those restrictions is already work in progress and will release soon.

1. Fields from deeply nested data objects are not reactive:

var data = {
    id: "foobar", // <-- observable
    content: {    // <-- observable
        title: "title",  // <-- not
        body: "body",    // <-- not
        footer: "footer" // <-- not
    }
};

2. Conditional or advanced template expressions are not supported:

<table>
    <tr>
        <td>Name:</td>
        <!-- Supported: -->
        <td>{{= data.name }}</td>
    </tr>
    <tr>
        <td>Tweets:</td>
        <!-- Not Supported: -->
        <td>{{= data.tweets ? data.tweets.length : 0 }}</td>
    </tr>
</table>

Just use plain property notation within curly brackets.

Stealth Mode

Whenever all your template expressions are just using proxy notation it enables the "stealth" mode which boost performance from every update process to the absolute maximum. This mode has no advantage when every render loop has to apply new items.

This enables stealth mode:

<item>
    <caption>Name:</caption>
    <p>{{= data.name }}</p>
    <caption>Email:</caption>
    <p>{{= data.mail }}</p>
</item>

This not:

<item>
    <caption>Name:</caption>
    <p>{{= data.name }}</p>
    <caption>Email:</caption>
    <p>{{ data.mail }}</p>
</item>

Also using conditionals, loops and inline javascript will prevent from switching to the stealth mode. Just includes (without loop) could be used additionally to the proxy notation, but it requires all fields also observed by the partial which is included.

Observable Array (Virtual NodeList)

Additionally to react on changes of properties you can create an observable Array which acts like a synchronized NodeList. It uses ES6 Proxy under the hood which fully falls back to classical observer, when not available.

Semantically the observable Array is equal to an array-like Javascript array.

Create an observable array:

var array = Mikado.array();

Create an observable array with initial data:

var items = [...];
var array = Mikado.array(items);

Bind this store to a Mikado instance:

var view = Mikado(target, template, { store: array });

Now the observable array ist linked with your instance. Whenever you change the array all changes applies automatically to the corresponding template.

You can use all common array built-ins, e.g.:

array.push({ ... });
var last = array.pop();
array.unshift({ ... });
array.splice(0, 1, { ... });

The best is you can get and set via array index access which is a rare available feature (including non-proxy fallback):

array[0] = { ... };
array[array.length] = { ... };
var first = array[0];

A list of all supported array prototypes:

  • length
  • push
  • pop
  • shift
  • unshift
  • slice
  • splice
  • concat
  • indexOf
  • lastIndexOf
  • filter
  • map
  • reverse
  • sort
  • swap

These methods are basically implemented, without some extensions like parameter chaining. They may come in a future update .e.g array.push(a, b, c) is not available, instead you have to call push for each item on by one.

The method array.swap(a, b) is an optionally performance shortcut.

There are some methods which differs slightly from the original implementation. These methods will apply changes in place and returning the original reference instead of applying on a copy:

  • concat
  • filter
  • map

When you need the original behavior you can simply do that by:

var new_array = [ ... ];
var copy = Array.prototype.concat.call(array, new_array);
var copy = Array.prototype.filter.call(array, function(){ ... });

There is a limitation when falling back to the non-proxy polyfill. You cannot fill sparse arrays or access indexes which are greater than the current array.length. There is just one undefined index which could always accessed (by read/write) that is the last "undefined" index on an array when you call array[array.length]. This index is a special marker which increase the "virtual" array size. Whenever you assign a value to this special index the size of the observable index growth automatically and the next "undefined" index in the queue becomes this marker. This limitation is not existing when ES6 proxy is available.

Also there are some drawbacks when reflection is used:

var array = Mikado.array();
console.log(array.constructor === Array); // -> false
console.log(array.prototype === Array.prototype); // -> false
console.log(array instanceof Array); // -> false

The proxy feature theoretically allows those reflection but could not be used to keep the polyfill working in addition of sharing mostly of the same codebase.

Bind Input Elements

WIP (release when reaching 2500 Github stars)

The attribute bind provides you a 2-way-binding of input elements with your data store.

<main>
    <input type="text" bind="{{ data.name }}">
    <input type="checkbox" bind="{{ data.status }}">
    <input type="radio" value="female" bind="{{ data.gender }}">
    <input type="radio" value="male" bind="{{ data.gender }}">
</main>

When data is changed, the input elements will automatically update, as well as other turn around, when the input elements gets new data the store will automatically update.

Best Practices

A Mikado instance has a stronger relation to the template as to the root element. Please keep this example in mind:

This is good:

var view = new Mikado(template);

view.mount(root_a).render(data);
view.mount(root_b).render(data);
view.mount(root_c).render(data);

This is okay, but instead of this:

view.mount(root);
view.init(tpl_a).render(data);
view.init(tpl_b).render(data);
view.init(tpl_c).render(data);

Doing this:

var view_a = new Mikado(tpl_a);
var view_b = new Mikado(tpl_b);
var view_c = new Mikado(tpl_c);

view_a.mount(root_c).render(data);
view_b.mount(root_b).render(data);
view_c.mount(root_a).render(data);

Ideally every template should have initialized by one Mikado instance and should be re-mounted when using in another context. Re-mounting is very fast but re-assigning templates is not as fast.

Memory Optimizations

Clear shared pools of the current template:

view.purge();

Clear cache:

view.sync(/* uncache? */ true);

Destroy a view:

view.destroy();

Unload/unregister a template definition:

view.unload();

Destroy a view + unload:

view.destroy(/* unload? */ true);

Reconcile (Diffing)

Mikado comes with its own new diffing algorithm which gains performance of reconcile/re-arrangement. The algorithm is based on a concept "Longest Distance" which was invented by me, the author of this library. I also discovered two other concepts from scratch from where I have also implemented the "3-Way-Splice", but longest distance has slightly better overall performance. Although by a very small margin. Theoretically the splice concept has some advantages but it isn't that easy to make them capable.

Mikados reconcile provides you the most effective diffing today (you can take the row "order" from the benchmark as a reference).

Concept of Shared Pools

The are four kinds of synchronized pools under the hood. Three of them are shared across all template instances to make them re-usable. That also save memory and skip redundant re-calculations.

Factory Pool

The factory pool shares partials or same template definitions. When partials or templates are used more than once they will point to the same instance. That will save memory, skip redundant re-calculations and also improve runtime execution, because different jobs can now run through the same process (less reference spread).

Template Pool

The template pool is a feature accordingly to the option reuse and extends the strategy of re-using. Templates has to be created by the factory just once and stay available for re-using along the whole runtime.

Keyed Pool

The keyed pool is basically the same concept like template pool, but it has keyed access and works differently than the template pool (which is queued and has indexed access). The keyed pool and the template pool are synchronized. It depends on the options which was set.

Live Pool

The live pool contains all elements which are actually rendered on screen (in use). That will keep track of not sharing elements which are already in use by another view. When elements were removed, they will move from live pool to the shared pools. When the option reuse was set to false, the live pool will also share its elements to the next render loop of the same view.

Some notes about pools

Pooling just extends concepts which are already exist/used:

  1. The queued pool extends the feature of "recycling" (reusing) nodes
  2. The keyed pool extends the feature of keeping components which are referential keyed

"Does pooling have any advantages?" yes, absolutely. Otherwise recycling or keyed wouldn't have any advantage at all. So semantically there is nothing changed when using pools.

"How does pooling improve performance?" the benchmark suite from here gets a benefit in 4 from 11 test cases by using pools (create, replace, append, toggle). In all other tests pooling has no effect, except it has an extra cost because of applying pool transitions. But that isn't the main essence behind pools. Everyone who is thinking pooling exist to just boost this benchmark did not understand this concept. The most important capability of Mikados pools isn't covered by any of this tests yet. Because the true advantage comes in when using partials, includes and partial loops. But those aren't strictly compared anywhere. If there was a benchmark which measures the performance of partial loops and mixin them in different contexts (by example) I'm pretty sure, that Mikado gains an astronomical performance factor over all others. Sadly I haven't the time to provide such a comparison. In the future someone might be release a benchmark which covers this case, I would go in immediately.

Motivation

This library was build by reversed engineering with these primary goals as its base: 1. providing a clean, simple and non-cryptic tool for developers who focus on living standards and common styles 2. designer-readable templates based on pure html (most famous and compatible markup in the web) 3. providing the best overall performance 4. can be flexibly integrated into every stack

Milestones

There are some features in draft and it points out that those features requires more effort to implement. I need some motivation, so I will wait for this library gets more popular.

Custom Builds

Perform a full build:

npm run build

Perform a light build:

npm run build:light

Perform a custom Build:

npm run build:custom ENABLE_CACHE=false LANGUAGE_OUT=ECMASCRIPT5 USE_POLYFILL=true

On custom builds each build flag will be set to false by default.

The custom build will be saved to dist/mikado.custom.xxxxx.js (the "xxxxx" is a hash based on the used build flags).

The destination folder of the build is: /dist/

Supported Build Flags

Copyright 2019 Nextapps GmbH Released under the Apache 2.0 License

0.0.8

5 years ago

0.0.7

5 years ago

0.0.6

5 years ago

0.0.5

5 years ago

0.0.4

5 years ago

0.0.2

5 years ago

0.0.1

5 years ago

0.0.0

5 years ago