npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@amundsan/literal-engine

v0.10.7

Published

Node template engine based on template literals, with no dependencies.

Downloads

14

Readme

literal-engine

Node template engine based on template literals, with no dependencies.

  • No custom syntax, just template literals (or template strings), ${...}
  • Support for include
  • Support for extend
  • Autoescaping by default (locally or globally deactivatable)
  • Builtin template helpers (include, escape)
  • Support adding custom template helpers
  • [TODO] Benchmark

Install

$ npm i @amundsan/literal-engine

Import

import Engine from '@amundsan/literal-engine'

Usage

Templates from strings

All templates in literal-engine must be registered before using them. String-based template are registered with the template(name,str) method, then compiled with render(name, data, extend)

const engine = new Engine()
engine.template('test', 'Hello ${who}')
console.log(engine.render('test', { who: 'world' }))
// => 'Hello world'

engine.template('test2', '${what} ${who}!')
console.log(
    engine.render('test2', { what: 'Hello', who: 'world' }),
    engine.render('test2', { what: 'Hi', who: 'universe' })
)
// => 'Hello world!' 'Hi universe!'

Templates from files

All files-based templates must be loaded with prepare() method, which load them recursively from root folder, and store them in the templates registry, as strings. The name used to store, and to recall when rendering, is the path of the file relative to its root folder, and without its extension (html by default). prepare() is the only promised method in the api.

<!-- page.html -->
<body>
    <h1>${title}</h1>
    <p>${content}</p>
</body>
const engine = new Engine({
    root: 'path/to/templates',
    extension: 'html', // by default, can be omitted
})
await engine.prepare()
console.log(engine.render('page', { title: 'My page', content: 'My content' }))
/*
<body>
    <h1>My page</h1>
    <p>My content</p>
</body>
*/

At any time, you can consult the list of all available templates (loaded from a file or a string) by dumping engine.templates, which is a plain object.

Autoescape

By default, autoescape is active. This means that all variables and expressions passed into ${...} in templates will be automatically escaped, replacing any dangerous characters by HTML entities. This feature can be globally disabled if required, by setting the autoescape constructor option to false.

const engine = new Engine({ autoescape: false })

If this option is globally disabled, you can still perform this task manually, using the "escape" helper in your templates.

<p>${ escape('<div>') }</p>
<!-- <p>&lt;div&gt;</p> -->

If this option is globally enabled, you can still locally disable autoescape by placing an additional dollar sign in front of a variable: $${...}. This is particularly useful when using the include function, or when you want to use an extend.

<p>$${ '<div>' }</p>
<!-- <p><div></p> -->

Helpers

You can make generic functions available in your templates by adding them to the list of helpers. They can also be added from the engine constructor.

const engine = new Engine({
    helpers: {
        oddCase: (str) =>
            str
                .toLowerCase()
                .split('')
                .map((s, i) => (i % 2 == 0 ? s.toUpperCase() : s))
                .join(''),
    },
})
engine.template('content', '<p>${ oddCase(sentence) }</p>')
console.log(
    engine.render('content', { sentence: 'Oh my God! They killed Kenny!' })
)
// <p>Oh mY GoD! tHeY KiLlEd kEnNy!</p>

You can also add a new helper at any time using the helper(name, func) function.

const engine = new Engine()
engine.helper('evenCase', (str) =>
    str
        .toLowerCase()
        .split('')
        .map((s, i) => (i % 2 != 0 ? s.toUpperCase() : s))
        .join('')
)
engine.template('content', '<p>${evenCase(sentence)}</p>')
console.log(
    engine.render('content', { sentence: 'Oh my God! They killed Kenny!' })
)
// <p>oH My gOd! ThEy kIlLeD KeNnY!</p>

Include

include is one of the helpers functions usable inside your templates. As it sounds, it includes a template into another template. It shares its signature with the render method (in fact... it IS the render method, internally) : include(name, data, extend)

NOTE: As autoescape is active by default, it must be deactivated when using the include function, which legitimately injects html code. This can be done using the $${...} syntax.

<!--head.html-->
<head>
    <title>${title} - Blog</title>
</head>
<!-- page.html -->
<html>
    $${ include('head', { title }) }
    <body>
        <h1>${title}</h1>
        <p>${content}</p>
    </body>
</html>
const engine = new Engine({ root: 'path/to/templates' })
await engine.prepare()
console.log(engine.render('page', { title: 'My page', content: 'My content' }))
/*
<html>
    <head>
        <title>My page - Blog</title>
    </head>
    <body>
        <h1>My page</h1>
        <p>My content</p>
    </body>
</html>
*/

Extend

Extending a template is the opposite of including: it "wrap" a template around another one. It can be done only with include function like this :

const data = { title: 'My page', content: 'My content' }
engine.render('base', { ...data, extend: engine.render('page', data) })

But there's a more convenient way of doing this, using the third argument of the render and include functions to specify the template to be extended. Technically, an "extend" template is just a regular template with a special variable ${extend}, where child template will be injected. As with the include function, autoescape must be disabled with the syntax $${extend}.

<!--base.html-->
<body>
    $${extend}
</body>
<!-- page.html -->
<h1>${title}</h1>
<p>${content}</p>
const engine = new Engine({ root: 'path/to/templates' })
await engine.prepare()
const data = { title: 'My page', content: 'My content' }
console.log(engine.render('page', data, 'base'))
/*
<body>
    <h1>My page</h1>
    <p>My content</p>
</body>
*/

Condition

Conditional display of variables can easily be done with a ternary structure.

const engine = new Engine()
engine.template('content', '<p>${ isAlive ? "Kenny" : "Ghost" }</p>')
const result = engine.render('content', { isAlive: true })
// <p>Kenny</p>

For cases that are more complicated or too verbose to be written directly into the template, it is still possible to use a function, added as a helper to the engine.

const engine = new Engine({
    helpers: {
        showKenny: (isAlive) => (isAlive ? 'Kenny' : 'Ghost'),
    },
})
engine.template('content', '<p>${ showKenny(isAlive) }</p>')
console.log(engine.render('content', { isAlive: false }))
// <p>Ghost</p>

Loop

Another important point in using a model engine is the ability to loop. You can do this very simply, once again using one of javascript's native functions, map.

const engine = new Engine()
engine.template('li', "<li>${ alive ? '😐' : '🫥' } ${name}</li>")
engine.template('ul', '<ul>${ list.map((li)=>include("li", { ...li })) }</ul>')
const list = [
    { name: 'Eric Cartman', alive: true },
    { name: 'Kyle Broflovski', alive: true },
    { name: 'Kenny McCormick', alive: false },
    { name: 'Stan Marsh', alive: true },
]
console.log(engine.render('ul', { list }))
/*
<ul>
    <li>😐 Eric Cartman</li>
    <li>😐 Kyle Broflovski</li>
    <li>🫥 Kenny McCormick</li>
    <li>😐 Stan Marsh</li>
</ul>
*/

Debugging

There's an engine constructor option for displaying errors directly in templates during the development phase, the debug option, which is a boolean set to false by default. All templates errors are logged to process.stderr, but it can be more convenient to have them directly in the rendered page.

const engine = new Engine({ debug: true })

You can of course ${console.log(myvar)} in a template to dump myvar in server console, and see what you can do with it.

But it can be sometimes difficult to know exactly what are the variables available in a template or a sub-template, and you can't do a console.log() without an effective variable name. In this case, you can print a special variable, named... ${variables}, which print a sorted and coma's separated list of actual variable names.

Credits

Freely adapted from brilliants template-literals-engine and escape-html