sontag
v0.2.0
Published
A just-enough template language.
Readme
Sontag
Note: Sontag is currently a work-in-progress, check back soon!
A just-enough template language in the vein of Twig, Jinja, Nunjucks, and Vento. If you’re familiar with any of these, you’ll feel right at home with Sontag.
A note on security
Sontag is basically a JavaScript runtime, primarily meant to be used in static site generators with your own templates and your own content. Do not use Sontag with untrusted templates and do not populate your templates with untrusted content, which is akin to running untrusted scripts on the machine or in the browser.
See SECURITY.md for more details.
Installation
npm install sontag Usage
import Sontag from 'sontag';
const env = new Sontag('./templates', {});
const result = await env.renderString('Hello, {{ name }}!', {
name: 'Dan'
}); // => Hello, Dan!API
env.render(template, context = {})
Render a template with the given context, asynchronously.
const result = await env.render('index.son', {
content: 'My content'
});The template path is relative to the cwd defined when initializing the environment. You can also pass an array of templates, and the first found will be rendered.
const result = await env.render(
['post-special.son', 'post.son', 'index.son'],
{
content: 'My content'
}
)env.renderString(str, context = {})
Render a template string asynchronously.
const result = await env.render(
"Content: {{ content }}",
{
content: 'My content'
}
);env.addFilter(name, filter)
Add a custom filter to the Sontag environment. The filter function will receive the arguments passed to it in templates.
function myFilter(arg1, arg2, ...args) {
this // refers to the Sontag environment
}The filter function can optionally be asynchronous (using the async keyword in front of the function declaration).
env.addTag(constructor)
Add a custom tag to the Sontag environment. The tag needs to inherit the types.Tag class. There are a few relevant properties, described below:
import { types } from 'sontag';
class MyTag extends types.Tag {
// Which tags can be used in templates.
// In the example below, you'd use {% mytag %} ... {% endmytag %}
static tagNames = ['mytag'];
// Whether the tag is self-closing or paired.
// This aspect of the tag can also be determined
// at runtime, if the class implements the singular()
// getter function as shown below.
static singular = false;
// Some tags work both as self-closing or paired,
// depending on the signature used in templates.
// Use this getter to decide at run-time whether the
// tag is self-closing.
singular() {
if (some_condition) {
return true;
} else {
return false;
}
}
}
env.addTag(MyTag);Templates
- Expressions are marked with
{{ ... }} - Tags are marked with
{% ... %} - Comments are marked with
{# ... #}
Everything else is plain text.
Expressions
All the usual JavaScript operators are available inside tags and expressions. Alternative operators are implemented for compatibility with other templating languages, so you can use these if you prefer:
Operator | JavaScript equivalent
-------- | ---------------------
a and b | a && b
a or b | a || b
not a | !a
x b-and y | x & y
x b-or y | x | y
x b-xor y | x ^ y
a // b | Math.floor(a / b)
a starts with b | a.startsWith(b)
a ends with b | a.endsWith(b)
a matches /regex/ | a.match(/regex/)
a in b | b.includes(a)
a..b | [a, a+1, ..., b]
The | operator is used to pipe values through filters. Filters are functions registered on the Sontag environment which you pipe rather than invoke. You can think of something like {{ post.title | capitalize | pick(10) }} as being equivalent to {{ pick(10, capitalize(post.title)) }}.
Note: Since the
|operator is reserved for filters, you’ll need to use theb-oroperator whenever you need the bitwise OR operator.
Tags
apply
Aliases: filter.
Equivalents:
call
Equivalents:
embed
Include another template inside the current template (like include), but override any of its blocks (like extends):
{% embed 'components/note.son' %}
{% block content %}
The note’s content
{% endblock %}
{% endembed %}If you have a single block defined in the template you’re including, you can also skip the block tag and write directly:
{% embed 'components/note.son' %}
The note’s content
{% endembed %}You can also take advantage of the short block declaration:
{% embed 'components/note.son' %}
{% block content "The note’s content" %}
{% endembed %}By default the included template has access to the outer context. You can limit this by using the only keyword:
{% embed 'components/note.son' only %}
{% block content "The note’s content" %}
{% endembed %}You can pass additional content with the with keyword:
{% embed 'components/note.son' with { post: posts[0] } %}
The block’s content
{% endembed %}Equivalents:
- Twig:
embed
extends
Extend another template by overriding any block it defines:
templates/base.son
<!doctype html>
<html>
<head>
<title>My Website</title>
</head>
<body>
{% block content %}
<!-- no content by default -->
{% endblock %}
</body>
</html>templates/my-page.son
{% extends 'base.son' %}
{% block content %}
My page content
{% endblock %}Inside a block, you can use the parent() function to get the original content of the block, as defined in the template we’re inheriting.
The extends tag, if present, needs to be the first tag in the template. Any content not included in a block will not get rendered.
Equivalents:
block
Open question: Does the block use the context in which it’s defined, or the context where it’s imported? (See also the
scopedattribute).
Equivalents:
use
Equivalents:
- Twig:
use
for
{% for post in posts %}
<article>
<h1>{{ post.title }}</h1>
{{ post.content }}
</article>
{% endfor %}Equivalents:
if
Equivalents:
include
Include another template inside the current template.
{% include 'components/header.son' %}
Main content goes here
{% include 'components/footer.son' %}The template name can be any expression:
{% include 'components/note-' + post.type + '.son' %}See also:
- the
includefunction - the
embedtag
Equivalents:
macro
Equivalents:
Twig: macro
Jinja: macro
Nunjucks: macro
import / from
Equivalents:
raw
Alias: verbatim
Equivalents:
set
Assigns a value to a variable:
{% set month = "August" %}It also has a paired version, which assigns the content of the tag to the variable name:
{% set month %}August{% endset %}Equivalents:
with
Declare a new variable scope.
By default we have access to the outer scope inside the with tag, but we can limit that by using the only keyword:
{% with { title: "Hello" } only %}
The title is: {{ title }}
{% endwith %}Equivalents:
- Twig:
with
Functions
block()
dump(object)
Output the stringified JSON of an object in the template, to inspect and debug.
{{ dump(post.title) }}include(template, context = {}, with_context = true, ignore_missing = false)
The function equivalent of the include tag.
parent()
Alias: super() (for compatibility with Nunjucks)
Inside a block tag, outputs the content of the block as defined in the template we’re referencing in the extends or embed tag.
{% extends "base.son" %}
{% block start %}
{{ parent() }}
Extra content
{% endblock %}