@rhinostone/swig
v2.4.3
Published
A simple, powerful, and extendable templating engine for node.js and browsers, similar to Django, Jinja2, and Twig.
Maintainers
Readme
Swig
Multi-flavor template engine for Node.js and browsers — native Swig syntax (Jinja2/Django-inspired) and Twig syntax via dedicated frontends sharing one IR backend. gina-io/swig started as a maintained continuation of the abandoned paularmstrong/swig (last released 2014) and is now a standalone project. Security and bug fixes ship here.
Part of the Gina ecosystem. This is the built-in template engine for Gina (npm), a Node.js MVC framework with HTTP/2, multi-bundle architecture, and scope-based data isolation.
Swig is a Jinja2/Django-inspired template engine for node.js and browsers. The syntax will feel familiar to Jinja2 and Django users, but Swig is not drop-in compatible with either — porting templates from an existing project requires a handful of changes. See the Migration Guide for the full parity list and workaround patterns.
Coming from Twig? Install @rhinostone/swig-twig instead — a dedicated Twig-syntax frontend with closer parity than working around incompatibilities here.
Workspace packages
| Package | Description | When to use |
| --- | --- | --- |
| @rhinostone/swig | Native Swig syntax (Jinja2/Django-inspired). Drop-in for @rhinostone/[email protected] consumers. | Upgrading from @rhinostone/[email protected], or starting fresh with Swig syntax. |
| @rhinostone/swig-twig | Twig-syntax frontend with closer Twig parity. | Migrating from PHP Twig, or writing new templates in Twig syntax. |
| @rhinostone/swig-core | Shared IR, backend, and runtime primitives. | Building a custom flavor frontend. Otherwise pulled in transitively. |
Each frontend pins the matching @rhinostone/swig-core version exactly during the alpha cycle — the IR is not stable across alpha minors. From 2.0.0 stable onward, frontends and the core release in lockstep.
Features
- Available for node.js and major web browsers.
- Express compatible.
- Object-Oriented template inheritance.
- Apply filters and transformations to output in your templates.
- Hardened against prototype-pollution —
__proto__/constructor/prototypeblocked at parser, tag-side, and IR-emission layers. CVE-2023-25345 fully patched. 9 regression cases undertests/regressions.test.js. - Automatically escapes all variable output (HTML by default; configurable per-call).
- Lots of iteration and conditionals supported.
- Robust without the bloat.
- Extendable and customizable — register custom filters, tags, and loaders per-instance.
Benchmarks
benchmarks/render.js measures sync-render throughput across five workload shapes against Nunjucks.
cd benchmarks && npm install && node render.jsIn production-typical settings (autoescape on), @rhinostone/swig outperforms Nunjucks on iteration-heavy templates by 2–3.5× and ties on simple control flow. See benchmarks/README.md for the methodology, the full result table, and how to reproduce on your own hardware.
Need Help? Have Questions? Comments?
- File an issue at gina-io/swig/issues.
- Swig v0.x → v1.x migration notes — the original upstream wiki has been deleted; see
HISTORY.mdentries around v1.0.0 for the individual breaking changes. For porting from Jinja2 or Django into Swig, see the Migration Guide.
Installation
npm install @rhinostone/swigFor Twig syntax:
npm install @rhinostone/swig-twigDocumentation
User-facing documentation lives in the Gina Docusaurus site under the Swig Template Engine section, maintained in gina-io/docs at docs/swig/. The JSDoc blocks in lib/swig.js, lib/filters.js, lib/tags/, and lib/loaders/ remain the canonical source-of-truth for the public API and are mirrored into the Docusaurus pages.
Basic Example
Template code
<h1>{{ pagename|title }}</h1>
<ul>
{% for author in authors %}
<li{% if loop.first %} class="first"{% endif %}>{{ author }}</li>
{% endfor %}
</ul>node.js code
var swig = require('@rhinostone/swig');
var template = swig.compileFile('/absolute/path/to/template.html');
var output = template({
pagename: 'awesome people',
authors: ['Paul', 'Jim', 'Jane']
});Output
<h1>Awesome People</h1>
<ul>
<li class="first">Paul</li>
<li>Jim</li>
<li>Jane</li>
</ul>For working example see examples/basic.
Migrating from @rhinostone/[email protected]
@rhinostone/[email protected] is drop-in for 1.x consumers — swig.compileFile, swig.renderFile, swig.setFilter, swig.setTag, and the rest of the public API are unchanged. The internal carve into @rhinostone/swig-core is transparent (test gate during the alpha cycle: byte-identical compiled output against the 1.x test suite).
2.0.0 also ships @rhinostone/swig-twig, a sibling Twig-syntax frontend. Switching is opt-in — your existing @rhinostone/swig install keeps working.
Migrating from Jinja2 or Django
Swig is inspired by Jinja2 and Django, not a drop-in replacement. Common pitfalls when porting existing templates:
- No
is/is not/not inoperators — rewrite{% if x is defined %}as{% if x !== undefined %},{% if x not in xs %}as{% if not (x in xs) %}. - Django
forloop.counter→ Swigloop.index(Swig follows Jinja2 loop-variable naming). {{ super() }}/{{ block.super }}→{% parent %}— Swig uses a dedicated tag inside the overriding block.- Django filter args use a colon (
|date:"Y-m-d") — Swig uses parens (|date("Y-m-d")). {% with x=1 %}→{% set x = 1 %}, and no block-form{% set %}…{% endset %}.- No
{% from "f" import x %}— use{% import "f" as ns %}+ns.xinstead. - Method calls require parens — Django auto-invokes
x.get_absolute_url; Swig needsx.get_absolute_url(). - ~25 Jinja2 filters are absent —
default,truncate,tojson,round,int,float,map,select,batch,trim, etc. Register them viaswig.setFilter(name, fn).
Full parity tables and workaround patterns: Migration Guide.
How it works
Swig reads template files and translates them into cached JavaScript functions. The pipeline is: parse → emit IR → lower IR to JS source → new Function(...). At render time, the compiled function runs against a context object to produce the output string.
In 2.x, frontend parsers (native Swig syntax in @rhinostone/swig, Twig syntax in @rhinostone/swig-twig) emit a shared intermediate representation. The backend in @rhinostone/swig-core lowers IR to JS. New flavors plug in at the frontend without touching the runtime.
License
MIT. Copyright (c) 2010-2016 Paul Armstrong and contributors, (c) 2026 Rhinostone. See LICENSE for the full text and AUTHORS for the contributor roster.
