gaikan

The fastest JavaScript template engine.

Gaikan (2.0.0)

Gaikan is the fastest JavaScript template engine and has been designed to use data-* attributes to implement most template features. Due to this design, the templates are completely designer- and tooling friendly. The template engine is compatible with nodejs, expressjs (a popular web application framework for nodejs) and modern browsers (IE7+). Test the template engine in the browser with the interactive Gaikan (work in progress, IE9+) tool.

npm install gaikan
var gaikan = require('gaikan');
app.engine('html', gaikan);
app.set('view engine', '.html');
<script src="gaikan-2.x.x-min.js"  type="text/javascript"></script>

The template engine is, at the moment, the fastest JavaScript template engine. The benchmark suite has been made available at template-benchmark (forked from an unmaintained template benchmark suite). The benchmark suite was run on an AMD Phenom(tm) II X4 955 3.2GHZ processor.

Gaikan               ( 2090ms) - fastest
ECT                  ( 2334ms) - 12% slower
Fest                 ( 2791ms) - 34% slower
Dust                 ( 3030ms) - 45% slower
doT                  ( 3940ms) - 89% slower
Hogan.js             ( 3977ms) - 90% slower
EJS without `with`   ( 5190ms) - 148% slower
Swig                 ( 5258ms) - 152% slower
Underscore           ( 6154ms) - 194% slower
Handlebars.js        ( 7255ms) - 247% slower
Eco                  ( 8315ms) - 298% slower
EJS                  ( 9059ms) - 333% slower
Jade without `with`  (10973ms) - 425% slower
CoffeeKup            (11062ms) - 429% slower
Jade                 (27295ms) - 1206% slower
Gaikan               ( 2147ms) - fastest
Fest                 ( 2535ms) - 18% slower
doT                  ( 3524ms) - 64% slower
Underscore           ( 5108ms) - 138% slower
Handlebars.js        ( 5734ms) - 167% slower
ECT                  ( 7223ms) - 236% slower
EJS without `with`   ( 8732ms) - 307% slower
Dust                 ( 9136ms) - 326% slower
Hogan.js             ( 9960ms) - 364% slower
Swig                 (10240ms) - 377% slower
Eco                  (12292ms) - 473% slower
Jade without `with`  (13510ms) - 529% slower
EJS                  (14917ms) - 595% slower
CoffeeKup            (15319ms) - 614% slower
Jade                 (34000ms) - 1484% slower

It is important to understand a template engine, and this template engine is essentially quite simple. The template engine consumes a template and produces a JavaScript function. That function, representing the template, is then invoked with a runtime (__r, the helper functions), a root object (the user input) and a fragments object (__f, more on this later). The result of the function is the output. For each example in this documentation, the following object is used as root:

{
    message: '<b>Hello world!</b>',
    messageMultipleLines: 'Hello\nworld!',
        messagePlain: 'Hello world!',
    people: [{
        age: 25,
        name: 'John'
    }, {
        age: 18,
        name: 'Jane'
    }]
}

Due to this nature of compiling the template to a JavaScript function, the template features (such as variables and attributes) produce JavaScript code and emit each line of code into the function. To demonstrate the behaviour of each feature, the examples in this documentation will show the template, the function and the output. For example:

Hello world!
function anonymous(__r, root, __f) {
    var __o = '';
    __o += 'Hello world!';
    return __o;
}
Hello world!

It is recommended to use the interactive Gaikan (work in progress, IE9+) tool to play with each example.

A variable is the only feature that does not use a data-* attribute.

!{root.message}
function anonymous(__r, root, __f) {
    var __o = '';
    __o += (typeof root.message === 'undefined' ? '' : root.message);
    return __o;
}
<b>Hello world!</b>

Escaping a variable prevents XSS and is the recommend variable usage.

#{root.message}
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += (typeof root.message === 'undefined' ? '' : alterant.encode(root.message));
    return __o;
}
&#60;b&#62;Hello world!&#60;/b&#62;

An alterant modifies a variable. Multiple alterants can be chained.

#{root.messagePlain|upper,url}
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += (typeof root.message === 'undefined' ? '' : alterant.url(alterant.upper(alterant.encode(root.messagePlain))));
    return __o;
}
HELLO%20WORLD!

The break alterant replaces new lines with <br />.

