@technomoron/unyuck
v1.0.6
Published
**Unyuck** is a drop-in preprocessor for Nunjucks templates. It resolves `extends`, `include`, and `block` directives at build time, producing a single flattened template plus an optional asset manifest. Instead of shipping a full template tree and a runt
Readme
Unyuck
Unyuck is a drop-in preprocessor for Nunjucks templates. It resolves
extends, include, and block directives at build time, producing a single
flattened template plus an optional asset manifest. Instead of shipping a full
template tree and a runtime loader, you can deploy just one resolved file — as
plain text or stored directly in a database.
Nunjucks is arguably one of the best template engines for text processing, flow control, variable expansion, and extensibility. But its loader model can be restrictive: it has no option to resolve and flatten templates into a single merged text representation, only to compile them into an AST. This is often impractical — or even impossible — when transferring or storing complex templates in databases or across networks. By flattening ahead of time, Unyuck removes the complexity of loading a full, chunked template when plain text output is the most practical option.
Unyuck is not a runtime replacement for Nunjucks. It is a build-time companion that preprocesses templates into deployable artifacts, leaving rendering, variable substitution, and logic evaluation to the standard Nunjucks runtime.
Why this matters:
- Synchronous and portable – Works without async I/O or filesystem access, so it’s safe for serverless functions, databases, or edge runtimes.
- Single-file deployment – Simplifies packaging and transfer; no need to bundle the entire directory tree.
- Performance – Eliminates runtime lookups for includes/extends; the result is pre-resolved.
- Security – Guards against directory traversal outside your template root if path access is a concern.
- Asset handling – Collects
asset("...")references into a manifest for inlining, CID attachments, or CDN URLs, with hooks for custom URL formatting. - Easy preprocess - Make text-based preprocessing possible on the entire merged template. No need to traverse the Nunjucks AST.
Unyuck preserves Nunjucks-compatible whitespace trimming ({% %} vs {%- -%}),
strips out template comments, and ensures the compiled result matches what
Nunjucks would render — just lighter, safer, and easier to deploy.
Repository
Installation
npm install unyuckUnyuck ships with both ES modules and CommonJS builds plus TypeScript declarations. Node.js 18+ is recommended.
Quick Start
ES modules
import { flatten, flattenWithAssets, Unyuck } from "unyuck";
const basePath = new URL("./templates", import.meta.url).pathname;
const baseUrl = "https://cdn.example.com/email";
// Flatten to a single HTML string
const html = flatten({ basePath, baseUrl, template: "welcome.njk" });
// Flatten and collect assets
const { html: merged, assets } = flattenWithAssets({
basePath,
baseUrl,
template: "parts/newsletter.njk",
});
// Inspect the AST
const processor = new Unyuck({ basePath, baseUrl });
const ast = processor.toAst("layout.njk");
const astWithAssets = processor.toAst("layout.njk", { withAssets: true });CommonJS
const { flattenWithAssets, Unyuck } = require("unyuck");
const path = require("path");
const basePath = path.join(__dirname, "templates");
const baseUrl = "https://cdn.example.com/email";
const { html, assets } = flattenWithAssets({
basePath,
baseUrl,
template: "email.njk",
});
const processor = new Unyuck(basePath, baseUrl);
const ast = processor.toAst("email.njk");Template Rules
- Template names are resolved relative to
basePath. - Relative jumps (
../partials/header.njk) are allowed but cannot escape the base directory. {%- -%}trimming follows Nunjucks semantics for includes, extends, and blocks.- Template comments (
{# … #}) are stripped before processing. - Asset references rely on
asset("relative.png", inline?):- Paths are resolved by walking up from the template directory looking for the
nearest
assets/folder. - Without
inline, the path expands tobaseUrl + urlPath, whereurlPathdefaults to the filename without theassets/prefix (noAssetUrl: true). SetnoAssetUrl: falseto keep the full relative path. - Provide an
assetFormatterto override the final string; its context includes{ baseUrl, filename, path, urlPath }. - With
inline(truthy second arg) the tag rewrites tocid:<filename>and the asset is still tracked. - Absolute remote URLs (
http://,https://) are left unchanged and skipped in the manifest.
- Paths are resolved by walking up from the template directory looking for the
nearest
API Reference (cheat sheet)
import {
Unyuck,
flatten,
flattenNoAssets,
flattenWithAssets,
parseTemplate,
} from "unyuck";Class: Unyuck
new Unyuck(basePath: string, baseUrl?: string, collectAssets?: boolean);
new Unyuck({
basePath,
baseUrl?,
collectAssets?,
assetFormatter?,
noAssetUrl?,
});basePath: template root.baseUrl(optional): required when assets are being collected unless anassetFormatteris supplied.collectAssets: default asset manifest collection behaviour.assetFormatter: callback receiving{ baseUrl, filename, path, urlPath }for custom URLs.noAssetUrl: drop the leadingassets/segment from generated URLs (defaults totrue).
Methods
flatten(template, { withAssets? })→string | { html, assets }(primary entry point)flattenWithAssets(template)→{ html, assets }Flatten and process assets, simple modeflattenNoAssets(template)→stringFlatten, don't process assets, simple mode
Helpers
toAst(template, { withAssets? })→BlockNode | { ast, assets }parse(template)→BlockNode(AST only, no assets)
Types: AssetFile, BlockNode, TemplateRequest.
License
MIT - Copyright (c) 2025 Bjørn Erik Jacobsen / Technomoron
