Note: Sontag is currently a work-in-progress, check back soon!
A just-enough template language in the vein of Twig, Liquid, Jinja, and Nunjucks. If you're familiar with any of these, you'll feel right at home with Sontag.
Just to get this out of the way: Sontag is written with static website generators in mind. The ones where you write the content and the templates yourself and the website gets built into static HTML files for other people to read and enjoy.
It does not include, at least for the time being, precautions such as autoescaping, nor protection against XSS attacks, nor a sandboxed environment. Do not use it to generate dynamic output based on user-provided templates or content.
Now, onwards to the good stuff.
# with npm
npm install sontag
# with yarn
yarn add sontag
const Sontag = require('sontag');
const env = new Sontag('./templates', {});
let result = await env.renderString('Hello, {{ name }}!', {
name: 'Dan'
}); // => Hello, Dan!
Render a template with the given context
, asynchronously.
let 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.
let result = await env.render(
['post-special.son', 'post.son', 'index.son'],
{
content: 'My content'
}
)
Render a template string asynchronously.
let result = await env.render(
"Content: {{ content }}",
{
content: 'My content'
}
);
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).
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.
get singular() {
if (some_condition) {
return true;
} else {
return false;
}
}
}
env.addTag(MyTag);
- Expressions are marked with
{{ ... }}
- Tags are marked with
{% ... %}
- Comments are marked with
{# ... #}
Everything else is plain text.
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 |
not a |
!a |
x b-and y |
x & y |
x b-or y |
`x |
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-or
operator whenever you need the bitwise OR operator.
Alias: filter
(For compatibility with Nunjucks)
Open question: Does the block use the context in which it's defined, or the context where it's imported? (See also the
scoped
attribute).
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 %}
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.
{% for post in posts %}
<article>
<h1>{{ post.title }}</h1>
{{ post.content }}
</article>
{% endfor %}
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
include
function - the
embed
tag
Alias: verbatim
(for compatibility with Twig)
Alias: capture
, assign
(for compatibility with Liquid)
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 %}
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 %}
Output the stringified JSON of an object in the template, to inspect and debug.
{{ dump(post.title) }}
The function equivalent of the include
tag.
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 %}