Package detail

micromustache

userpixel239.5kMIT8.0.3

A fast, minimal and secure template engine for JavaScript

template, mustache, string processing, string interpolation

readme

Downloads GitHub stars Known Vulnerabilities GitHub license Version code style: prettier GitHub issues

micromustache

Logo

A secure, fast and lightweight template engine with some handy additions.

⛹ Check out the playground

Think of it as a sweet spot between plain text interpolation and mustache.js; Certainly not as logic-ful as Handlebars! Sometimes a stricter syntax is the right boundary to reduce potential errors and improve performance. This tool has a limited scope that doesn't attempt to solve everybody's use case, and instead do a specific thing well.

  • 🏃 2x-3x faster than MustacheJS (Micromustache is the fastest template engine that doesn't need pre-compilation and still works in CSP environments)
  • 🔒 Secure has limits for variable length, number of interpolations, nesting debth and common Javascript pitfalls (__proto__, constructor, getters/etc). Works in CSP environments (no usage of eval() or new Function()). Published only with 2FA. No regexp.
  • 🎈 Lightweight No dependencies, less than 400 lines of source code, easy to audit, small API surface, easy to pick up
  • 🐁 Small memory footprint sane caching strategy, no memory leak
  • 🏳 No dependencies
  • Bracket notation support a[1]['foo'] accessors (mustache.js/handlebar syntax of a.1.foo is also supported).
  • 🚩 Meaningful errors in case of template syntax errors to make it easy to spot and fix. All functions test their input contracts and throw meaningful errors to improve developer experience (DX).
  • TypeScript types included out of the box and updated with every version of the library
  • 🐇 Works in node (CommonJS) and Browser (UMD) and EcmaScript 6 Modules (ESM)
  • 🛠 Well tested (full test coverage over 120+ tests). Also tested to produce the same results as Mustache.js.
  • 📖 Full JSDoc documentation

If variable interpolation is all you need, micromustache is a drop-in replacement for MustacheJS (see its differences with Mustache.js)

Try it in your browser!

Getting started

Use directly with UNPKG:

import { render } from 'https://unpkg.com/browse/micromustache/dist/micromustache.mjs'
console.log(render('Hello {{name}}!', { name: 'world' }))
// Hello world!

Install:

$ npm i micromustache

Use:

const { render } = require('micromustache')
console.log(render('Hello {{name}}!', { name: 'world' }))
// Hello world!

Why not just use EcmaScript template literals?

Template literals work great when the template and the variables are in the same scope but not so well when the template is in another scope or is not known ahead of time. For example, suppose you had a function like this:

function greet(name) {
  return `Hi ${name}!`
}

After your function became successful and you got rich 🤑 you may decide to dominate the world and expand to new markets which speak other languages. You need to internationalize it. Adding one more language is easy:

function greet(name, lang) {
  // Note the lang parameter that contains a language code
  return lang === 'sv' ? `Hej ${name}!` : `Hi ${name}!`
}

But how about a bunch of them?

function greet(name, lang) {
  switch (lang) {
    case 'sv': return `Hej ${name}!`
    case 'es': return `Hola ${name}!`
    default:
    case 'en': return `Hi ${name}!`
  }
}

That doesn't scale well as you dominate country after country and need to support more languages! Besides, that's just one string! The main problem is that the content (the text) is coupled to the code (the variable interpolation). Template engines help you to move the content out of the function and let something else deal with that concern.

const { render } = require('micromustache')
// A very simplified i18n database
const db = {
  en: {
    greeting: 'Hi {{name}}!',
    // ...
  },
  sv: {
    greeting: 'Hej {{name}}!',
    // ...
  },
  // ...
}

function greet(name, lang) {
  return render(db[lang].greeting, { name } )
}

Now it's better! 😎 All the templates are together and they are easy to update and translate. By default, we use the popular syntax that encloses variable names between double curly braces ({{ and }}) but you can customize micromustache if you prefer something else. Just like template literals, you can of course reference deep nested objects:

const { render } = require('micromustache')
const scope = {
  fruits: [
    { name: 'Apple', color: 'red' },
    { name: 'Banana', color: 'yellow' },
  ]
}
console.log(render('I like {{fruits[1].color}}!', scope))
// I like Bababa!

It worth to note that Mustache and Handlebars don't support fruits[1].color syntax and rather expect you to write it as fruits.1.color.

The real power of micromustache comes from letting you resolve a variable name using your own functions! To pass a resolver function, you can use renderFn() instead of render():

const { renderFn } = require('micromustache')
// Just converts the variable name to upper case
const up = str => str.toUpperCase()

console.log(renderFn('My name is {{Alex}}!', up))
// My name is ALEX!

The resolver gets the scope as its second parameter. If you want to lookup a value, there's a get() function as well:

const { renderFn, get } = require('micromustache')

// Looks up the value and converts it to stars
function star(varName, scope) {
  // varName comes from the template and is 'password' here
  // scope is { password: 'abc' }
  const value = get(scope, varName) // value is 'abc'
  return '*'.repeat(value.length)
}

console.log(renderFn('My password is {{password}}!', star, { password: 'abc' }))
// My password is ***!

If you want to resolve a value asynchronously, we got you covered using the renderFnAsync() instead of renderFn(). For example the following code uses node-fetch to resolve a url.

const { renderFnAsync } = require('micromustache')
const fetch = require('node-fetch')

async function taskTitleFromUrl(url) {
  const response = await fetch(url)
  const obj = await response.json()
  return obj.title
}

console.log(await renderFnAsync('Got {{https://jsonplaceholder.typicode.com/todos/1}}!', fetch))
// Got delectus aut autem!

If you find yourself working on a particular template too often, you can compile() it once and cache the result so the future renders will be much faster. The compiler returns an object with render(), renderFn() and renderFnAsync() methods. The only difference is that they don't get the template and only need a scope:

const { compile } = require('micromustache')
const compiled = compile('Hello {{name}}! I am {{age}} years old!')
console.log(compiled.render({ name: 'world', age: 42 }))
// Hello world! I'm 42
// The methods are bound so you can use the destructed version for brevity
const { render } = compile
console.log(render({ name: 'world', age: 42 }))
// Hello world! I'm 42

If the compiled variable above is garbage collected, the cache is freed (unlike some other template engines that dearly keep hold of the compiled result in their cache which may leads to memory leaks or out of memory errors over longer usage).

Using the options you can do all sorts of fancy stuff. For example, here is an imitation of the C# string interpolation syntax:

const { render } = require('micromustache')
const $ = scope => strings => render(strings[0], scope, { tags: ['{', '}'] })

const name = 'Michael'
console.log($({ name })`Hello {name}!`)
// Hello Michael!

API

On Github pages

Examples

Check out the examples directory. Note that they need you to build the project locally.

FAQ

On wiki

Known issues

On wiki

License

MIT


Made in Sweden 🇸🇪 by @alexewerlof

changelog

Change Log

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

Unreleased

  • Support optional chaining syntax. But the default behaviour of mustache is like we use ?. everywhere we write ..
  • Support comments: {{! ...}} like MustacheJS
  • Reenable tests/ for mustache compatibility and add relevant options
  • Add a string literal tag function (generic render)
  • Add the possibility to process variable names before and after they are resolved using get(). This can allow HTML escaping for example. Also see #50

8.0.0

  • The CommonJS file has changed name: V7=dist/micromustache.js v8=dist/micromustache.cjs. If you just use require('micromustache') it sould work without any change.
  • The depth option is added
  • Updated the dependencies
  • Addressed known security issues

BREAKING CHANGES

  • The scope could be a function as well, but with this release we only accept Object.
  • Previously string characters could be accessed with array-index syntax, but now it is not possible (eg. render('{{str[0]}}', { str: 'Hello' }) will not return 'H' anymore)
  • Drop support for Safari10

7.0.0

BREAKING CHANGES:

  • The CLI is removed
  • Variable names cannot be longer than 1000 characters

6.0.0

  • We no more try to JSON.stringify() arrays and objects. You can use .renderFn() to do that. There's still the get() utility function to help do the lookup.
  • Object bracket accessors are now supported: obj['foo'] (previously only obj.foo worked like Mustache and handlebars)
  • Rewrote the project in TypeScript
  • Custom resolver can also be async (see .renderFnAsync())
  • Compile and rendering is significantly faster than Mustache
  • A change in terminology to better reflect JavaScript terms: What Mustache and the previous version of the lib called view is not called scope.
  • Expose a CommonJS build for the browser limited to ECMAScript 5 features.

BREAKING CHANGES:

  • The biggest change is that if you used compile() in version 5, it returned a function but since version 6, it returns an object that has a render() function
  • The behaviour of the resolver function has changed: In v5 if the resolver threw an error we fell back to the standard .get() functionality but v6 just throws that error in an effort to make debugging easier.
  • We don't use default exports anymore so const render = require('micromustache/render') should be refactored to const { render } = require('micromustache')
  • Now the compiler returns a renderer object with a render() function
  • If you pass a non-string template it'll throw an error
  • If you provide a custom resolver and it throws, we throw and will not swallow that error
  • If one of the nested keys do not exist, we throw and will not swallow that error

5.4.0

  • Modernize the the dependencies and build system

5.1.0 2017-01-08

  • Add command line support

5.0.0 2017-01-08

  • Update readme
  • remove building browser packages (use webpack, browserify or any other modern method to build)
  • Command line interface
  • Add travis build
  • Use linting rules from Schibsted
  • Converted to CommonJS (dropped AMD and UMD in favor of modern build tools)
  • Use yarn
  • Rewrote test with chai.expect instead of chai.assert
  • list of params for resolver is reversed to (varName, view)
  • dropped to_html alias
  • dropped bower support
  • dropped CDN support
  • dropped this feature: If the value is a function, call it passing the name of the variable as the only argument.

2.2.1 - 2016-07-23

  • Mainly modernizing the package after 2 years
  • Added documentation
  • Replaced the old nodeunit test system with mocha/chai
  • Replaced the old gulp build system with pure NPM

2.1.20 - 2014-07-11

  • Replaced makefile with Gulp
  • Adapted the UMD pattern to easily work with AMD, CommonJS or even good old style globals
  • Removed the test case of Object and Array values (they aren't supported in Micromustache).
  • Updated the test runner to use rawgit.com
  • Added this changelog

1.2.1 - 2013-12-18

  • Added new logo
  • Released into NPM