#{root.messageMultipleLines|break}
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += (typeof root.messageMultipleLines === 'undefined' ? '' : alterant.break(alterant.encode(root.messageMultipleLines)));
    return __o;
}
Hello<br />world!

The decode alterant decodes special HTML characters.

#{root.message|decode}
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += (typeof root.message === 'undefined' ? '' : alterant.decode(alterant.encode(root.message)));
    return __o;
}
<b>Hello world!</b>

The encode alterant encodes spcial HTML characters. The equivalent of a # variable statement.

!{root.message|encode}
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += (typeof root.message === 'undefined' ? '' : alterant.encode(root.message));
    return __o;
}
&#60;b&#62;Hello world!&#60;/b&#62;

The lower alterant changes the variable to lower case.

#{root.messagePlain|lower}
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += (typeof root.messagePlain === 'undefined' ? '' : alterant.lower(alterant.encode(root.messagePlain)));
    return __o;
}
hello world!

The title alterant changes the variable to title case.

#{root.messagePlain|title}
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += (typeof root.messagePlain === 'undefined' ? '' : alterant.title(alterant.encode(root.messagePlain)));
    return __o;
}
Hello World!

The upper alterant changes the variable to upper case.

#{root.messagePlain|upper}
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += (typeof root.messagePlain === 'undefined' ? '' : alterant.upper(alterant.encode(root.messagePlain)));
    return __o;
}
HELLO WORLD!

The url alterant encodes the variable as an url component.

#{root.messagePlain|url}
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += (typeof root.messagePlain === 'undefined' ? '' : alterant.url(alterant.encode(root.messagePlain)));
    return __o;
}
Hello%20world!

A set operates on an array or object (with properties).

The empty set checks if a variable is an empty set.

#{set.empty(root.people)}
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var set = __r.set;
    var __o = '';
    __o += (typeof set.empty(root.people) === 'undefined' ? '' : alterant.encode(set.empty(root.people)));
    return __o;
}
false

The sort set can sort a variable, optionally by key and reverse.

<div data-for="person in set.sort(root.people, 'age')">
    #{person.name} is #{person.age}
</div>
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var set = __r.set;
    var __o = '';
    __o += '<div>';
    var __v0 = set.sort(root.people, 'age');
    if (__v0) {
        for (var __k0 = 0; __k0 < __v0.length; __k0 += 1) {
            var person = __v0[__k0];
            __o += '\n    ' + (typeof person.name === 'undefined' ? '' : alterant.encode(person.name)) + ' is ' + (typeof person.age === 'undefined' ? '' : alterant.encode(person.age)) + '\n';
        }
    }
    __o += '</div>';
    return __o;
}
<div>
    Jane is 18

    John is 25
</div>

The data-* attributes emits (or omits) lines of code in the function.

The data-if attribute emits an if.

<div data-if="root.message.length > 2">
    #{root.message}
</div>
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += '<div>';
    if (root.message.length > 2) {
        __o += '\n    ' + (typeof root.message === 'undefined' ? '' : alterant.encode(root.message)) + '\n';
    }
    __o += '</div>\n';
    return __o;
}
<div>
    &#60;b&#62;Hello world!&#60;/b&#62;
</div>

The data-for attribute emits a for. Use in for arrays, of for objects. Key can be omitted.

<div data-for="value, key in root.people">
    #{value.name} at index #{key} is #{value.age}
</div>
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += '    <div>';
    var __v1 = root.people;
    if (__v1) {
        for (var key = 0; key < __v1.length; key += 1) {
            var value = __v1[key];
            __o += '\r\n\t    ' + (typeof value.name === 'undefined' ? '' : alterant.encode(value.name)) + ' at index ' + (typeof key === 'undefined' ? '' : alterant.encode(key)) + ' is ' + (typeof value.age === 'undefined' ? '' : alterant.encode(value.age)) + '\r\n\t';
        }
    }
    __o += '</div>';
    return __o;
}
<div>
    John at index 0 is 25

    Jane at index 1 is 18
</div>

The data-attribute-* attribute conditionally adds attributes to an element.

<option data-attribute-selected="root.message.length > 10"></option>
function anonymous(__r, root, __f) {
    var __o = '';
    __o += '<option';
    if (root.message.length > 10) {
        __o += ' selected';
    }
    __o += '></option>';
    return __o;
}
<option selected></option>

