Sign Up for Free

RunKit +

Try any Node.js package right in your browser

This is a playground to test code. It runs a full Node.js environment and already has all of npm’s 400,000 packages pre-installed, including joytpl with all npm packages installed. Try it out:

var joytpl = require("joytpl")

This service is provided by RunKit and is not affiliated with npm, Inc or the package authors.

joytpl v0.0.21

joy - js template engine with short syntax and modern features


Template engine we use in Triggre.

This is an early release, use it at your own risk.


  1. razor like minimal syntax
  2. precompilation oriented
  3. templates are modules
  4. templates and helpers are functions
  5. clear names collision solving
  6. compression in mind



npm i joytpl -g
joytpl -h

  Usage: joytpl [options] <file ...>


    -V, --version                       output the version number
    -t, --charset <charset>             files charset (default: utf8)
    -m, --modules <modules>             module system (default: es6)
    -p, --runtime-path <runtimePath>    runtime path (default: joytpl/runtime)
    -s, --short-runtime <shortRuntime>  short runtime (default: false)
    -j, --js <jsVersion>                js version (default: es6)
    -b, --beautify <beautify>           formatted code (default: false)
    -h, --help                          output usage information
  • modules: es6, commonjs, amd
  • jsVersion: es5, es6

Write result in an output file:

joytpl path/to/input/file > output/file


npm i joytpl
app.set('view engine', 'joytpl');

To avoid suffix:

const joy = require('joytpl');

// ...

app.set('view engine', 'joy');

// ...

U can't use imports within express views. If u need imports plz use precompilation. In Triggre we use express support just for initial template of SPA which is usually pretty simple.


npm i joytpl
const joy = require('joytpl');, options, function(err, result) {
    if (err) {

    // result.content - result text of the module
    // result.extracted - object that contains all extractions

Beyond same options available in bash there are advanced ones:

    extractors: {NodeType: [(node, exported, options) => {...}], ...},
    validators: {NodeType: [(node, exported, options) => {...}], ...}

You can add custom extractors or validators to specific AST node type(look processor module for the types).
Want to forbid some variables names or extract all l10n text to single JSON file? No problem.

Dev Tools



@* you will never recall what this code is for *@


@import * as foo from 'bar/foo' 
@import foo from 'bar/foo'

Two exact import types currently supported.

Based on modules option it goes to:

import * as foo from 'bar/foo';
const foo = require('bar/foo');
define(['bar/foo'], function(foo) {});


Start sequence escape:

Escape unpaired brackets in blocks:

... {

Paired brackets may stay unescaped:

... {
    <script type="application/json">
            "foo": "bar"


All variables passed in a tpl function via object can be used with data prefix like:


Some more examples:

@data.htmlEscaped @* escape utility by default *@
@! @* raw html *@

@(data.hello)world together @* with borders *@


@formField(additionalClasses='size-' + data.size) {
    <input type="text" name="fullName" />

Joy has function calls not function definitions. Arguments in this calls are expressions with any supported types. Optional block after ")" is also specific argument. Treat it as an easy form of passing big chunk of text(html). It's also so called named argument(it has reserved name content) like additionalClasses in example. There is a rule in joy for functions: if u have a single named argument then all arguments in call must be named.

In case text in block is JSON it's parsed and passed to the function as a data object ignoring other arguments.

So in general there are two usage scenarios:

We have raw js function(imported or global) and want to use it as a tpl helper:

@Math.pow(7, 2)

We want to use a tpl from other one:

@import * as card from 'foo/card'
@import * as fullName from 'bar/fullName'

@!card() {
    @fullName(, surname=data.surname)


@import * as card from 'foo/card'
@import * as fullName from 'bar/fullName'

@!card() {
    @fullName() {{
        "name": "",
        "surname": "@data.surname"


@if data.n < 1 {
else if data.n > 1 && data.n < 100 {
    in range
else {

Put parentheses to distinguish functions bodies from conditional:

@if foo() {
    @* function argument *@
} {
    @* conditional body *@
@if (bar()) {
    @* conditional body *@


    @each item in data.items {
    @each key:value in data.items {
        <li class="@if key % 2 == 0 {even} else {odd}">@value</li>


Types that can be used in expressions:

  • bool(true/false)
  • number(1/1.0)
  • null
  • undefined
  • string("q"/'q')
  • variable
  • function

Objects and arrays are not supported yet.


Operators that can be used in expressions:

  • unary ! + -
  • && ||
  • < > <= >= == === != !==
  • + -
  • * / %

Other operators are not supported yet.

For more details see examples directory.

Enjoy! :)


RunKit is a free, in-browser JavaScript dev environment for prototyping Node.js code, with every npm package installed. Sign up to share your code.
Sign Up for Free