A runtime-agnostic, zero-dependency Jinja2 like template engine for JavaScript. Perfect for edge environments, Cloudflare Workers, browsers, and server-side runtimes.
- π Zero Dependencies - No external dependencies, just pure JavaScript
- π Runtime Agnostic - Works everywhere: browsers, Node.js, Cloudflare Workers, Deno, etc.
- π― Jinja2 Compatible - Familiar Jinja2 syntax and semantics
- π‘οΈ Built-in Security - HTML escaping by default, optional safe mode
- π§ Extensible - Custom filters and functions support
- β‘ Lightweight - Minimal footprint
npm install un-jinja
# or
pnpm add un-jinja
# or
yarn add un-jinjaimport { render } from 'un-jinja'
const template = 'Hello {{ name }}!'
const context = { name: 'World' }
const result = render(template, context)
console.log(result) // Output: Hello World!// Simple variables
render('{{ greeting }}', { greeting: 'Hello' })
// Nested paths
render('{{ user.profile.name }}', { user: { profile: { name: 'John' } } })
// Array access
render('{{ items.0 }}', { items: ['first', 'second'] })// Built-in filters
render('{{ text | upper }}', { text: 'hello' }) // HELLO
render('{{ items | join(", ") }}', { items: ['a', 'b'] }) // a, b
render('{{ count | default(0) }}', { count: null }) // 0
// Math filters
render('{{ 5 | add(3) }}', {}) // 8
render('{{ 10 | sub(2) }}', {}) // 8render('{% if show %}Visible{% endif %}', { show: true })
render('{% if count > 0 %}Items{% else %}Empty{% endif %}', { count: 5 })
render('{% if type == "admin" %}Admin{% elif type == "user" %}User{% else %}Guest{% endif %}', { type: 'user' })const template = `
{% for item in items %}
{{ loop.index }}: {{ item }}
{% endfor %}
`
render(template, { items: ['a', 'b', 'c'] })
// Loop context variables available:
// - loop.index (1-based)
// - loop.index0 (0-based)
// - loop.first (boolean)
// - loop.last (boolean)
// - loop.length (total items)const customFilters = {
multiply: (val, factor) => val * factor,
greet: name => `Hello, ${name}!`
}
render('{{ 5 | multiply(3) }}', {}, customFilters) // 15
render('{{ "Alice" | greet }}', {}, customFilters) // Hello, Alice!// Default: auto-escape
render('{{ html }}', { html: '<script>alert("xss")</script>' })
// Output: <script>alert("xss")</script>
// Marked as safe
render('{{ html | safe }}', { html: '<strong>Bold</strong>' })
// Output: <strong>Bold</strong>- Text:
upper,lower,capitalize,title,trim,striptags - Collections:
join,first,last,length,reverse,sort,groupby - Defaults:
default - Math:
add,sub,mul,div - Encoding:
urlencode,tojson,xmlattr - Formatting:
filesizeformat
import { render } from 'un-jinja'
// Nested variables
render('<h1>Hello {{ user.name }}</h1>', { user: { name: 'Edge' } })
// Conditionals
render('{% if show %}Visible{% else %}Hidden{% endif %}', { show: true })
// Loop with context
const template = `<ul>{% for user in users %}<li>{{ loop.index }}: {{ user.name }}{% if loop.last %} (End){% endif %}</li>{% endfor %}</ul>`
render(template, { users: [{ name: 'Alice' }, { name: 'Bob' }] })
// Custom filters
const customFilters = {
currency: (val, symbol = 'β¬', digits = 2) => `${symbol}${Number(val).toFixed(digits)}`
}
render('<p>Price: {{ amount | currency("$", 2) }}</p>', { amount: 12.5 }, customFilters)- template (string): The template string to render
- context (object, optional): Data object passed to the template
- filters (object, optional): Custom filter functions
Returns the rendered string.
<script type="module">
import { render } from 'https://unpkg.com/un-jinja@latest'
const template = 'Hello {{ name }}!'
const result = render(template, { name: 'Browser' })
document.body.innerHTML = result
</script>export default {
fetch(request) {
const template = 'Hello {{ country }}!'
const result = render(template, { country: request.cf?.country })
return new Response(result)
}
}MIT License Β© Shivam Meena