npm.io
1.0.2 • Published 10 months ago

adequate-little-templates

Licence
MIT
Version
1.0.2
Deps
0
Size
38 kB
Vulns
0
Weekly
0

Adequate Little Templates

npm version

A minimal, CSP-safe templating language. Unlike the micro templates I could find, this does not compile to a JavaScript function requiring eval or equivalent, so it will run in more contexts and be safer for untrusted input.

Currently: ~3KB gzipped.

Quick Start

npm i adequate-little-templates
import { render, compile, registerFunction } from 'adequate-little-templates';

// One-shot render
render('Hello {{ name }}!', { name: 'World' });
// -> "Hello World!"

// Compile for reuse
const template = compile('Hello {{ name }}!');
template({ name: 'World' }); // -> "Hello World!"
template({ name: 'Again' }); // -> "Hello Again!"

Syntax

Variable Interpolation

Output values, HTML escaped:

{{ title }}
{{ meta.author }}
{{ deeply.nested.value }}
Raw Output

Output without HTML escaping (for trusted HTML like highlighted excerpts):

{{+ excerpt +}}
{{+ meta.description +}}
Pipes and Functions

Transform values with pipes:

{{ title | lowercase }}
{{ title | uppercase }}
{{ title | truncate(50) }}
{{ title | truncate(50, "…") }}
{{ name | lowercase | truncate(20) }}

Or use function call syntax:

{{ truncate(title, 50) }}
{{ replace(title, "foo", "bar") }}

Pipes are sugar for function calls. These are equivalent:

{{ title | truncate(50) }}
{{ truncate(title, 50) }}
Conditionals
{{#if author}}
  <span>By {{ author }}</span>
{{/if}}

{{#if image}}
  <img src="{{ image }}">
{{:else}}
  <div class="placeholder"></div>
{{/if}}

{{#if eq(status, "published")}}
  <span class="green">Published</span>
{{:else if eq(status, "draft")}}
  <span class="yellow">Draft</span>
{{:else}}
  <span class="gray">Unknown</span>
{{/if}}
Iteration
{{#each items as item}}
  <li>{{ item.name }}</li>
{{/each}}

{{#each items as item, index}}
  <li>{{ index }}: {{ item.name }}</li>
{{/each}}

{{#each items as item}}
  <li>{{ item }}</li>
{{:else}}
  <li>No items found</li>
{{/each}}

{{#each items | limit(3) as item}}
  <li>{{ item.name }}</li>
{{/each}}
Escaping Delimiters

Output literal {{ with a backslash:

\{{ this is not a template tag }}

Built-in Functions

Comparison
Function Description
eq(a, b) Equal
ne(a, b) Not equal
gt(a, b) Greater than
lt(a, b) Less than
gte(a, b) Greater than or equal
lte(a, b) Less than or equal
Boolean
Function Description
and(a, b, ...) Logical AND
or(a, b, ...) Logical OR
not(a) Logical NOT
String
Function Description
lowercase(s) Convert to lowercase
uppercase(s) Convert to uppercase
trim(s) Remove leading/trailing whitespace
truncate(s, n) Truncate to n chars with "..."
truncate(s, n, suffix) Truncate with custom suffix
replace(s, find, rep) Replace all occurrences
Array
Function Description
limit(arr, n) Take first n items
first(arr) First item
last(arr) Last item
length(arr) Array (or string) length
join(arr, sep) Join with separator
Misc
Function Description
default(val, fallback) Use fallback if val is falsy
safeUrl(url) Sanitize URL, blocking dangerous protocols
URL Sanitization

Use safeUrl to protect against javascript: and other dangerous URL schemes when rendering user-provided URLs:

<a href="{{ meta.url | safeUrl }}">{{ title }}</a>

Allowed:

  • Relative URLs: /path, ./path, ../path, ?query, #hash
  • HTTP(S): https://example.com
  • FTP: ftp://example.com/file.txt
  • Email: mailto:user@example.com
  • Phone: tel:+1234567890

Blocked (returns empty string):

  • javascript:alert('xss')
  • data:text/html,...
  • vbscript:...
  • Any other protocol

Custom Functions

Register your own functions:

import { registerFunction, render } from 'adequate-little-templates';

registerFunction('double', (n) => n * 2);
registerFunction('formatDate', (ts) => new Date(ts).toLocaleDateString());
registerFunction('pluralize', (count, singular, plural) =>
  count === 1 ? singular : plural
);

render('{{ double(5) }}', {});
// -> "10"

render('{{ count }} {{ pluralize(count, "item", "items") }}', { count: 3 });
// -> "3 items"

// Works with pipes too
render('{{ num | double }}', { num: 21 });
// -> "42"

Truthiness

The following values are falsy:

  • null
  • undefined
  • false
  • 0
  • "" (empty string)
  • NaN
  • [] (empty array)
  • {} (empty object)

Everything else is truthy.

Error Handling

Errors render inline for easy debugging:

[Error: use #each for arrays]
[Error: cannot render object]
[Error: unknown foo()]
[Error: #each needs array]
[Error: join() needs 2 args]

API

Functions
render(template: string, data: TemplateData): string

Parse and render a template in one step.

compile(template: string): Template

Parse a template and return a reusable render function. Use this when rendering the same template multiple times.

registerFunction(name: string, fn: CustomFn): void

Register a custom function for use in templates.

TypeScript

If you need explicit type annotations, all types are exported:

import { render, TemplateData, Value, Template, CustomFn } from 'adequate-little-templates';

const data: TemplateData = { title: 'Hello', count: 42 };
const myFn: CustomFn = (a, b) => Number(a) + Number(b);

Keywords