Security News
PyPI Introduces Digital Attestations to Strengthen Python Package Security
PyPI now supports digital attestations, enhancing security and trust by allowing package maintainers to verify the authenticity of Python packages.
micromustache
Advanced tools
A secure, fast and lightweight template engine with some handy additions.
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.
__proto__
, constructor
, getters/etc). Works in CSP environments (no usage of eval()
or new Function()
). Published only with 2FA. No regexp.a[1]['foo']
accessors (mustache.js/handlebar syntax of a.1.foo
is also supported).If variable interpolation is all you need, micromustache is a drop-in replacement for MustacheJS (see its differences with Mustache.js)
Use directly with UNPKG:
import { render } from 'https://unpkg.com/browse/micromustache/dist/esm.js'
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!
render(template, scope, options)
Replaces every {{varName}} inside the template with values from the scope parameter.
template
The template string containing zero or more {{varName}}
as placeholders for looking up values from the scope
parameter.scope?: object
An object containing values for variable names from the the template. If it's omitted, we default to an empty object. Since functions are objects in javascript, the scope
can technically be a function too but it won't be called. It'll be treated as an object and its properties will be used for the lookup.options?: object
see below 👇The template string where its variable names replaced with corresponding values
TypeError
if the template
is not a string, scope
is not an object, or options
is invalidSyntaxError
if the template does not comply with the syntaxrenderFn(template, resolveFn, scope, options)
Same as render but accepts a function that allows you to resolve the variable name to a value as you choose. Tip: you may do some extra processing and use the get()
function underneath but that's up to you.
WARNING: When dealing with user input, always make sure to validate it.
resolveFn: (varName, scope) => any
a function that takes a variable name and resolves it to a value. The value can be a number, string or boolean. If it is not, it'll be "stringified".WARNING: When dealing with user input, always make sure to validate it.
renderFnAsync(template, resolveFnAsync, scope, options)
Same as renderFn()
but expects a resolver function that always returns a promise.
resolveFnAsync: (varName, scope) => Promise<any>
a function that takes a variable name and returns a promise that resolves to a value. The value can be a number, string or boolean. If it is not, it'll be "stringified".A promise that resolves to the final output once all the resolveFnAsync
functions are resolved
compile(template, options)
Compiles a template and returns an object that has the render functions. This drammatically improves the interpolation speed (2x-3x) compared to render()
.
template
same template that is passed to render()
options?: object
see below 👇An object with 3 methods:
render(scope)
same as the render()
function above but without the template
parameterrenderFn(resolveFn, scope)
same as the renderFn()
function above but without the template
parameterrenderFnAsync(resolveFnAsync, scope)
same as the renderFnAsync()
function above but without the template
parameterTypeError
if the template
is not a string or options
is invalid.SyntaxError
if the template does not comply with the syntax.get(scope, varName, propExists)
A useful utility function that is used internally to lookup a variable name as a path to a property in an object.
scope
same as the scope
parameter to the render()
function above.varName: string | string[]
a string like a.b.c
or a['b'].c
. It can also be a path array like ['a', 'b', 'c']
(same as lodash's get()). Tip: the array version is a whole lot faster because we skips parsing it.propExists?: boolean = false
see the meaning of this param under the options
below.Differences with JavaScript:
- No support for keys that include
[
or]
. ex.a['[']
- No support for keys that include
'
or"
. ex.a['"']
foo[bar]
is allowed and treated asfoo['bar']
(this behaviour is similar to how lodashget()
works). But JavaScript treatsbar
as a variable and tries to lookup its value or throws aReferenceError
if there is no variable calledbar
.
The value or undefined
.
If the scope is undefined
or null
the result is always undefined
.
ReferenceError
if it cannot find a value in the specified path and propExists
is set.options
All the functions that can take an option, expect it as an object with these properties:
explicit?: boolean = false
When set to a truthy value, rendering literally puts a 'null'
or 'undefined'
for values that are null
or undefined
. By default it swallows those values to be compatible with Mustache.propsExist?: boolean = false
When set to a truthy value, we throw a ReferenceError
for invalid varNames. Invalid varNames are the ones that do not exist in the scope. By default, invalid varNames will be resolved to an empty string.
If a value does not exist in the scope, two things can happen:
propsExist
is falsy (default), the value will be resolved to an empty stringpropsExist
is truthy, a ReferenceError
will be thrownvalidateVarNames?: boolean = false
When set to a truthy value, validates the variable namestags?: string[2] = ['{{', '}}']
The string symbols that mark the opening and closing of a variable name in the template.Full API docs (beta)
Made in Sweden 🇸🇪 by @alexewerlof
FAQs
A fast, minimal and secure template engine for JavaScript
The npm package micromustache receives a total of 31,433 weekly downloads. As such, micromustache popularity was classified as popular.
We found that micromustache demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
PyPI now supports digital attestations, enhancing security and trust by allowing package maintainers to verify the authenticity of Python packages.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.