๐ฎ Live Interactive Playground
Experience VinylJS's direct $O(1)$ Real DOM mutation in real-time. No compilation, no virtual DOM:
No build tools, no framework boilerplate.
Plain HTML, standard JavaScript objects, and $O(1)$ Real DOM reactivity.
state.count++ and the UI updates automatically.
Experience VinylJS's direct $O(1)$ Real DOM mutation in real-time. No compilation, no virtual DOM:
VinylJS requires no node_modules or build steps. Pick the script bundle suitable for your deployment:
<!-- 1. VinylJS Production Core (Required, Minified) --> <script src="https://vinyljs.com/vinyl.js"></script> <!-- 2. DJ Time-Travel DevTools Add-on (Optional, Load below vinyl.js during development) --> <script src="https://vinyljs.com/vinyl.debug.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>VinylJS App</title>
<!-- Load VinylJS via Global Edge CDN -->
<script src="https://vinyljs.com/vinyl.js"></script>
</head>
<body>
<div id="app">
<h1>{{ greeting }}</h1>
<input type="text" bind="greeting:value">
<p>Count: <strong>{{ count }}</strong></p>
<button onclick="state.count++">Increment</button>
</div>
<script>
const state = vj.bind('#app', {
greeting: 'Welcome to VinylJS ๐ง',
count: 0
});
</script>
</body>
</html>
Essential syntax, declarative directives, and JavaScript API quick references:
| Feature | Syntax / API | Description | Example |
|---|---|---|---|
| DOM Utilities | D.q / D.qa / D.make |
Zero prototype pollution DOM querying, creation, and manipulation suite | const el = D.q('#app'); |
| Manual Mapping | vj.map(el, data) |
Direct 1-time DOM template mapping without reactive proxy overhead | vj.map('#app', { title: 'Hello' }); |
| Reactive Binding | vj.bind(el, data) |
Binds Real DOM and plain JS object with fine-grained $O(1)$ reactivity | const state = vj.bind('#app', { count: 0 }); |
| DOM Extraction | vj.extract(el) |
Reads existing Real DOM values and forms into a plain JS data object | const data = vj.extract('#myForm'); |
| Reactive Extraction | vj.extract(el, true) |
Scans DOM markup and directly instantiates a 2-way reactive Proxy | const state = vj.extract('#myForm', true); |
| Disconnect Binding | state.$unbind() / vj.unbind(el) |
Detaches reactive subscriptions and listeners while keeping DOM intact | state.$unbind(); |
| Raw Object Access | state.$raw |
Returns unproxied plain JavaScript object without observer overhead | const raw = state.$raw; |
| Live Mustache | {{ expression }} |
Pinpoint text node evaluation subscribing directly to mutated paths | <span>{{ user.name }} ({{ count * 2 }})</span> |
| One-Time Evaluation | [[ expression ]] |
Evaluates once on initial mount without registering reactive subscribers | <h1>[[ initialTitle ]]</h1> |
| Shorthand Bind | bind="[Model]" |
Smart auto-attribute mapping (input: value, checkbox: checked, tag: text) | <input bind="userName"> |
| Explicit Target | bind="[Model]:[Attribute]" |
Directly binds model property to specified DOM attribute or handler | <input bind="price:value"> |
| Full MAP Binding | bind="[Model]:[Attribute]:[Pipe]" |
Model, DOM Attribute, and display Formatter Pipe 3-stage standard | <input bind="price:value:formatWon"> |
| Input Sanitizer | pickup="fnName" |
Transforms/sanitizes user input before committing to reactive state | <input bind="price:value" pickup="cleanNumeric"> |
| Repeat List | bind="items:list" |
$O(1)$ key reconciliation array synchronization with built-in {{ $index }} |
<div bind="todos:list"><p>{{ $index }}: {{ title }}</p></div> |
| Component Tile | bind="prop:tile" vjTileSrc="..." |
Mounts reusable HTML component with browser-native Scoped CSS (@scope) | <div bind="user:tile" vjTileSrc="tiles/card.html"></div> |
| Property Watcher | state.$watch(path, cb) |
Observes changes on a specific dot path or wildcard ('*') | state.$watch('count', (newVal, oldVal) => { ... }); |
| Global Store | vj.store(initialData) |
Global reactive store broadcast across all component tiles and pages | const store = vj.store({ theme: 'dark' }); |
| Direct Network Pipe | vj.fetch(url).into(target) |
Streams HTTP JSON directly into DOM or state with Latest-Wins protection | vj.fetch('/api/user').into(state.user); |
| SPA Page Router | new vj.pagemanager(host) |
Mobile-grade router with 100% lossless form & scroll snapshot restoration | const router = new vj.pagemanager('#routerHost'); |
| DJ DevTools Runtime | vinyl.debug.js |
Floating inspector for subscriber trees, event streams, and time-travel | <script src="vinyl.debug.js"></script> |
The core template engine that declaratively binds DOM structures, attributes, and text nodes using
bind attributes and mustache expressions ({{ }}).
VinylJS follows the intuitive 3-stage MAP standard. Zero configuration, zero confusion:
<!-- 1. Shorthand (Smart Auto-Attribute based on tag type) --> <input bind="userName"> <!-- Model: userName, Attribute: auto 'value' --> <input type="checkbox" bind="isVip"> <!-- Model: isVip, Attribute: auto 'checked' --> <span bind="notice"></span> <!-- Model: notice, Attribute: auto 'text' --> <!-- 2. Explicit Model : Attribute --> <input bind="price:value"> <div bind="items:list"></div> <span bind="description:html"></span> <!-- 3. Full MAP Binding (Model : Attribute : Pipe) --> <input bind="price:value:formatWon"> <!-- Dedicated input sanitization via pickup attribute --> <input bind="price:value" pickup="cleanNumeric">
<style scope>
.card { padding: 18px; border-radius: 10px; background: #0f172a; border: 1px solid #334155; }
.name { color: #38bdf8; font-weight: bold; }
</style>
<div class="card">
<h3 class="name">{{ name }}</h3>
<p>{{ role }}</p>
</div>
<script>
function init(el, model) {
console.log('Mounted with model:', model);
}
function onDestroy(el) {
console.log('Unmounted: Timers, styles, and listeners auto-cleaned');
}
</script>
Powered by native ES6 Proxies and direct node subscriber pointers (Map<Path, Set<Updater>>). Mutating state only notifies the exact text nodes or inputs subscribed to that path.
const state = vj.bind('#app', {
user: { name: 'Taylor', age: 26 },
tags: ['JavaScript', 'WebEngine']
});
// 1. Direct mutation triggers pinpoint DOM update instantly
state.user.name = 'Jordan';
// 2. Full native array mutation methods tracked
state.tags.push('Reactive');
state.tags.splice(0, 1);
// 3. Deep property watcher with wildcard '*' support
const unwatch = state.$watch('user.age', (newVal, oldVal) => {
console.log(`Age updated from ${oldVal} to ${newVal}`);
});
// 4. Access raw unwrapped plain JS object
const rawObject = state.$raw;
// 5. Global reactive store accessible everywhere
const store = vj.store({ theme: 'dark', session: null });
Mobile-grade view transitions, automatic HTML file loading, lossless form state restoration (Slot Snapshots), and
AbortController-based concurrency protection (Latest-Wins).
Simply pass the HTML file URL to router.go(). The router automatically fetches the markup, processes Scoped CSS, and executes lifecycle hooks defined in its <script>:
// 1. Initialize router with container host
const router = new vj.pagemanager('#routerHost', {
maxStackSize: 20
});
// 2. Navigate directly to HTML file (auto-loaded, scoped, and cached)
router.go('pages/profile.html', { id: 100 });
router.back();
<!-- pages/profile.html -->
<h1>Profile: {{ id }}</h1>
<input bind="userName:value">
<script>
function init(el, state, navSignal) {
console.log('Page loaded with param:', state);
}
function onFront(el) {
console.log('Page brought to front');
}
function onDestroy(el) {
console.log('Page destroyed');
}
</script>
When single-file bundling or in-memory components are preferred, register inline templates or classes directly without external file requests:
// Register inline template string
router.register('home', '<h1>Home Page</h1>');
// Or register component class with render() and lifecycle hooks
class ProfilePage {
render(state) {
return '<h1>Profile: {{ id }}</h1>';
}
init(el, state, navSignal) {
console.log('Page init with param:', state);
}
onDestroy(el) {
console.log('Page destroyed');
}
}
router.register('profile', ProfilePage);
router.go('profile', { id: 100 });
High-performance standalone HTTP client built on native Fetch with built-in concurrency cancellation.
// 1. Pipe HTML partial directly into a DOM container
vj.fetch('/partials/summary.html').into('#summaryBox');
// 2. Pipe JSON response directly into a reactive state object
vj.fetch('/api/user/profile').into(state.user);
// Rapid consecutive requests are automatically cancelled via AbortController
// guaranteeing Latest-Wins consistency without race conditions.
Zero-configuration floating developer tools overlay for real-time state inspection and time travel:
vinyl.debug.js), 100% tree-shaken from production builds.setState(), ref.value, or signal() are required. Directly mutate standard JavaScript properties: state.count++ or state.user.name = 'Jordan'.
WeakRef. When a DOM element is removed from the document, its subscribers are automatically pruned upon notification dispatch. Component tiles have an Auto-Disposer that clears active intervals, timeouts, and listeners automatically.
vj.extract(formEl) reads pre-rendered values directly off the Real DOM and instantly converts them into a two-way synchronized reactive proxy in 1 line.
vj.config.scriptMode = 'seal', the engine operates in strict mode without requiring unsafe-eval, making it ideal for high-security enterprise environments.