npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

Iโ€™ve always been into building performant and accessible sites, but lately Iโ€™ve been taking it extremely seriously. So much so that Iโ€™ve been building a tool to help me optimize and monitor the sites that I build to make sure that Iโ€™m making an attempt to offer the best experience to those who visit them. If youโ€™re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, ๐Ÿ‘‹, Iโ€™m Ryan Hefnerย  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If youโ€™re interested in other things Iโ€™m working on, follow me on Twitter or check out the open source projects Iโ€™ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soonโ€“ish.

Open Software & Tools

This site wouldnโ€™t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you ๐Ÿ™

ยฉ 2026 โ€“ย Pkg Stats / Ryan Hefner

voniq

v0.2.2

Published

๐Ÿš€ Fast, lightweight and modern template engine for Node.js

Readme

Install

npm install voniq

Features

  • Familiar syntax โ€” {variable}, {#if}, {#each}, components
  • Zero dependencies, ESM-only, written in TypeScript
  • Templates are compiled once into plain string builders โ€” no eval
  • Renders after compilation are synchronous and allocation-light
  • Compiled templates cached by path, concurrent-safe
  • Components with props and children, resolved relative to the importing file
  • Strict by default: missing variables fail loudly with helpful errors
  • 100% test coverage

Quick start

templates/welcome.viq

Hello {user.name},

{#if items}
Order items:
{#each items as product}
- {product.name}: ${product.price}
{/each}

Total: ${total}
{#else}
Your cart is empty.
{/if}

Render it:

import { render } from 'voniq'

const result = await render('templates/welcome.viq', {
  user: { name: 'Ana' },
  items: [
    { name: 'Keyboard', price: 199.9 },
    { name: 'Mouse', price: 89.5 }
  ],
  total: 289.4
})

Output:

Hello Ana,

Order items:
- Keyboard: $199.9
- Mouse: $89.5

Total: $289.4

Syntax

Interpolation

Hello {username}
Hello { username }        <!-- whitespace is trimmed -->
Hello {user.address.city} <!-- dot paths everywhere -->

Missing variables throw a clear error instead of rendering silently empty:

voniq: missing variable "{username}"

Conditionals

{#if premium}
Premium plan
{#else if trial}
Trial: {days} days left
{#else}
Free plan
{/if}

Conditions are variable paths checked against template truthiness:

| Truthy | Falsy | | ----------------- | ----------------- | | 'text' | '' | | 1 | 0, NaN | | ['item'] | [] | | {} | null, undefined | | any object | false |

Empty lists are falsy, so {#if items} reads naturally. Missing variables evaluate to false instead of throwing.

Directives placed alone on a line don't leave blank lines behind:

<ul>
  {#each users as user}
  <li>{user.name}</li>
  {/each}
</ul>

Iteration

{#each users as user}
{index}. {user.name}
{/each}
  • The current item binds to as alias (defaults to item)
  • {index} carries the loop position
  • Blocks nest freely; inner scopes see outer ones

Components

Define once in components/button.viq:

<a
  href="{props.href}"
  class="button button-{props.variant}"
>
  {props.children}
</a>

Import and invoke it anywhere:

{% import "components/button" %}

{#button href="{{url}}" variant="primary"}
  Confirm your email
{/button}
  • Props are declared as attributes: "text" is a literal, "{{path}}" binds a value from the calling scope
  • The content between the tags arrives as {props.children}
  • Components without content can self-close: {#footer year="{{y}}" /}
  • Component scope is isolated: only props is visible inside
  • Paths resolve relative to the importing file (../../shared/x works), extension .viq optional
  • Circular imports are detected and reported

API

render(path, data?)

Reads, compiles and caches a template file, then renders it.

const html = await render('templates/activate.viq', {
  url: 'https://example.dev/activate'
})

Returns Promise<string>. Subsequent calls with the same path skip reading and compiling entirely.

compile(template)

Compiles a template string without touching the filesystem.

const renderFn = await compile('Hello {name}')

renderFn({ name: 'Ada' }) // 'Hello Ada'
renderFn({ name: 'Alan' }) // 'Hello Alan'

Returns Promise<(data?: Vars) => string>. The returned function is synchronous โ€” all I/O happens once, during compilation.

Types

type Vars = Record<string, unknown>

Errors at compile time

Broken templates fail when compiled, not in production renders:

voniq: unclosed "{#if}"
voniq: unexpected "{/each}"
voniq: duplicated "{#else}"
voniq: invalid binding "{{ }}" in prop "href"
voniq: circular import "b"
voniq: unknown component "{#button}"

License

MIT