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

zogjs

v0.4.10

Published

MiniVue: A minimal reactive framework inspired by Vue.js

Readme

Zog.js

Full reactivity with minimal code size.

Zog.js is a minimalist JavaScript library for building reactive user interfaces. It allows you to write clean, declarative templates directly in your HTML and power them with a simple, yet powerful, reactivity system. Inspired by the best parts of modern frameworks, Zog.js offers an intuitive developer experience with zero dependencies and no build step required.


Highlights

  • Reactive primitives: ref (primitives only), reactive (objects/arrays), computed
  • Effects: watchEffect with automatic dependency tracking
  • Lightweight template compiler for declarative DOM binding and interpolation ({{ }})
  • Template directives: z-if, z-for, z-text, z-html, z-show, z-model, z-on (shorthand @)
  • App lifecycle: createApp(...).mount(selector) and .unmount()
  • Hook System: Extend and customize behavior with lifecycle hooks
  • Plugin architecture: app.use(plugin, options) for modular extensions
  • Async effect queue: Batched updates with effect sorting for optimal performance

Installation

Via npm

npm install zogjs

Direct ES Module

<script type="module">
  import { createApp, ref } from 'zog.js';
  // or from CDN
  import { createApp, ref } from 'https://cdn.zogjs.com/0.4.7/zog.js';
</script>

Quick Start

Basic Counter Example

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Zog.js Counter</title>
</head>
<body>
    <div id="app">
        <h1>{{ title }}</h1>
        <p>Current count: {{ count }}</p>
        <button @click="increment">Increment</button>
        <button @click="decrement">Decrement</button>
    </div>

    <script type="module">
        import { createApp, ref } from './zog.js';

        createApp(() => {
            const title = ref('Counter App');
            const count = ref(0);

            const increment = () => count.value++;
            const decrement = () => count.value--;

            return { title, count, increment, decrement };
        }).mount('#app');
    </script>
</body>
</html>

Core Concepts

Reactivity Primitives

ref(primitive) — For primitive values only

Creates a reactive reference for strings, numbers, and booleans only.

const count = ref(0);
count.value++; // Triggers reactive updates

// In templates, .value is automatically unwrapped:
// {{ count }} instead of {{ count.value }}

⚠️ Important (v0.4.7): ref() throws an error if passed an object or array. Use reactive() instead.

// ❌ WRONG - throws error
const user = ref({ name: 'John' });

// ✅ CORRECT
const user = reactive({ name: 'John' });

reactive(object) — For objects and arrays

Returns a deep reactive proxy of an object or array.

const state = reactive({
    user: { name: 'John', age: 30 },
    todos: ['Learn Zog.js']
});

// All nested properties are reactive
state.user.age = 31;
state.todos.push('Build an app');

Array reactivity: All array methods are fully reactive:

  • Mutators: push, pop, shift, unshift, splice, sort, reverse, fill, copyWithin
  • Iterators: map, filter, find, findIndex, findLast, findLastIndex, every, some, forEach, reduce, reduceRight, flat, flatMap, values, entries, keys, includes, indexOf, lastIndexOf

computed(getter)

Creates a lazily evaluated, memoized reactive value.

const firstName = ref('John');
const lastName = ref('Doe');

const fullName = computed(() => `${firstName.value} ${lastName.value}`);

console.log(fullName.value); // "John Doe"
firstName.value = 'Jane';
console.log(fullName.value); // "Jane Doe"

watchEffect(fn, opts?)

Runs a reactive effect immediately and re-runs when dependencies change.

const count = ref(0);

const stop = watchEffect(() => {
    console.log('Count is:', count.value);
});

count.value++; // Logs: "Count is: 1"
stop(); // Stop watching

Template Interpolation

Text nodes containing {{ expression }} are automatically reactive:

<p>Hello, {{ name }}!</p>
<p>You have {{ items.length }} items.</p>
<p>Total: {{ price * quantity }}</p>

Template Directives

Conditional Rendering

z-if, z-else-if, z-else: Conditionally render elements.

<div z-if="score >= 90">Excellent!</div>
<div z-else-if="score >= 70">Good job!</div>
<div z-else>Keep trying!</div>

List Rendering

z-for: Repeat elements for each item in an array.

<!-- Simple iteration -->
<li z-for="item in items">{{ item }}</li>

<!-- With index -->
<li z-for="(item, index) in items">
    {{ index + 1 }}. {{ item.name }}
</li>

<!-- With key (recommended) -->
<li z-for="item in items" :key="item.id">
    {{ item.name }}
</li>

z-for behavior (v0.4.7):

  • Object items are reactive (direct property access)
  • Primitive items are ref-wrapped (auto-unwrapped in templates)
  • Index is a plain number that updates correctly when array changes
  • Always use :key with unique IDs for performance

Content Directives

<p z-text="message"></p>           <!-- Safe textContent -->
<div z-html="htmlContent"></div>   <!-- innerHTML (⚠️ XSS risk) -->
<div z-show="isVisible">...</div>  <!-- Toggle display -->

Two-Way Binding

z-model: Bind form inputs bidirectionally.

<input z-model="username" />
<textarea z-model="bio"></textarea>
<input type="checkbox" z-model="agreed" />
<input type="radio" z-model="color" value="red" />
<select z-model="country">
    <option value="us">United States</option>
</select>

Event Handling

@event or z-on:event: Attach event listeners.

<!-- Method handler (recommended) -->
<button @click="handleClick">Click me</button>

<!-- Inline expression (need .value for refs) -->
<button @click="count.value++">Increment</button>

