micromustache

repository·master·Indexed 42 days ago

https://github.com/alexewerlof/micromustache

A fast, minimal, and secure template engine for JavaScript (version 8.0.3). It serves as a middle ground between simple text interpolation and full-featured engines like Handlebars, operating without eval() for CSP compatibility. It supports dot and bracket notation for object access and provides APIs for standard rendering via render(), custom resolution via renderFn() and renderFnAsync(), and performance optimization through pre-compilation with compile().

Tokens
5.9K
Snippets
24
Records
27
Agent score
53%

What's inside micromustache

  1. Understand micromustache terminology and lifecycle

    master

    To use micromustache effectively, it is helpful to understand its core terminology and the high-level rendering lifecycle:

    Core Concepts

    • Path: A string in a template used to refer to a value (e.g., person.name).
    • Ref: An array derived from a Path used for internal lookups (e.g., ['person', 'name']).
    • Scope: The object used to resolve values (referred to as a view in Mustache.JS).
    • Value: The actual data used to interpolate a path.
    • Tags: The delimiters used to mark paths in a template. The default tags are {{ and }}.

    Internal Processes

    • Parsing: Converting a Path into a Ref.
    • Resolving: Looking up a Value for a Path using either the Scope or a custom resolver function.
    • Tokenization: Breaking a template into constant strings and Paths.
    • Compiling: The process of tokenizing a template to create a Renderer object.
    • Rendering: The process of interpolating Paths in a template with their resolved values.
    • Stringification: The final step (also called generator) of combining resolved values into the final string result.
    • Renderer: An object containing a tokenized template with methods for rendering.

    High-Level Rendering Flow

    1. render() └── 2. compile() └── 3. tokenize() └── 4. resolve() └── 5. get() or resolveFn() └── 6. stringify()
    1. render()
       |__ 2. compile()
              |__ 3. tokenize()
                     |__ 4. resolve()
                            |__ 5. get() or resolveFn()
                                   |__ 6. stringify()
  2. Use micromustache in the browser via UNPKG

    master

    For quick browser-side usage without a build step, you can import the ESM module directly from UNPKG.

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

    master

    The playground is a simple jQuery-slide application used to experiment with micromustache. To run it on your local machine, you must first build the TypeScript source and then open the HTML entry point in a browser.

    1. Build the micromustache project using the provided TypeScript build script.
    2. Open playground/index.html in a web browser (e.g., Google Chrome).
    npm run build:ts
    google-chrome playground/index.html
  4. Use ResolveFn and ResolveFnAsync for custom value resolution

    master

    Instead of relying solely on a data scope object, you can provide a custom resolver function to determine the value for every path found in the template.

    • ResolveFn: A synchronous callback used with .renderFn(). It receives the path string and the current scope.
    • ResolveFnAsync: An asynchronous callback used with .renderFnAsync(). It returns a Promise that resolves to the value to be interpolated.

    Both function types follow this signature: (path: string, scope?: Scope) => any | Promise<any>

    Example: A template Hi {{a.b.c}}! will pass 'a.b.c' as the path argument to your resolver.

    // Synchronous resolver
    const resolveFn: ResolveFn = (path, scope) => {
      return path === 'a.b.c' ? 'custom-value' : 'default';
    };
    
    // Asynchronous resolver
    const resolveFnAsync: ResolveFnAsync = async (path, scope) => {
      const val = await fetchValueFromDb(path);
      return val;
    };
  5. Customize template tags

    master

    You can change the default double curly brace {{ }} syntax by passing a tags array in the options object (the third argument to render).

    const { render } = require('micromustache')
    
    // Imitating C# style interpolation {name}
    const scope = { name: 'Michael' }
    console.log(render('Hello {name}!', scope, { tags: ['{', '}'] }))
    // Hello Michael!
  6. Resolve paths asynchronously with renderFnAsync()

    master

    If your path resolution requires asynchronous operations (like fetching data from an API), use renderFnAsync(). This function returns a Promise.

    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
    }
    
    // The resolver function is passed as the second argument
    console.log(await renderFnAsync('Got {{https://jsonplaceholder.typicode.com/todos/1}}!', taskTitleFromUrl))
    // Got delectus aut autem!
  7. Render templates with render()

    master

    The render function performs standard variable interpolation. It supports both Mustache-style dot notation (a.b.c) and bracket notation (a[1]['foo']) for accessing nested object properties.

    const { render } = require('micromustache')
    
    // Basic interpolation
    console.log(render('Hello {{name}}!', { name: 'world' }))
    // Hello world!
    
    // Bracket notation support
    const scope = {
      fruits: [
        { name: 'Apple', color: 'red' },
        { name: 'Banana', color: 'yellow' },
      ]
    }
    console.log(render('I like {{fruits[1].color}}!', scope))
    // I like yellow!
  8. Resolve paths with custom functions using renderFn()

    master

    Use renderFn() instead of render() when you want to provide a custom resolver function. The resolver function receives the path from the template as its first argument and the scope as its second argument. This allows you to transform values or perform custom lookups during the rendering process.

    const { renderFn, get } = require('micromustache')
    
    // Example 1: Transforming values (e.g., uppercase)
    const up = str => str.toUpperCase()
    console.log(renderFn('My name is {{Alex}}!', up))
    // My name is ALEX!
    
    // Example 2: Using get() for custom lookups
    function star(path, scope) {
      const value = get(scope, path)
      return '*'.repeat(value.length)
    }
    console.log(renderFn('My password is {{password}}!', star, { password: 'abc' }))
    // My password is ***! 
  9. Pre-compile templates with compile() for performance

    master

    If you need to render the same template multiple times, use compile() to create a compiled template object. This object contains render(), renderFn(), and renderFnAsync() methods that are already bound to the template, so you only need to pass the scope. Compiled templates are cached and will be garbage collected along with the compiled object, preventing memory leaks.

    const { compile } = require('micromustache')
    
    const compiled = compile('Hello {{name}}! I am {{age}} years old!')
    
    // Standard render
    console.log(compiled.render({ name: 'world', age: 42 }))
    // Hello world! I am 42 years old!
    
    // You can also destructure the methods
    const { render } = compile
    console.log(render({ name: 'world', age: 42 }))
  10. Configure tokenize() with TokenizeOptions

    master

    You can customize the tokenization behavior by passing a TokenizeOptions object to the tokenize function.

    Options:

    • maxPathLen (number, optional): The maximum allowed length for a path. This prevents the parser from scanning too far if a closing tag is missing. Defaults to 1000. Must be a positive number.
    • tags (tuple of two strings, optional): An array of exactly two distinct strings representing the [openTag, closeTag]. Defaults to ['{{', '}}'].

    Tag Constraints:

    • The two strings must be non-empty.
    • They must be distinct.
    • One cannot contain the other (e.g., ['{{', '{'] is invalid).
    import { tokenize } from 'micromustache'
    
    const options = {
      tags: ['[[', ']]'],
      maxPathLen: 50
    }
    
    const tokens = tokenize('Value: [[ user.id ]]', options)
    // tokens.strings: ['Value: ']
    // tokens.paths: ['user.id']
  11. Configure RendererOptions

    master

    When creating a Renderer (typically via the .compile() method), you can pass a RendererOptions object to customize how values are interpolated and how paths are handled.

    • explicit (boolean): If set to a truthy value, the renderer will literally output 'null' or 'undefined' as strings when it encounters null or undefined values. By default, these values are swallowed (omitted) to maintain compatibility with Mustache behavior.
    • validatePath (boolean): If set to a truthy value, the renderer validates the paths during construction. This will throw an error if any path in the template cannot be parsed correctly.
    const options: RendererOptions = {
      explicit: true,
      validatePath: true
    };