wontache
v0.2.0
Published
Compact, spec-compliant Mustache implementation with extras
Downloads
7,600
Maintainers
Readme
Wontache
Compact, spec-compliant Mustache implementation with extras
Wontache is a fast, precompiling implementation of the Mustache templating language for JavaScript, written in just a few hundred lines of literate CoffeeScript. It fully implements version 1.3.0 of the Mustache specification, including the optional modules, as well as some extra features which are listed further down below. An up-to-date reference of the Mustache templating language is available over here. You can try out the Wontache engine in its full glory, right from your browser, in the playground.
Compared to:
- Handlebars.js, Wontache is much smaller, substantially faster and faithful to Mustache's original syntax and semantics.
- Mustache.js, Wontache is much more accurate at indentation handling, substantially faster and committed to adhere to the official specification.
- Hogan.js, Wontache is more up-to-date with the specification, more accurate at indentation handling and a bit faster on average, but overall similar in spirit. While I was unaware of Hogan.js when I started writing Wontache and the libraries have no common heritage, I hope that Wontache can be a worthy successor of Hogan.js, which is end-of-life.
The above claims are based on extensive benchmarks and rendering comparisons. There is no user-friendly presentation of the comparison yet, but if you wish, you can inspect the comparison source code. Of course, the best way to determine how Wontache performs in your application is to try it.
- Quickstart
- Development status
- Extras
- Companion libraries and tools
- Build variants
- Underscore dependency
- Manual
- Credits
Quickstart
import mustache from 'wontache';
const template = '{{#people}}Hello {{name}}!\n{{/people}}';
const data = {
people: [
{name: 'Alice'},
{name: 'Bob'},
],
};
const templateFunction = mustache(template);
const output = templateFunction(data);
// Hello Alice!
// Hello Bob!
// You can also do a double call for a quick one-off:
output = mustache(template)(data);
Development status
The 0.x release series is intended for evaluation. Wontache already completely implements the latest Mustache specification and is fully tested. Templates that compile in Wontache 0.x are expected to also compile in the 1.x release series and should mostly produce the same output. However, the interface has not fully stabilized yet. Among other things, this means that you might need to recompile your templates when Wontache is updated.
Most of the current development work is focused on adding production-friendly conveniences, such as TypeScript and Flow type declarations, a command line tool and integrations for various code bundling tools. More extensive documentation is planned as well. You can track progress towards the official launch over here.
Extras
- Intelligently handles block indentation in the context of inheritance, in accordance with a proposed extension by the author.
- new in version 0.1.0 (integrated in version 1.3.0 of the spec) Implements dynamic names in partials and parents, allowing you to write
{{>*name}}
to mean thatname
should be looked up in the context to find the actual name of the partial that should be interpolated.name
may also be a dotted name or the implicit iterator.
.
You can track planned extras over here.
Companion libraries and tools
rollup-plugin-wontache
lets you bundle Mustache template files with your JavaScript code using Rollup.wontache-loader
lets you bundle Mustache template files with your JavaScript code using webpack.
Integrations for more bundling and task automation tools are planned, as well as other companion libraries and tools.
Build variants
Wontache has several build variants. If you use the package in Node.js or bundle it with a Node.js-aware tool such as Rollup, WebPack or Browserify, the right build variant will be selected automatically. In other situations, such as loading from a CDN, using an AMD loader or usage in deno, you may need to select a build variant explicitly. The following build variants are of interest outside of Node.js:
mustache-umd.js
is an old-fashioned UMD module. When not using CommonJS or AMD, themustache
function is globally available as_.mustache
. The global name is still subject to change.mustache-esm.js
is an ES module that imports the Underscore library as a monolithic interface. This variant is appropriate for direct use in ESM environments.module.js
is an ES module that imports individual Underscore functions from their respective modules. This variant is suitable for inclusion in a custom Underscore. More on Underscore below.
Underscore dependency
Wontache depends on Underscore for some utility functions. This enables us to write shorter, more maintainable, more portable and higher quality code than if the library was dependency-free. We encourage everyone to embrace Underscore (and other libraries) for these same reasons.
If you are concerned about dependency size, you can use the module.js
build variant in order to enable treeshaking. This is the module
entry in the package.json
, so bundlers like Rollup and WebPack will find it automatically. The subset of Underscore that Wontache depends on is only about 190 lines of ES3, including blank lines and comments.
Alternatively, the package includes a customUnderscore
subdirectory with an index.js
that lists all of our direct and indirect Underscore dependencies. You can use this in order to compose your own custom Underscore.
Wontache should also work with Lodash, although this is untested.
Manual
Compilation
The default
and only export is a function that we call mustache
by convention. It takes a Mustache template string as first argument and returns the compiled template as a function:
import mustache from 'wontache';
var template = 'template with {{variable}}';
var compiled = mustache(template);
The Rollup plugin and the webpack loader will generate the above code automatically and wrap it in a module, so you can import the compiled template directly from a standalone template file:
{{! template.mustache }}
template with {{variable}}
import compiled from './template.mustache';
Rendering
The compiled template function takes the input data as its first argument. This can be any JavaScript value. Values to interpolate in the template are taken from the data. The template function returns a string with the final result of the template, given the data.
compiled({variable: 'flair'});
// 'template with flair'
Partials
The Partial and Parent tags let you render and interpolate a template inside another template. If your templates contain either of those tags, you have to do some administration so that compiled templates are able to find each other by name. This administration takes the form of an object, where each key is the name of a template that may be embedded. The corresponding value may be either a template string or a template function (if you set a template string, it will be compiled on first use).
// The administration.
var namedTemplates = {
link: '<a href="{{&url}}">{{title}}</a>'
};
// A template that will need the above administration.
var template = '<ul>{{#.}}<li>{{>link}}{{/.}}</ul>';
var linkList = mustache(template);
// The data.
var links = [{
url: 'https://jgonggrijp.gitlab.io/wontache/',
title: 'Wontache home page'
}];
There are two possible ways to make the partial administration available to a template function. The most hygienic way is to pass an object with a partials
property as the second argument in the call to the template. This approach is strongly recommended for library authors, because it ensures that the partials provided to your template are completely isolated from the partials that other libraries or the application may be using.
linkList(links, {partials: namedTemplates});
// '<ul><li><a href="https://jgonggrijp.gitlab.io/wontache/">Wontache home page</a></ul>'
If desired, you can write a wrapper function that always passes the same partial administration to a given template function.
function render(templateFunc, data) {
return templateFunc(data, {partials: namedTemplates});
}
render(linkList, links);
// Same output as above ('<ul><li><a href="...</a></ul>').
The most convenient way is to assign your administration to mustache.partials
. Template functions automatically fall back to this if you don't pass a set of partials explicitly, so it can be "set and forget". This approach is intended for application authors. When using it, do keep in mind that mustache.partials
is easy to compromise, by overwriting either one of its keys or the property as a whole. In principle, a sloppy library author could do this as well. You may even want to dedicate a test to ensuring that mustache.partials
is complete.
Object.assign(mustache.partials, namedTemplates);
// Could also use Underscore's _.extend for compatibility.
linkList(links);
// Same output again ('<ul><li><a href="...</a></ul>').
Changing the delimiters
The {{
and }}
default delimiters were chosen for a low probability of conflict with other computer languages. However, a conflict is still possible, for example when the template is meant to generate LaTeX code, or when you are writing a template that includes example Mustache template code that should be rendered verbatim. In such cases, you can change the delimiters.
The normal way to change the delimiters is by including a Set Delimiter tag in the template text. This is recommended in most cases, because this ensures your templates are portable to other Mustache implementations. It also gives you the freedom to switch delimiters halfway through a template, even multiple times if necessary.
var template = '{{=< >=}} template with <variable>';
var compiled = mustache(template);
However, you may have a large application with many templates. If there is a specific, alternative set of delimiters that you are consistently using in each of them, you probably don't want to start each template with the same Set Delimiter tag. For this purpose, you can pass the alternative delimiters as a second argument to mustache
instead.
var template = 'template with <variable>';
var compiled = mustache(template, ['<', '>']);
If you are also calling mustache
in many places, you can wrap the function so you don't have to explicitly pass the delimiters every time:
var myMustache = template => mustache(template, ['<', '>']);
// Equivalent, using Underscore:
import _, { partial } from 'underscore';
var myMustache = partial(mustache, _, ['<', '>']);
// usage in either case:
var compiled = myMustache('template with <variable>');
Precompilation
Template compilation is a costly operation. Precompilation is an optimization that lets you do the compilation ahead of time.
A common situation where you might want to use precompilation, is in a client side web application. The application will respond faster if it does not need to compile its templates before rendering them. Precompilation lets you do the compilation already before the application code is sent to the client, for example in a serverside process that bundles the code.
Keep in mind that the precompiled template code is larger than the original template text. You save startup time at the receiving end, at the expense of transferring more data.
The easiest way to use precompilation, is to set precompile: true
when using the Rollup plugin or the webpack loader. However, you can also tap directly in the underlying mechanisms if you need a custom solution.
As described above, when you compile a template the "regular" way, you obtain a function that will render the template. This function has a source
property, which is a string that encodes a JavaScript object. That object contains the end result of compilation. It can be passed to mustache
instead of the original template text, in order to recreate the compiled template function. In other words, if you build a string precompiled
as follows,
var precompiled = "import mustache from 'wontache';\n" +
"export default mustache(" + compiled.source + ");";
and you write precompiled
to a file, you have created a new JavaScript module that exports compiled
. No compilation needs to be done in that module; all that work was already done when you created compiled
the first time, before writing the module code to disk.
compiled.source
does not include the outer mustache()
function call in order to give you freedom. For example, you could name the mustache
import differently, or you could write JavaScript code that has the precompiled objects in an array rather than passing them directly to mustache
.
If writing JavaScript modules is exactly what you want to do, however, you do not need to build the strings yourself. Instead, you can use wrapModule
, as described in the following section.
wrapModule
The Rollup plugin and integrations for other build tools all do roughly the same thing: take a Mustache template and wrap it as a JavaScript module. The common logic is contained in the wrapModule
function, which is the default and only export of the auxiliary module wontache/wrap-module
.
wrapModule
takes the raw template string as the first argument and an object with options as an optional second argument. It returns a string with the JavaScript module code. The following options are available.
precompile
: boolean to indicate whether you want to use precompilation, defaultfalse
.type
: string naming the module type, either'ESM'
(default),'AMD'
or'CommonJS'
.delimiters
: the delimiters that the compiler should start parsing with, default['{{', '}}']
.wontache
: the name used for the default import from Wontache, default'mustache'
.
import wrapModule from 'wontache/wrap-module';
const wrappedModule = wrapModule(template, {precompile: true});
// import mustache from 'wontache';
// export default mustache(...);
Decompilation
Since templates sometimes need to be recompiled, there is a "backdoor" of sorts to reconstruct the original template text from the compiled function. While this is mostly an implementation detail, it might occasionally be useful for debugging purposes.
compiled(null, {decompile: true});
// 'template with {{variable}}'
Credits
The name "Wontache" was suggested by my dear friend Arie de Bruin.