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

vidoriel

v0.1.4

Published

Virtual dom template engine to create html and javascript user interfaces and render them server- and browser-side.

Downloads

105

Readme

Vidoriel

Vidoriel is a virtual DOM template system with strong focus on server-side rendering (SSR). Its unique strength is seamlessly handling the same .vdrl templates across server-side rendering, client-side hydration, and browser reactivity with the same codebase.

Installation

npm install vidoriel

Quick Start - Full SSR Workflow

1. Create templates

index.vdrl

<!DOCTYPE html>
<html>
	<head>
		<title>My App</title>
		{ssr}
	</head>
	<body>
		{content}
		<script type="module" src="/client.js"></script>
	</body>
</html>

greeting.vdrl

<app-container>
	<div class="greeting">
		<h1>Hello {name}!</h1>
		{if items}
			<ul>
			{each items}
				<li>{$i} of {$l}: {$v}</li>
			{/each}
			</ul>
		{/if}
		<form :submit>
			<button type="submit">Add Item</button>
		</form>
	</div>
</app-container>

2. Server-side rendering (generates HTML + serves modules):

server.js

// Node.js: run with --import flag
// Bun: bunfig.toml handles .vdrl imports
import GreetingTemplate from './greeting.vdrl';
import IndexTemplate from './index.vdrl';
import { createServer } from 'http';
import { readFile } from 'fs/promises';
import { compile } from 'vidoriel';

const server = createServer(async (req, res) => {
	// Serve compiled .vdrl files as JavaScript modules to the browser
	if (req.url.endsWith('.vdrl')) {
		const vdrlContent = await readFile(`.${req.url}`, 'utf8');
		const jsModule = await compile(vdrlContent);
		res.writeHead(200, { 'Content-Type': 'text/javascript' });
		res.end(jsModule);
		return;
	}
	
	// Prepare data
	const data = {
		name: 'World',
		items: ['Apple', 'Banana', 'Cherry'],
	};
	
	// Render content component
	const content = new GreetingTemplate(data);
	
	// Render full page with Vidoriel (including data for client hydration)
	const index = new IndexTemplate({
		content,
		ssr: '<script>globalThis.ssr = ' + JSON.stringify(data) + ';</script>'
	});
	
	res.writeHead(200, { 'Content-Type': 'text/html' });
	res.end(index.html());
});

3. Client-side hydration (makes it interactive):

client.js

import GreetingTemplate from './greeting.vdrl';

const app = new GreetingTemplate({
	...globalThis.ssr, // Reuse server-rendered data
	// Add interactivity
	submit(formData) {
		// formData contains form inputs (if any)
		const fruits = ['Orange', 'Mango', 'Kiwi', 'Pineapple'];
		const randomFruit = fruits[Math.floor(Math.random() * fruits.length)];
		this.items.push(randomFruit); // Automatically updates DOM!
	}
});

// Hydrate existing server-rendered HTML
app.mount(document.querySelector('app-container'));

The magic: Same template, same data, works perfectly on server AND client!

📁 See complete working example: Runnable server + client demo Bun: examples/ssr-workflow-bun/ NodeJS: examples/ssr-workflow-node/

💡 Learn more about SSR patterns: Component Patterns Guide

Variables

Basic Variables

{variable}          <!-- Simple variable -->
{variable.sub.prop} <!-- Nested object access -->
{user.name}         <!-- Object properties -->

Variable Types

{variable}     <!-- Optional (empty string if undefined) -->
{!requiredVar} <!-- Required (throws error if undefined) -->

Undefined variables resolve to empty strings. Use {!var} to throw an error if a variable is supposed to exist.

Control structures

if / elif / else

Full JavaScript conditions

{if user.id === 65 && group === 98}
	<p>Admin user in special group</p>
{elif user.active}
	<p>Active user</p>
{else}
	<p>Inactive user</p>
{/if}

Express condition syntax

{if arr}              <!-- Checks arr.length > 0 for arrays -->
{if variable}         <!-- Checks typeof variable !== 'undefined' && !!variable -->
{if !variable}        <!-- Negated variable check -->

