node package manager

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, can give better performance and it can be a better fit in an otherwise asynchronous environment such as node.js.

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 instead of waiting until the last bit of data trickles in. 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:

  • large-response demonstrates flow control
  • file-browser demonstrates rendering with data that asynchronously becomes available
  • postgres-query-stream demonstrates rendering with data from a PostgreSQL database, stream reading from the database
  • react shows how to explicitly flush the render buffer before blocking for a large or slow operation

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

As a special concern, it is possible to explicitly flush the render buffer during rendering. Mustachio offers two ways of doing this:

  • by using the {{_flush}} tag in the template. Note that if your data object includes data named _flush it overrides the flush function
  • call stream.flush() in the code. It returns a Promise, and when this resolves, the render buffer has been flushed

See the React example for more about both methods.

These are the types of values you can put in the data object: Fundamental types, objects, arrays, functions, generator functions, promises, text streams and object mode 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!

The file-browser example demonstrates use of promises as data.

A text stream can be interpolated, like a string:

{
  "cmdline": require('fs').createReadStream('/proc/self/cmdline', 'utf8')
}

You ran: {{cmdline}}You ran: node

Note that streams must have an encoding set, so they output text rather than binary data.

An object mode stream can be iterated over, like an array:

{
  "objects": () => {
    const objects = [ 1, 2, 3 ];
    return new (require('stream')).Readable({
      objectMode: true,
      read() {
        this.push(objects.length ? objects.shift() : null);
      }
    });
  }
}

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

The postgres-query-stream example demonstrates use of object mode streams.