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
:attributesyntax - App lifecycle:
createApp(...).mount(selector)and.unmount() - Hook system:
addHook,removeHookfor 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.valuereactive(object)— returns a reactive proxy of an object (deep reactive for nested objects and arrays)computed(getter)— memoized reactive value that recomputes when dependencies changewatchEffect(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 truthyz-else-if="expression"andz-else— chainable conditional branchesz-for="item in list"— repeat an element for each item. Supports:keyattribute for optimized diffingz-text="expression"— settextContentz-html="expression"— setinnerHTMLz-show="expression"— togglesdisplay(nonewhen falsy)z-model="prop"— two-way binding for inputsz-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"wheredoSomethingexists in scope). z-forsupportsinandofand(item, index)tuple syntax.- Use
:keyinz-forloops 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
utilsobject:isObj- Check if value is an objectevalExp- Safely evaluate expressionsDep- Dependency tracking classReactiveEffect- Effect system classScope- Component scope managercompile- 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
.valuein JavaScript, but are automatically unwrapped in templates. - Array reactivity is fully supported - methods like
push,pop,splicetrigger updates automatically. - Use
:keyinz-forloops for better performance when lists change. - The library is ESM: import using
type="module"or bundle with a bundler (Vite, Webpack, Rollup). z-htmlcan 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
:keyattribute inz-forloops 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()andremoveHook()for tapping into internal lifecycle events - Enhanced Plugin API: Plugins now receive
utilsobject with internal classes and functions - nextTick() restored: Convenient helper for waiting on DOM updates
- Improved Error Handling: Global
onErrorhook 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.