Real DOM • Zero-Build

Direct Reactive DOM Binding
for JavaScript

No build tools, no framework boilerplate.
Plain HTML, standard JavaScript objects, and $O(1)$ Real DOM reactivity.

<script src="https://vinyljs.com/vinyl.js"></script>
๐Ÿš€
Zero-Build & Zero-Dependency
No npm, no bundlers, and zero config files. Drop one script tag into any HTML page and build reactive apps immediately.
๐Ÿ’ก
Natural Plain JavaScript
No compiler or JSX required. Mutate state with plain JavaScript like state.count++ and the UI updates automatically.
โšก
$O(1) Real DOM Pinpoint
Zero Virtual DOM diffing. State changes directly update target text nodes and form inputs via direct node pointers.
๐Ÿงน
Zero Memory Leaks
Built-in Auto-Disposer. When elements detach from the DOM, timers and event listeners are automatically freed with zero leaks.

๐ŸŽฎ Live Interactive Playground

Experience VinylJS's direct $O(1)$ Real DOM mutation in real-time. No compilation, no virtual DOM:

LIVE RUNTIME
// HTML Template <div id="counterDemo"> <h3>Count: {{ count }}</h3> <p>Double: {{ count * 2 }} | Triple: {{ count * 3 }}</p> <button onclick="state.count++">+1 Increment</button> <button onclick="state.count = 0">Reset</button> </div> // JavaScript const state = vj.bind('#counterDemo', { count: 1 });
Count: {{ count }}
Double: {{ count * 2 }} | Triple: {{ count * 3 }}

๐Ÿš€ Quick Start & 3 Bundles

VinylJS requires no node_modules or build steps. Pick the script bundle suitable for your deployment:

1-Line Global CDN Bundles
<!-- 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>

Complete HTML5 Starter Boilerplate

index.html
<!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>

โšก Quick Cheatsheet

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>

๐Ÿ—บ๏ธ vinyl.map.js โ€” DOM Mapping & Scoped CSS

The core template engine that declaratively binds DOM structures, attributes, and text nodes using bind attributes and mustache expressions ({{ }}).

The MAP Binding Standard (Model : Attribute : Pipe)

VinylJS follows the intuitive 3-stage MAP standard. Zero configuration, zero confusion:

MAP Standard: bind="[Model]:[Attribute]:[Pipe]"
<!-- 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">

Component Tiles & Native Scoped CSS

Tile Component (`tiles/userCard.html`)
<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>

๐Ÿง  vinyl.bind.js โ€” $O(1)$ Reactive Proxy Core

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.

JavaScript Reactivity
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 });

๐Ÿ“ฑ vinyl.page.js โ€” SPA Page Router

Mobile-grade view transitions, automatic HTML file loading, lossless form state restoration (Slot Snapshots), and AbortController-based concurrency protection (Latest-Wins).

1. Automatic HTML File Routing (Recommended)

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>:

main.js & pages/profile.html
// 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>

2. In-Memory Registration (Bundled / Single-File)

When single-file bundling or in-memory components are preferred, register inline templates or classes directly without external file requests:

Inline Template or Class Registration
// 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 });

๐ŸŒ vinyl.http.js โ€” Network Layer & .into()

High-performance standalone HTTP client built on native Fetch with built-in concurrency cancellation.

.into(target) Direct Response Pipe
// 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.

๐Ÿ› ๏ธ vinyl.debug.js โ€” DJ Time-Travel DevTools

Zero-configuration floating developer tools overlay for real-time state inspection and time travel:

  • DJ Time-Travel Deck: Drag the slider like scratching a vinyl record to rollback and restore past component state snapshots losslessly.
  • Real-time Binding Inspector: Inspect all active subscriber maps and direct node pointers visually.
  • Zero Production Footprint: Completely separated runtime module (vinyl.debug.js), 100% tree-shaken from production builds.

๐Ÿ’ก Best Practices & FAQ

01. Direct State Mutation vs Setter Functions
No special setter functions like setState(), ref.value, or signal() are required. Directly mutate standard JavaScript properties: state.count++ or state.user.name = 'Jordan'.
02. How does Memory Management work without Leaks?
VinylJS holds DOM elements using 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.
03. How does Hydration differ from React/Vue?
Instead of downloading megabytes of virtual DOM bundles to recreate existing HTML, 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.
04. Can VinylJS run in Content Security Policy (CSP) environments?
Yes. By setting vj.config.scriptMode = 'seal', the engine operates in strict mode without requiring unsafe-eval, making it ideal for high-security enterprise environments.