Skip to content

gina-io/swig

Repository files navigation

Swig

CI NPM version NPM Downloads Socket Badge

Multi-flavor template engine for Node.js and browsers — native Swig syntax (Jinja2/Django-inspired), Twig syntax, Python Jinja2 syntax, and Django Template Language 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.

Coming from Python Jinja2? Install @rhinostone/swig-jinja2 — a dedicated Jinja2-syntax frontend (near-subset) with closer parity than porting to native Swig syntax here.

Coming from Django? Install @rhinostone/swig-django — a dedicated Django Template Language frontend that renders real Django templates, with far closer parity than native Swig syntax here.

Workspace packages

Package Description When to use
@rhinostone/swig Native Swig syntax (Jinja2/Django-inspired). Drop-in for @rhinostone/swig@1.x consumers. Upgrading from @rhinostone/swig@1.x, 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-jinja2 Python Jinja2-syntax frontend (near-subset). Migrating from Python Jinja2, or writing new templates in Jinja2 syntax.
@rhinostone/swig-django Django Template Language frontend (real DTL). Migrating from Django, or writing new templates in Django 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 (no caret, no tilde) — frontends and the core release in lockstep on every cut.

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 / prototype blocked at parser, tag-side, and IR-emission layers. CVE-2023-25345 fully patched. 11 CVE regression cases under tests/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.js

In 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.md entries 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/swig

For Twig syntax:

npm install @rhinostone/swig-twig

For Python Jinja2 syntax:

npm install @rhinostone/swig-jinja2

For Django syntax:

npm install @rhinostone/swig-django

Documentation

User-facing documentation lives in the Gina Docusaurus site under the Swig Template Engine section, maintained in gina-io/docs at docs/templating/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/swig@1.x

@rhinostone/swig@2.x is drop-in for 1.x consumersswig.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; 2.5.0 adds @rhinostone/swig-jinja2 for Python Jinja2 syntax; and 2.7.0 adds @rhinostone/swig-django for Django Template Language syntax. 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 in operators — rewrite {% if x is defined %} as {% if x !== undefined %}, {% if x not in xs %} as {% if not (x in xs) %}.
  • Django forloop.counter → Swig loop.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.x instead.
  • Method calls require parens — Django auto-invokes x.get_absolute_url; Swig needs x.get_absolute_url().
  • ~25 Jinja2 filters are absentdefault, truncate, tojson, round, int, float, map, select, batch, trim, etc. Register them via swig.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, Python Jinja2 syntax in @rhinostone/swig-jinja2, Django Template Language syntax in @rhinostone/swig-django) 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.

About

Take a swig of the best template engine for JavaScript.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages