WakaPAC is a complete application framework in a single JavaScript file that provides reactive data binding, component hierarchy, and automatic DOM updates. Include one script tag, write HTML and JavaScript, and you're building reactive applications - no npm, no bundlers, no configuration files.
The name WakaPAC combines Waka (the sound Pac-Man makes) with PAC (the Presentation-Abstraction-Control architecture pattern it's built on) - because good software architecture should be as addictive as classic arcade games.
Here's a complete interactive application:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/gh/quellabs/wakapac@main/wakapac.min.js"></script>
</head>
<body>
<div id="app">
<h1>Hello {{ name }}!</h1>
<input data-pac-bind="value: name" placeholder="Enter your name">
<p>Character count: {{ name.length }}</p>
</div>
<script>
wakaPAC('#app', {
name: 'World'
});
</script>
</body>
</html>
That's it. Type in the input and watch the heading and character count update automatically. No compilation, no virtual DOM, no JSX - just reactive HTML.
Two-way binding between your data and the DOM. Change your data, the DOM updates. User interaction updates the DOM, your data changes.
<input data-pac-bind="value: email">
<p>You entered: {{ email }}</p>
Use familiar HTML with simple data attributes and mustache syntax:
<button data-pac-bind="click: save, enable: !saving">
{{ saving ? 'Saving...' : 'Save' }}
</button>
<ul data-pac-bind="foreach: items" data-pac-item="item">
<li>{{ item.name }}: {{ item.price }}</li>
</ul>
Derive values from your data that update automatically when dependencies change:
wakaPAC('#app', {
firstName: 'John',
lastName: 'Doe',
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
});
Build complex applications with parent-child component relationships and message passing:
// Parent component
wakaPAC('#parent', {
receiveFromChild(type, data) {
console.log('Child sent:', type, data);
}
});
// Child component - automatically detects parent
wakaPAC('#child', {
sendUpdate() {
this.notifyParent('update', { value: 42 });
}
});
Bind the same reactive abstraction template to multiple DOM elements using class, tag, or attribute selectors:
// Single element (ID selector) - returns single abstraction
const app = wakaPAC('#app', { count: 0 });
// Multiple elements (class selector) - returns array of abstractions
const counters = wakaPAC('.counter', { count: 0 });
// Tag selector - returns array
const sections = wakaPAC('section', { visible: true });
// Attribute selector - returns array
const widgets = wakaPAC('[data-widget]', { active: false });
Each matched element becomes an independent component instance with its own state:
<div class="counter">
<p>Count: {{count}}</p>
<button data-pac-bind="click: increment">+</button>
</div>
<div class="counter">
<p>Count: {{count}}</p>
<button data-pac-bind="click: increment">+</button>
</div>
<script>
// Creates two independent counters
wakaPAC('.counter', {
count: 0,
increment() { this.count++; }
});
// Each counter maintains separate state
</script>
WakaPAC identifies each component instance with a data-pac-id attribute. For parent-child communication with multiple instances, provide explicit IDs:
<div id="parent">
<div class="child" data-pac-id="child-1"></div>
<div class="child" data-pac-id="child-2"></div>
</div>
<script>
wakaPAC('#parent', {
updateChild() {
// Target specific child by data-pac-id
this.notifyChild('child-1', 'update', { value: 42 });
}
});
</script>
Access scroll position, viewport dimensions, network status, and more through reactive properties - no event listeners needed:
<button data-pac-bind="visible: browserScrollY > 300, click: scrollToTop">
↑ Back to Top
</button>
<div data-pac-bind="visible: !browserOnline" class="offline-banner">
You're offline - changes will sync when reconnected
</div>
Template expressions support JavaScript-like operations including ternaries, arithmetic, comparisons, and limited array methods:
<p>Total: ${{ items.reduce((sum, i) => sum + i.price, 0) }}</p>
<span>{{ count > 0 ? count + ' items' : 'Empty' }}</span>
<div data-pac-bind="class: { active: isSelected, disabled: !enabled }">
.length, .name) works normally, but method calls are restricted for security. Only these array methods are allowed: includes(), indexOf(), join(). See Interpolation for details.
| Feature | WakaPAC | Vue/React/Alpine |
|---|---|---|
| Build tools required | No | Yes (for full features) |
| Drop-in script tag | Yes | Limited (Alpine yes, Vue/React need builds) |
| Two-way binding | Built-in | Vue: yes, React: manual, Alpine: yes |
| Component hierarchy | Automatic parent detection | Manual props/events |
| Template syntax | Mustache + data attributes | Vue: similar, React: JSX, Alpine: similar |
| Learning curve | Low - HTML + JavaScript | Medium-high (concepts, ecosystem) |
WakaPAC is built on the Presentation-Abstraction-Control (PAC) pattern, which cleanly separates three concerns:
What the user sees and interacts with - your templates and DOM elements.
What your application knows - your data model, computed properties, and methods.
What keeps them in sync - WakaPAC's reactive layer that handles DOM updates, event routing, and dependency tracking.
The key difference from MVC: In PAC, the Presentation and Abstraction layers never communicate directly - all interaction goes through the Control layer. This means:
As a developer, you only work with Presentation (HTML) and Abstraction (JavaScript). The Control layer is WakaPAC itself, working behind the scenes.
WakaPAC is ideal for:
Consider alternatives if you need:
WakaPAC is a single JavaScript file with zero dependencies. The entire framework - reactive system, component hierarchy, expression parser, browser properties - is in one file you can drop into any project.
File sizes:
Comparison (all sizes minified + gzipped):
WakaPAC optimizes updates through:
This simple approach is efficient because DOM manipulation is the expensive operation - evaluation and comparison are fast.