Zog.js Documentation

Full reactivity with minimal code size

Highlights

  • Reactive primitives: ref, reactive, computed
  • Effects: watchEffect
  • 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 @)
  • Dynamic attribute binding with :attribute syntax
  • App lifecycle: createApp(...).mount(selector) and .unmount()
  • Hook system: addHook, removeHook for lifecycle events NEW
  • Plugin system: createApp().use(plugin) for extending functionality

Quick Start

Using the CDN

The fastest way to get started is using the Zog.js CDN:

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

    createApp(() => {
        const count = ref(0);
        const increment = () => count.value++;

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

Complete 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>
    </div>

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

        createApp(() => {
            const title = ref('Counter App');
            const count = ref(0);
            const increment = () => count.value++;

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

Core Concepts

Reactivity Primitives

  • ref(initialValue) — creates a reactive reference with .value
  • reactive(object) — returns a reactive proxy of an object (deep reactive for nested objects and arrays)
  • computed(getter) — memoized reactive value that recomputes when dependencies change
  • watchEffect(fn, opts?) — run a reactive effect immediately and re-run when dependencies change

Template Interpolation

Any text node containing {{ expression }} will be evaluated against the component scope:

<p>Hello, {{ name }} — you have {{ items.length }} items.</p>

Template Directives

The compiler supports a small set of directives for common tasks:

  • z-if="expression" — render element only when truthy
  • z-else-if="expression" and z-else — chainable conditional branches
  • z-for="item in list" — repeat an element for each item. Supports :key attribute for optimized diffing
  • z-text="expression" — set textContent
  • z-html="expression" — set innerHTML
  • z-show="expression" — toggles display (none when falsy)
  • z-model="prop" — two-way binding for inputs
  • z-on:event="handler" or @event="handler" — attach event listener
  • :attribute="expression" — bind any attribute dynamically (style objects, class objects, boolean attributes)

API Reference

ref(value)

Creates a reactive reference.

const count = ref(0);
count.value = 2; // Set value
console.log(count.value); // Get value

reactive(obj)

Wraps an object or array in a reactive proxy. Supports deep reactivity for nested structures.

const state = reactive({ todos: [] });
state.todos.push('New item'); // Reactive!

computed(getter)

Creates a lazily evaluated computed value.

const doubled = computed(() => count.value * 2);
console.log(doubled.value); // Access computed value

watchEffect(fn, opts?)

Run fn immediately, re-run whenever a reactive dependency changes.

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

// Later: stop watching
stop();

createApp(setup)

Creates an app instance. setup returns the reactive scope for templates.

const app = createApp(() => {
    return { /* scope */ };
});

app.mount('#app'); // Mount to DOM
app.unmount(); // Cleanup

use(plugin, options?) NEW

Install a plugin to extend Zog.js functionality.

const MyPlugin = {
    install(zog, options) {
        // Plugin logic here
        console.log('Plugin installed!', options);
    }
};

use(MyPlugin, { theme: 'dark' });

nextTick(fn?)

Waits for the next DOM update cycle before executing a callback. Returns a Promise.

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

count.value++;
await nextTick();
console.log('DOM has been updated!');

addHook(name, fn) NEW

Registers a lifecycle hook. Available hooks: beforeCompile, afterCompile, beforeEffect, onError.

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

addHook('beforeEffect', (effect) => {
    console.log('Effect running:', effect.id);
});

removeHook(name, fn) NEW

Removes a previously registered hook.

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

const myHook = (effect) => console.log(effect);
addHook('beforeEffect', myHook);

// Later...
removeHook('beforeEffect', myHook);

Hook System NEW

Version 0.3.0 introduces a powerful Hook system that allows you to tap into Zog.js internal lifecycle events. Perfect for debugging, logging, performance monitoring, and extending functionality.

Available Hooks

Hook Name Parameters Description
beforeCompile (el, scope, cs) Runs before an element is compiled. Return false to skip compilation of that element.
afterCompile (el, scope, cs) Runs after an element and its children have been compiled successfully.
beforeEffect (effect) Triggered every time a reactive effect is about to run.
onError (error, source, args) Global error handler for compilation errors, event handler errors, and plugin errors.

Adding and Removing Hooks

import { addHook, removeHook } from './zog.js';

// Add a hook
const myHook = (effect) => {
    console.log('Effect running with ID:', effect.id);
};

addHook('beforeEffect', myHook);

// Remove it later when no longer needed
removeHook('beforeEffect', myHook);

Example: Performance Monitor

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

addHook('beforeEffect', (effect) => {
    const start = performance.now();
    effect._startTime = start;
    console.log(`⏱️ Effect ${effect.id} starting...`);
});

addHook('onError', (error, source, args) => {
    console.error(`❌ Error in ${source}:`, error);
    // Send to analytics service
    sendToAnalytics({ error, source, timestamp: Date.now() });
});

Use Cases

  • Debugging: Log every reactive update to understand application behavior
  • Performance Monitoring: Track effect execution times and optimize bottlenecks
  • Error Tracking: Capture and send errors to monitoring services like Sentry
  • Development Tools: Build custom devtools extensions with lifecycle visibility
  • Testing: Verify that effects run in the correct order during tests

Examples

Simple Counter

<div id="counter">
  <p>Count: <span z-text="count"></span></p>
  <button @click="inc">+1</button>
</div>

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

createApp(() => {
  const count = ref(0);
  const inc = () => count.value++;
  return { count, inc };
}).mount('#counter');
</script>

Todo List with Reactive Arrays

<div id="todo">
  <input z-model="newItem" placeholder="Add todo" />
  <button @click="add">Add</button>

  <ul>
    <li z-for="(item, i) in todos" :key="item">
      {{ i + 1 }}. {{ item }}
      <button @click="remove(i)">remove</button>
    </li>
  </ul>
</div>

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

createApp(() => {
  const state = reactive({ todos: ['Buy milk'] });
  const newItem = ref('');

  function add() {
    if (newItem.value.trim()) {
      state.todos.push(newItem.value.trim());
      newItem.value = '';
    }
  }

  function remove(i) {
    state.todos.splice(i, 1);
  }

  return { todos: state.todos, newItem, add, remove };
}).mount('#todo');
</script>

Dynamic Styling and Classes

<div id="styling">
  <button @click="toggleActive">Toggle Active</button>
  <div 
    :class="{ active: isActive, 'text-bold': true }"
    :style="{ color: textColor, fontSize: size + 'px' }">
    Styled content
  </div>
</div>

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

createApp(() => {
  const isActive = ref(false);
  const textColor = ref('blue');
  const size = ref(16);
  
  const toggleActive = () => isActive.value = !isActive.value;
  
  return { isActive, textColor, size, toggleActive };
}).mount('#styling');
</script>

Unmounting Applications

When you need to clean up an application instance (e.g., in SPAs or dynamic component mounting), use the unmount() method:

const app = createApp(setup).mount('#app');

// Later, when component is removed:
app.unmount(); // Cleans up all effects, listeners, and scopes

Template Directives

Directive Purpose Example
{{ expr }} Interpolate JS expression from scope <p>Hello, {{ name }}</p>
z-if / z-else-if / z-else Conditional rendering <div z-if="isOpen">Open</div>
z-for Loop: item in list or (item, i) in list <li z-for="(item, i) in items">{{ i }} - {{ item }}</li>
:key Unique key for z-for optimization <li z-for="item in items" :key="item.id">
z-text Set textContent <p z-text="message"></p>
z-html Set innerHTML <div z-html="rawHtml"></div>
z-show Toggle display (style) <div z-show="visible">...</div>
z-model Two-way binding with inputs <input z-model="value" />
z-on:event or @event Event listener <button @click="handle">Click</button>
:attribute Dynamic attribute binding <div :id="dynamicId" :disabled="isDisabled">
:class Dynamic class binding (object/string) <div :class="{ active: isActive }">
:style Dynamic style binding (object) <div :style="{ color: textColor }">

Notes:

  • Event directive accepts a scope function or expression (@click="doSomething" where doSomething exists in scope).
  • z-for supports in and of and (item, index) tuple syntax.
  • Use :key in z-for loops for better performance when lists change.
  • Boolean attributes (like disabled, checked) are handled automatically with : binding.

Plugin System

Zog.js supports a powerful plugin architecture. Plugins receive full access to the core API and internal utilities, enabling deep extensions of the framework.

Plugin API

When you install a plugin, it receives an object with:

  • Core Functions: reactive, ref, computed, watchEffect, createApp
  • Hook Management: addHook, removeHook
  • Internal Utilities: Exposed via utils object:
    • isObj - Check if value is an object
    • evalExp - Safely evaluate expressions
    • Dep - Dependency tracking class
    • ReactiveEffect - Effect system class
    • Scope - Component scope manager
    • compile - Template compilation function

Creating a Plugin

// router-plugin.js
const RouterPlugin = {
    install(app, options) {
        const { reactive, watchEffect, addHook } = app;
        
        // Create reactive route object
        const route = reactive({ 
            path: window.location.pathname,
            query: new URLSearchParams(window.location.search)
        });
        
        // Listen to browser navigation
        window.addEventListener('popstate', () => {
            route.path = window.location.pathname;
        });
        
        // Optional: Add lifecycle hooks
        addHook('afterCompile', (el) => {
            console.log('Route component compiled:', el.tagName);
        });
        
        // Return API to be merged into app
        return { route };
    }
};

export default RouterPlugin;

Using a Plugin

import { createApp } from './zog.js';
import RouterPlugin from './router-plugin.js';

const app = createApp(setup)
    .use(RouterPlugin, { basePath: '/' })
    .mount('#app');

Plugin Return Values

Plugins can optionally return an object that will be merged into the application context, or return the app instance itself for method chaining.

Plugin Use Cases

  • Routing systems with history management
  • Global state management solutions
  • Form validation libraries
  • HTTP client wrappers
  • Animation helpers
  • Devtools integrations

Tips & Gotchas

  • Template expressions are evaluated against the scope object returned by your setup() function.
  • Refs must be accessed with .value in JavaScript, but are automatically unwrapped in templates.
  • Array reactivity is fully supported - methods like push, pop, splice trigger updates automatically.
  • Use :key in z-for loops for better performance when lists change.
  • The library is ESM: import using type="module" or bundle with a bundler (Vite, Webpack, Rollup).
  • z-html can cause XSS vulnerabilities - only use with trusted content.

Performance Optimizations

Zog.js v0.3.0 includes several optimizations:

  • Expression caching: Compiled template expressions are cached (limit: 200) for better performance
  • Unified array handling: Single method handler for all array operations
  • Key-based diffing: Use :key attribute in z-for loops for efficient list updates
  • Batched updates: Reactive changes are batched and executed in microtasks
  • Lazy computed values: Computed properties only recalculate when accessed and dependencies change

Development & Contributing

  • The repository contains the single-file implementation zog.js.
  • To experiment, create an HTML page with <script type="module"> and import from the CDN or local file.
  • If you plan to add features or refactors:
    • Keep the public API stable (exports listed above).
    • Document any additional template directives clearly.
    • Add minimal reproducible examples for each new feature.
    • Ensure optimizations don't introduce bugs - test thoroughly.

What's New in v0.3.0

New Features:

  • Hook System: addHook() and removeHook() for tapping into internal lifecycle events
  • Enhanced Plugin API: Plugins now receive utils object with internal classes and functions
  • nextTick() restored: Convenient helper for waiting on DOM updates
  • Improved Error Handling: Global onError hook catches compilation and runtime errors
  • Better Array Reactivity: All array methods including iterators are fully reactive

License

Zog.js is open-source software licensed under the MIT License.

You are free to use, modify, and distribute this project in commercial or non-commercial applications.

For full details, see the LICENSE file.