node package manager

Introducing npm Enterprise add-ons. Integrate third-party dev tools into npm…

mustachio

A pull streaming Mustache engine

Mustachio is a pull streaming implementation of the Mustache templating language. It is fully compliant with version 1.1 of the Mustache spec.

Getting started

const mustachio = require('mustachio');

const mu = mustachio.resolver();

mu("demo", { name: "World" }).stream().pipe(process.stdout);

And then, in a directory templates, create the file demo.mustache:

Hello, {{name}}!

Running this program outputs:

Hello, World!

If you want to use this together with Express, see mustachio-middleware.

Why Mustachio?

Mustachio is streaming, whereas other templating engines usually are blocking. Traditionally, when serving a web request, the control flow is a sequence of three discrete steps:

  1. Input: Collect all the requisite data from the database, filesystem and any other sources
  2. Rendering: Pass the data (sometimes called "view") and (compiled) template to the templating engine to render the full response
  3. Output: Send the rendered response to the client

In this model, no output can happen before the rendering is finished, and no rendering or output can happen before all the data has been collected. This is often good enough! However, a streaming model offers greater flexibility and can give better performance.

In Mustachio these three steps happen interleaved, in a streaming fashion. This means rendering can proceed as soon as the relevant data becomes available. Consequently, Mustachio will be able to respond immediately to many requests. It also means flow control works, so rendering and gathering of input data can be suspended when the client can not keep up. This frees up resources for handling other requests.

The examples directory contains examples that highlight different qualities of the streaming model:

API

const mustachio = require('mustachio');
const template = mustachio.string("Hello, {{name}}\n");
const rendering = template.render({ name: "World" });

Stream render:

rendering.stream().pipe(process.stdout);

Render to string:

rendering.string().then(result => console.log(result));
const resolver = mustachio.resolver();

const rendering = resolver("template-name", { name: "World" });

This rendering object has stream() and string() methods just like the rendering object above that we got from the template string.

The resolver uses the template ID you give in to locate a file in the file system, under the directory templates in your project directory. It looks for files with file extensions .mustache, .mu, .mu.html and .html in that order. The same mechanism is used to resolve partial includes.

When using the resolver, the compiled template is cached in memory and reused when rendering the same template again. Additionally, a file system watcher is set up to invalidate the cache whenever the template file is edited.

You can customize the base directory for templates and the file name suffixes used for resolving partials by passing a configuration object to the resolver:

const resolver = mustachio.resolver({
  root: path.join(__dirname, "my-templates"),
  suffixes: [ ".my.oh.my" ]
});

To get other behaviour and even resolve partials from other sources than the file system (such as the database, or HTTP calls), pass a custom partials resolver object:

const resolver = mustachio.resolver({
  partialsResolver: new CustomPartialsResolver()
});

See partials for details.

When using a template string, partials will by default not be resolved. To enable partials resolving for such templates, pass a partials resolver to the render function:

template.render(data, partialsResolver);

These are the values you can put in the data object: Fundamental types, objects, arrays, functions, generator functions, promises and streams.

The power of Mustachio comes from combining these building blocks. It works perfectly well to specify a function that returns a promise which resolves to a generator that yields functions which ... etc, etc. See the file-browser example for an effective use of this.

{
  "number": 5.25,
  "desc": "is a number.",
  "true-bool": true
}

{{number}} {{desc}} {{#true-bool}}Yes!{{/true-bool}} {{true-bool}}5.25 is a number. Yes! true

{
  "a": {
    "b": 5,
    "c": {
      "d": 6
    }
  }
}

{{#a}}{{b}}, {{c.d}}{{/a}}5, 6

{ "a": [1, 2, 3] }

{{#a}}({{.}}){{/a}}(1)(2)(3).

{
  "a": () => 5
}

{{a}}5

Functions get called with the containing object as the this argument:

{
  "a": 5,
  "b": function () { return this.a * 2; }
}

{{b}}10

Generator functions will be treated as arrays:

{
  "a": function* () {
    yield 0;
    yield "is smaller than";
    yield 1;
  }
}

{{#a}}({{.}}){{/a}}(0)(is smaller than)(1)

{
  "a": new Promise((resolve, reject) => {
    // Any asynchronous operation
    require('fs').stat(__dirname, (stat, err) => {
      if (err) reject(err);
      else resolve(stat);
    });
  })
}

{{#a.isDirectory}}A directory!{{/a.isDirectory}}A directory!

Given

{
  "ls": () => {
    const ls = require('child_process').spawn('ls', [ '/usr' ]);
    ls.stdout.setEncoding('utf8');
    return ls.stdout;
  }
}

the template

Files in /usr:
{{ls}}
That's all, folks!

could render to

Files in /usr:
bin
etc
games
include
lib
lib32
libexec
local
sbin
share
src
var

That's all, folks!

Note that streams must have an encoding set. If the streams emit binary data rather than text strings, the template rendering will fail.