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 🙏

© 2025 – Pkg Stats / Ryan Hefner

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 the b-or operator 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:

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 scoped attribute).

Equivalents:

use

Equivalents:

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:

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:

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 %}

source()

Filters

batch(number)

default(value)