Implemented
ConfigureRender(name, ctx)RenderString(src, ctx)-
include,extends, blocks,super() -
set,if,for,{{ expr }} -
macro,import ... as,call -
raw,verbatim,filter ... endfilter
Multi-language templating with a Go core, CLI, and WASM bridge.
GitHub Pages serves thisdocs/ folder.
Packages: npm (@samuelbines/nunchucks) | Go package (pkg.go.dev) | Go source
VS Code extension:
BishopCoTech.nunchucks-syntax
(search: nunchucks syntax)
npm install @samuelbines/nunchucks
npm link @samuelbines/nunchucks
Nunchucks template
{% extends "layout.njk" %}
{% block body %}
<h2>{{ user.name | title }}</h2>
{% if user.active %}
<p>Status: active</p>
{% endif %}
{% endblock %}
Use the selector to view how to render templates from each language/runtime.
env := nunchucks.Configure(nunchucks.ConfigOptions{Path: "views"})
out, err := env.Render("index.njk", map[string]any{
"user": map[string]any{"name": "sam"},
})
if err != nil {
panic(err)
}
fmt.Println(out)
Go Details
# CLI
go run ./cmd/nunchucks help
go run ./cmd/nunchucks render -views ./views -template index.njk -data '{"title":"Hello"}'
go run ./cmd/nunchucks precompile -views ./views -out ./public -data '{"title":"Hello"}'
# WASM build
cd go
GOOS=js GOARCH=wasm go build -o wasm/nunchucks.wasm ./cmd/nunchucks-wasm
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" wasm/wasm_exec.js
Examples: go run ./examples/basic and
go run ./examples/advanced.
import { configure } from "@samuelbines/nunchucks";
const app = express();
const env = configure({
path: "views",
loader: FileSystemLoader,
dev: true,
watch: true,
devRefresh: true,
});
env.express(app);
import { loadNunchucksWasm } from "./go/wasm/loader.js";
const nc = await loadNunchucksWasm({
wasmURL: "./nunchucks.wasm",
goURL: "./wasm_exec.js",
});
const out = nc.renderFromMap({
template: "index.njk",
files: {
"index.njk": "Hello {{ user.name }}",
},
context: { user: { name: "sam" } },
});
# Python wrapper pattern (current): call nunchucks CLI
import json
import subprocess
cmd = [
"nunchucks", "render",
"-views", "./views",
"-template", "index.njk",
"-data", json.dumps({"user": {"name": "sam"}}),
]
out = subprocess.check_output(cmd, text=True)
print(out)
// Dart wrapper pattern (current): call nunchucks CLI
import 'dart:convert';
import 'dart:io';
final result = await Process.run('nunchucks', [
'render',
'-views', './views',
'-template', 'index.njk',
'-data', jsonEncode({'user': {'name': 'sam'}}),
]);
print(result.stdout);
Go and TS are native engines. JS/Python/Dart can use the Go CLI/WASM bridge today.
Package: github.com/SamuelDBines/nunjucks/go
env := nunchucks.Configure(nunchucks.ConfigOptions{Path: "views"})
out, err := env.Render("index.njk", map[string]any{
"title": "Nunchucks",
"items": []any{"alpha", "beta"},
})
if err != nil {
panic(err)
}
fmt.Println(out)
go run ./cmd/nunchucks help
go run ./cmd/nunchucks render -views ./views -template index.njk -data '{"title":"Hello"}'
go run ./cmd/nunchucks precompile -views ./views -out ./public -data '{"title":"Hello"}'
cd go
go run ./examples/basic
go run ./examples/advanced
# views/layout.njk
<h1>{% block title %}Title{% endblock %}</h1>
<main>{% block body %}{% endblock %}</main>
# views/macros.njk
{% macro badge(text) %}<span class="badge">{{ text | upper }}</span>{% endmacro %}
# views/home.njk
{% extends "layout.njk" %}
{% import "macros.njk" as ui %}
{% block title %}Users{% endblock %}
{% block body %}
{% for u in users %}
{{ ui.badge(u.name) }}
{% endfor %}
{% endblock %}
env := nunchucks.Configure(nunchucks.ConfigOptions{Path: "views"})
out, err := env.Render("home.njk", map[string]any{
"users": []any{
map[string]any{"name": "sam"},
map[string]any{"name": "kai"},
},
})
if err != nil { panic(err) }
{% set active = users | selectattr("active") %}
Active={{ active | length }}
{% for grp in users | groupby("role", "none") %}
{{ grp.grouper }}={{ grp.list | length }}
{% endfor %}
BestScoreKey={{ scores | dictsort("value", false, true) | first | first }}
Intervals={{ range(0, 10, 3) | join(",") }}
out, err := env.Render("report.njk", map[string]any{
"users": []any{
map[string]any{"name":"A","active":true,"role":"Dev"},
map[string]any{"name":"B","active":false,"role":"ops"},
map[string]any{"name":"C","active":true,"role":"dev"},
},
"scores": map[string]any{"x": 4, "y": 7, "z": 2},
})
ConfigureRender(name, ctx)RenderString(src, ctx)include, extends, blocks,
super()
set, if, for,
{{ expr }}
macro, import ... as, call
raw, verbatim,
filter ... endfilter
lower, upperThe Go core can be compiled to WebAssembly and consumed from JS runtimes.
cd go
GOOS=js GOARCH=wasm go build -o wasm/nunchucks.wasm ./cmd/nunchucks-wasm
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" wasm/wasm_exec.js
Then load with go/wasm/loader.js and call:
renderFromMap({ template, files, context })renderString({ source, context })Current Go runtime support:
if, for, setextends, block, include
macro, import ... as, call
raw, verbatim,
filter ... endfilter
{% import "macros.njk" as ui %}
{% set name = "sam" %}
{% if name %}{{ ui.badge(name) }}{% endif %}
Current Go filters include:
abs, batch, capitalize,
center, default
dictsort, dump, escape,
first, float, forceescape
groupby, indent, int,
join, last, length,
list
lower, nl2br, random,
reject, rejectattr, replace
reverse, round, safe,
select, selectattr, slice,
sort
string, striptags, sum,
title, trim, truncate,
upper
urlencode, urlize, wordcount
{{ "NUNCHUCKS" | lower }}
{{ "nunchucks" | upper }}
{% filter upper %}hello {{ user.name }}{% endfilter %}
Note: behavior is close to Nunjucks/Jinja for most filters, with a few edge cases still being tightened.
Use this when you want reusable UI components and nested content.
# base.njk
<!doctype html>
<html>
<body>
<header>{% block header %}Default Header{% endblock %}</header>
<main>{% block content %}{% endblock %}</main>
</body>
</html>
# macros.njk
{% macro panel(kind="info") %}
<section class="panel panel-{{ kind }}">
{{ caller() }}
</section>
{% endmacro %}
# page.njk
{% extends "base.njk" %}
{% import "macros.njk" as ui %}
{% block header %}Account Overview{% endblock %}
{% block content %}
{% call ui.panel("success") %}
Hello {{ user.name | title }}
{% endcall %}
{% endblock %}
env := nunchucks.Configure(nunchucks.ConfigOptions{Path: "views"})
out, err := env.Render("page.njk", map[string]any{
"user": map[string]any{"name": "samuel"},
})
if err != nil { panic(err) }
fmt.Println(out)
Use this when rendering dashboards, reports, and config summaries.
{# users grouped by role (case-insensitive by default) #}
{% for grp in users | groupby("role", "none") %}
Role={{ grp.grouper }} count={{ grp.list | length }}
{% endfor %}
{# active users only #}
Active={{ users | selectattr("active") | length }}
{# reverse sort map by value #}
TopKey={{ scores | dictsort("value", false, true) | first | first }}
{# deterministic ranges from builtin function #}
Steps={{ range(1, 8, 2) | join(",") }}
out, err := env.Render("report.njk", map[string]any{
"users": []any{
map[string]any{"name":"Zed","role":"Dev","active":true},
map[string]any{"name":"Amy","role":"ops","active":false},
map[string]any{"name":"Bob","role":"dev","active":true},
},
"scores": map[string]any{"alpha": 92, "beta": 87, "gamma": 95},
})