Attribute Binding

:attribute: Dynamically bind any attribute.

<img :src="imageUrl" :alt="imageAlt" />
<button :disabled="isDisabled">Submit</button>
<div :class="{ active: isActive, error: hasError }">Content</div>
<div :style="{ color: textColor, fontSize: size + 'px' }">Text</div>

Hook System

import { onHook } from './zog.js';

// Available hooks: beforeCompile, afterCompile, onError
onHook('beforeCompile', (el, scope, cs) => {
    console.log('Compiling:', el.tagName);
});

onHook('onError', (error, context, details) => {
    console.error(`Error in ${context}:`, error);
});

Plugin System

Creating a Plugin

// my-plugin.js
export const MyPlugin = {
    install(api, options) {
        // api contains: app, reactive, ref, computed, watchEffect,
        //               onHook, compile, Scope, evalExp
        
        api.onHook('beforeCompile', (el, scope, cs) => {
            // Custom directive example
            if (el.hasAttribute('z-focus')) {
                el.removeAttribute('z-focus');
                setTimeout(() => el.focus(), 0);
            }
        });
    }
};

Using Plugins

import { createApp } from './zog.js';
import { MyPlugin } from './my-plugin.js';

createApp(() => ({ /* ... */ }))
    .use(MyPlugin, { debug: true })
    .mount('#app');

Complete Example: Todo List

<div id="app">
    <input z-model="newTodo" @keyup.enter="addTodo" placeholder="Add todo" />
    <button @click="addTodo">Add</button>

    <ul>
        <li z-for="(todo, index) in todos" :key="todo.id">
            <input type="checkbox" 
                   :checked="todo.done" 
                   @change="toggleTodo(todo.id)" />
            <span :class="{ done: todo.done }">
                {{ index + 1 }}. {{ todo.text }}
            </span>
            <button @click="removeTodo(todo.id)">×</button>
        </li>
    </ul>
    
    <p z-show="todos.length === 0">No todos yet!</p>
    <p>{{ remaining }} of {{ todos.length }} remaining</p>
</div>

<script type="module">
import { createApp, ref, reactive, computed } from './zog.js';

createApp(() => {
    const newTodo = ref('');
    const todos = reactive([]);
    let nextId = 1;
    
    const remaining = computed(() => todos.filter(t => !t.done).length);

    function addTodo() {
        if (!newTodo.value.trim()) return;
        todos.push({ id: nextId++, text: newTodo.value, done: false });
        newTodo.value = '';
    }

    function removeTodo(id) {
        const idx = todos.findIndex(t => t.id === id);
        if (idx > -1) todos.splice(idx, 1);
    }

    function toggleTodo(id) {
        const todo = todos.find(t => t.id === id);
        if (todo) todo.done = !todo.done;
    }

    return { newTodo, todos, remaining, addTodo, removeTodo, toggleTodo };
}).mount('#app');
</script>

API Reference

| Function | Description | |----------|-------------| | ref(primitive) | Reactive reference for primitives only | | reactive(object) | Deep reactive proxy for objects/arrays | | computed(getter) | Cached computed value | | watchEffect(fn, opts?) | Auto-tracking reactive effect | | createApp(setup) | Create app with .mount(), .unmount(), .use() | | nextTick(fn) | Execute after DOM update | | onHook(name, fn) | Register lifecycle hook |

Directive Reference

| Directive | Example | |-----------|---------| | {{ expr }} | <p>{{ message }}</p> | | z-if / z-else-if / z-else | <div z-if="show">Text</div> | | z-for | <li z-for="item in items" :key="item.id"> | | z-model | <input z-model="value" /> | | z-show | <div z-show="visible"> | | z-text / z-html | <p z-text="msg"></p> | | @event | <button @click="handler"> | | :attr | <img :src="url" /> | | :class | <div :class="{ active: isActive }"> | | :style | <div :style="{ color: c }"> |


Browser Support

Requires ES6 Proxy support:

  • Chrome 49+, Firefox 18+, Safari 10+, Edge 12+
  • ❌ Internet Explorer

Bundle Size

  • ~5KB minified
  • Zero dependencies
  • No build step required

Changelog

v0.4.7 (Current)

  • ✅ Added comprehensive code documentation
  • ✅ Removed unused code

v0.4.6

Breaking Changes:

  • ⚠️ ref() now only accepts primitive values (string, number, boolean)
  • ⚠️ Use reactive() for objects and arrays

Bug Fixes:

  • 🐛 Fixed z-for index reactivity (index now updates correctly when array changes)
  • 🐛 Restored effect sorting by ID for correct execution order
  • 🐛 Added expression cache limit (500) to prevent memory leaks

Improvements:

  • ✨ Plugin API now receives full access: reactive, ref, computed, watchEffect, onHook, compile, Scope, evalExp
  • ✨ Cleaner separation between ref (primitives) and reactive (objects)
  • ✨ Improved Scope management with parent-child relationships

v0.3.2

  • ✨ Added Hook System (beforeCompile, afterCompile, beforeEffect, onError)
  • ✨ Plugin API with access to hooks and utilities
  • 🚀 Optimized effect queue management

Migration from v0.3.x to v0.4.x

Breaking Change: ref() no longer accepts objects/arrays.

// Before (v0.3.x)
const user = ref({ name: 'John' });
user.value.name = 'Jane';

// After (v0.4.x)
const user = reactive({ name: 'John' });
user.name = 'Jane';

License

MIT License - Free to use in commercial and non-commercial projects.


Made with ❤️ for simplicity.