
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
A modern, async-first template engine inspired by Jinja2, built for Bun with full support for async/await operations.
A modern, async-first template engine inspired by Jinja2, built for Bun with full support for async/await operations.
bun install
import { Template } from "./src/index";
// Simple variable rendering
const result = await Template.render("Hello, {{ name }}!", { name: "World" });
console.log(result); // "Hello, World!"
// With async filters
const result2 = await Template.render(
"{{ message | upper }}",
{ message: "hello" },
{
filters: {
upper: (value: string) => value.toUpperCase(),
},
}
);
console.log(result2); // "HELLO"
Variables are rendered using double curly braces:
{{ variable }}
{{ user.name }}
{{ items[0] }}
Use {% set %} to assign variables within templates:
{% set name = "World" %}
Hello, {{ name }}!
Variables can be assigned from async operations:
{% set doc = await zodula("123") %}
Document ID: {{ doc.id }}
Document Name: {{ doc.name }}
Intermediate function and method calls do not unwrap Promises or thenables. Only an explicit await in the template (or output/set/if/for boundaries) adopts them. That way fluent APIs can return a thenable builder from one step and still call further methods on it before the final resolution:
{% set doc = await client.resource("Foo").load("id-1").withOptions(bypass) %}
Variable output, {% set %}, {% if %}, and {% for %} each settle the expression once (via Promise.resolve), so {{ fn() }} still prints the resolved value when fn returns a Promise or thenable—you do not need to write {{ await fn() }} for that case.
Variables can also be assigned from function calls and filters:
{% set result = add(5, 3) %}
The sum is: {{ result }}
{% set data = "hello" | upper %}
Uppercase: {{ data }}
{% if user.logged_in %}
Welcome, {{ user.name }}!
{% else %}
Please log in.
{% endif %}
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
Loop variables are available:
{% for item in items %}
{{ loop.index }}. {{ item }}
{% if loop.first %} (First) {% endif %}
{% if loop.last %} (Last) {% endif %}
{% endfor %}
Available loop variables:
loop.index - 1-based indexloop.index0 - 0-based indexloop.first - true if first iterationloop.last - true if last iterationloop.length - total lengthApply filters using the pipe operator:
{{ message | upper }}
{{ price | formatCurrency }}
{{ text | truncate(50) }}
Filters can be async functions:
const template = "{{ data | fetchData }}";
const result = await Template.render(template, { data: "user123" }, {
filters: {
fetchData: async (id: string) => {
// Simulate async operation
await new Promise(resolve => setTimeout(resolve, 10));
return `User: ${id}`;
},
},
});
Call functions directly in templates:
{{ getCurrentTime() }}
{{ formatDate(date) }}
Functions can be async:
const result = await Template.render("{{ getTime() }}", {
getTime: async () => {
await new Promise(resolve => setTimeout(resolve, 10));
return new Date().toISOString();
},
});
Support for various expressions:
{{ a + b }}
{{ price * quantity }}
{{ user.age >= 18 }}
{{ isActive and isVerified }}
{{ not isHidden }}
Both Jinja2-style (and, or) and JavaScript-style (&&, ||) operators are supported:
{{ user.name || 'Anonymous' }}
{{ user.email || 'No email' }}
{{ user.active && 'Yes' || 'No' }}
{{ product.inStock || product.preOrder }}
{{ user.tags && user.tags.length > 0 }}
Both styles work the same way - they return the first truthy value (for or/||) or the first falsy value (for and/&&), not boolean values.
Conditionally render elements based on data:
{% if user.avatar %}
<img src="{{ user.avatar }}" alt="Avatar" />
{% endif %}
{% if user.email %}
<p>Email: <a href="mailto:{{ user.email }}">{{ user.email }}</a></p>
{% endif %}
{% if user.bio %}
<p class="bio">{{ user.bio }}</p>
{% else %}
<p class="bio empty">No bio available</p>
{% endif %}
{% if user.tags && user.tags.length > 0 %}
<div class="tags">
{% for tag in user.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
Use || for fallback values:
<h2>{{ user.name || 'Anonymous User' }}</h2>
<p>{{ review.text || 'No review text' }}</p>
<span>Rating: {{ review.rating || 'N/A' }}</span>
import { Template } from "./src/index";
// Static method
const result = await Template.render(template, context, options);
// Instance method
const tpl = new Template(template, options);
const result = await tpl.render(context);
interface TemplateOptions {
filters?: FilterRegistry; // Custom filters
autoescape?: boolean; // Auto-escape HTML (default: true)
trimBlocks?: boolean; // Trim whitespace blocks
lstripBlocks?: boolean; // Strip leading whitespace
}
const tpl = new Template(template, {
filters: {
upper: (value: string) => value.toUpperCase(),
lower: (value: string) => value.toLowerCase(),
capitalize: (value: string) =>
value.charAt(0).toUpperCase() + value.slice(1),
},
});
// Or add filters dynamically
tpl.addFilter("reverse", (value: string) =>
value.split("").reverse().join("")
);
See src/example.ts for comprehensive examples:
bun run src/example.ts
# Run examples
bun run src/example.ts
# Run tests (if you add tests)
bun test
MIT
FAQs
A modern, async-first template engine inspired by Jinja2, built for Bun with full support for async/await operations.
We found that binba demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.