each

Simple array

const fruits = ['Apple', 'Banana', 'Orange', 'Cherry'];
<fruit-list>
{each fruits}
	<fruit-item>
		<fruit-index>{$i}</fruit-index>  <!-- Index: 0, 1, 2, 3 -->
		<fruit-name>{$v}</fruit-name>    <!-- Value: Apple, Banana, etc. -->
		<fruit-total>{$l}</fruit-total>  <!-- Length: 4 -->
	</fruit-item>
{/each}
</fruit-list>

Array of objects

const users = [
	{ name: 'Alice', slug: 'alice', active: true },
	{ name: 'Bob', slug: 'bob', active: false }
];
<user-list>
{each users}
	<user-item class={active ? 'active' : false}> <!-- false means the attribute will not be shown -->
		<user-index>{$i}</user-index> <!-- $i works here too -->
		<a href="users/{slug}"><user-name>{name}</user-name></a> <!-- The properties of an item are directly accessible -->
		<user-name>{$v.name}</user-name> <!-- $v is the value, so you can call name like this too -->
	</user-item>
{/each}
</user-list>

Events

Vidoriel supports event binding with the : prefix:

<button :click=handleClick>Click me</button>
<form :submit=handleSubmit>
	<input :bind=username placeholder="Enter name">
	<input type="submit" value="Submit">
</form>
<button :click> <!-- shorthand for :click=click -->

Event Types

  • :click - Click events
  • :submit - Form submission
  • :input - Input changes
  • :bind - Two-way data binding for form inputs
  • Any standard DOM event (:mouseover, :keydown, etc.)

Event Handler Context

Event handlers receive a context object:

{	event: DOMEvent,     // The original DOM event
	element: DOMElement, // The target element
	vNode: VirtualNode   // The virtual DOM node
}
import Template from './greeting.vdrl';

const greeting = new Template({
	name: 'World',
	click({ event, element, vNode }) {
		alert('Clicked!');
	},
});

An exception is the :submit event, which provides the form data as an object as first argument

submit(formData, { event, element, vNode }) {
	console.log(formData); // { username: 'John' }
}

💡 Learn more: See Event Handling Examples for advanced patterns

Components

How to Organize (Choose Your Style)

1. Factory Functions (quick & simple)

import Vidoriel from 'vidoriel';

export function Card(data) {
	return new Vidoriel(`<div>{title}</div>`, {
		title: 'Hello',
		click() {
			alert('Clicked!');
		},
	});
}

2. Import .vdrl Files (clean separation)

see detailed explanation below in build integration

import Template from './card.vdrl';

const card = new Template({
	title: 'Hello',
	click() {
		alert('Clicked!');
	},
});

3. Convention-Based (scalable apps)

  • /components/card/card.vdrl <- view
  • /components/card/card.controller.js <- controller
  • /components/card/card.provider.js <- provider of data

Your framework auto-wires them for example like this (but probably automated by looping through the directories):

import CardView from './components/views/card.vdrl';
import CardController from './components/controllers/card.controller.js';
import CardProvider from './components/providers/card.provider.js';

const provider = new CardProvider(); // data provider
const controller = new CardController(provider.fetchSomeData()); // controller
const card = new CardView(controller); // view

card.mount(document.querySelector('app-container'));

📖 See Component Patterns Guide for detailed patterns and architecture examples.

How Components Work Together

Sub-Components (independent instances with own state):

const button = new Vidoriel(`<button :click=handleClick>Click</button>`, {
	handleClick() { console.log('Clicked!'); }
});

const page = new Vidoriel(`<div>{button}</div>`, { button });
// button keeps its own state and handlers

Sub-Templates (shared markup, inherits parent data):

globalThis.componentStore = {
	tags: { 'user-card': '/path/to/card-template' },
	paths: { '/path/to/card-template': parse(`<div>{name}</div>`) }
}; // our nodejs and bun plugin does this automatically