The data-extract attribute removes the containing element.

<div data-extract>
    #{root.message}
</div>
function anonymous(__r, root, __f) {
    var alterant = __r.alterant;
    var __o = '';
    __o += '\n    ' + (typeof root.message === 'undefined' ? '' : alterant.encode(root.message)) + '\n';
    return __o;
}
&#60;b&#62;Hello world!&#60;/b&#62;

The data-include attribute includes another template. The root passed to the included template is by default root, but can be changed using templateName|root. The data-fragment attribute is used to add a placeholder, or fill a placeholder in the included template.

<div data-include="templateName">
    <div data-fragment="content">
        Replaces the content fragment placeholder in test.
    </div>
</div>
function anonymous(__r, root, __f) {
    var __o = '';
    __o += '<div>';
    var __f0 = {};
    __f0['content'] = function (__r, root) {
        var __o = '';
        __o += '\n        Replaces the content fragment placeholder in test.\n    ';
        return __o;
    };
    __o += __r('templateName', root, __f0);
    __o += '</div>';
    return __o;
}
<div>Test template with placeholder in b: <b>
        Replaces the content fragment placeholder in test.
</b></div>

The data-evaluate emits the block as JavaScript code. The equivalent of a @ variable statement.

<div data-evaluate>
    console.log('Executed when rendering!');
</div>
@{console.log('Executed when rendering!')}
function anonymous(__r,root,__f) {
    var __o = '';
    console.log('Executed when rendering!');
    __o += '\n';
    console.log('Executed when rendering!')
    return __o;
}

These include some common scenarios with simple solutions.

The @ variable statement, which evaluates, can act as inline if when in an attribute.

<div class="@{root ? 'green' : 'red'}"></div>
function anonymous(__r, root, __f) {
    var __o = '';
    __o += '<div class="' + (root ? 'green' : 'red') + '"></div>';
    return __o;
}
<div class="green"></div>

As there is no data-else attribute, a test can be stored and used in multiple data-if attributes.

@{var messageValid = root.message.length > 10;}
<div data-if="messageValid">Valid!</div>
<div data-if="!messageValid">Not valid...</div>
function anonymous(__r, root, __f) {
    var __o = '';
    var messageValid = root.message.length > 10;
    __o += '\n<div>';
    if (messageValid) {
        __o += 'Valid!';
    }
    __o += '</div>\n<div>';
    if (!messageValid) {
        __o += 'Not valid...';
    }
    __o += '</div>';
    return __o;
}
<div>Valid!</div>
<div></div>

The server API is available in a nodejs/expressjs environment.

gaikan(input, root, fragments = {}, callback = undefined, skipLayout = false)
Compiles a function for the input file, invokes it, and returns the output.
gaikan.compileFromFile(input)
Compiles a function for the input file.
gaikan.compileFromString(input)
Compiles a function for the input string.

Available as gaikan.options.

Enables or disables template caching. When enabled, a compiled template is not compiled again.

gaikan.options.enableCache = isProduction;

Enables or disables template compression. When enabled, compiled functions compress output.

gaikan.options.enableCompression = isProduction;

An array with relative directories in which to search templates. Usused for expressjs.

gaikan.options.directories = ['views', '.'];

Templates resolve from the root directory, which defaults to the main script directory.

gaikan.options.rootDir = path.dirname(require.main.filename);

An array with allowed template file extensions. Usused for expressjs.

gaikan.options.extensions = ['gaikan', 'html'];

Changes output to be the content fragment for the layout. Used when skipLayout is false.

gaikan.options.layout = null;

The browser API is available in a modern browser environment.

gaikan(input, root, fragments = {})
Not yet implemented.
gaikan.compileFromString(input)
Compiles a function for the input string.

Available as gaikan.options.

Enables or disables template compression. When enabled, compiled functions compress output.

gaikan.options.enableCompression = false;

In both the nodejs/expressjs and modern browser environment, the template engine serves as runtime.

Alterants are available as gaikan.alterant. Adding functions makes them available in each template.

Alterants are available as gaikan.set. Adding functions makes them available in each template.

Escape variables to prevent XSS, and do not allow user content as template.

* Improve interactive testing environment (examples/interactive).
* Implement client-side template cache.
* Implement expressjs hook to run rendering on client.
* Implement designer prototyping tooling with auto-refresh and data stubbing.