const app = new Vidoriel(`
	{each users}
		<user-card />
	{/each}
`, { users: [...] }, componentStore);
// each card inherits data from parent's loop though the users with each

💡 Deep dive: Component Patterns Guide covers all organization patterns, sub-components vs sub-templates, nested components, and best practices

Localization

Support for internationalization with custom translate function:

// Set up custom translate function by overwriting Vidoriel.translate
Vidoriel.translate = (key, data, ...args) => {
	const translations = {
		welcome_message: 'Welcome to our app!',
		greeting: 'Hello {0}, you have {1} messages'
	};
	
	let text = translations[key] || key;
	// Replace {0}, {1}, etc. with args
	args.forEach((arg, i) => {
		text = text.replace(`{${i}}`, arg);
	});
	return text;
};
{#welcome_message}            <!-- Uses translate function -->
{#greeting name messageCount} <!-- Passes variables name and messageCount as args -->

API Reference

Vidoriel Class

const app = new Vidoriel(template, data, componentTree);

Methods

  • html() - Generate HTML string (server-side)
  • mount(element) - Hydrate/mount to DOM (client-side)
  • patch() - Manually trigger re-render
  • render() - Get virtual DOM tree

Properties

  • data - Reactive data object (auto-triggers patches)
  • virtualDOM - Current virtual DOM state

Data Reactivity

Data is automatically wrapped in proxies for reactivity:

app.data.username = 'Alice'; // Automatically triggers DOM update
app.data.items.push('New Item'); // Array changes also trigger updates

How It Works

Server-Side Rendering

  • Build plugins compile .vdrl → JavaScript modules
  • Use .html() method to generate HTML strings
  • Perfect for HTTP responses, static site generation
// Server generates HTML
const html = app.html(); // Returns: '<div class="greeting">...</div>'

Client-Side Hydration

  • Same .vdrl imports work in browser
  • Use .mount() to attach to existing DOM
  • Automatic reactivity and virtual DOM diffing
// Browser makes it interactive
app.mount(document.querySelector('#app'));
app.data.name = 'Alice'; // DOM updates automatically!

Zero Configuration SSR

  • No build step needed - plugins handle everything
  • Same templates work server AND client
  • Seamless hydration - no mismatches or flashes
  • Full reactivity after hydration

Direct Template Usage

You can also use template strings without .vdrl files:

import Vidoriel from 'vidoriel';

const app = new Vidoriel(`
<div>Hello {name}!</div>
`, { name: 'World' });

// Server: app.html()
// Browser: app.mount(element)

Build Integration

Vidoriel's build plugins are essential for its preferred SSR workflow - they make .vdrl files work as JavaScript modules in both server and browser environments.

What the plugins do:

  • Server: Compile .vdrl → JS modules for import statements
  • Browser: Server responds with text/javascript header + compiled JS
  • Seamless: Same import MyComponent from './component.vdrl' works everywhere
  • Zero config: No webpack, no build step, just works

Bun

# bunfig.toml
preload = ["vidoriel/bun-preload.js"]

Node.js (v20+)

node --import=vidoriel/nodejs-plugin-import.js your-script.js

Node.js (Legacy)

node --loader=vidoriel/nodejs-plugin.js your-script.js

Error Codes

Renderer Errors

  • 01: Template error evaluating condition
  • 02: Mandatory variable not found
  • 03: Tried to use function as var
  • 04: Weird render tree, maybe accessing object as var?
  • 05: Invalid variable node

DOM Errors

  • 06: Unknown node
  • 07: insertBefore error
  • 08: removeChild error

Parser Errors

  • 09: Closing tag mismatch
  • 10: DOM Stack empty

Patcher Errors

  • 11: No real dom parentNode

Component Errors

  • 12: Can't patch component that never was mounted (auto-prevents default)

License

MIT License: You are free to use, modify, and distribute this software, but it comes with no warranty. Do not use the exact same name or imply that your fork or application is directly affiliated with Vidoriel or Hybrilios.

Credits

  • Vidoriels patch mechanics are based on snabbdom but no longer campatible with it
  • It was developed for the Hybrilios framework