VinylJS Documentation

The Pure, Lossless Reactive Engine for Modern Web.
Bypass heavy bundler build steps and virtual DOM diffing overhead. VinylJS directly pinpoints and mutates browser Real DOM nodes with fine-grained $O(1)$ reactivity.

⚑ Quick Cheatsheet

Essential syntax and API mapping for quick reference:

Feature Syntax Description Example
Reactive Binding vj.bind(target, model) Binds Real DOM and plain object with $O(1)$ reactivity vj.bind('#app', { count: 0 });
Mustache Expression {{ expression }} Pinpoint text node update with expressions <span>{{ user.name }} ({{ count * 2 }})</span>
One-time Evaluation [[ expression ]] Renders once initially without creating reactive subscriptions <h1>[[ title ]]</h1>
Two-Way Form Input bind="prop:value" Bi-directional synchronization between input and state <input bind="userName:value">
Text Content bind="text: prop" Binds textContent directly to model property <span bind="text: description"></span>
Repeat List bind="items:list" Efficient array list synchronization with $index <div bind="items:list"><p>{{ $index }}: {{ name }}</p></div>
Component Tile bind="prop:tile" Mounts HTML tile fragment with native Scoped CSS <div bind="user:tile" vjTileSrc="tiles/userCard.html"></div>
DOM Hydration vj.hydrate(el) Extracts existing Real DOM values into a reactive proxy const state = vj.hydrate('#orderForm');
State Watcher state.$watch(path, cb) Observes changes on a property or wildcard ('*') state.$watch('count', (newVal, oldVal) => { ... });
Global Store vj.store(initialData) Global reactive store broadcast across all components const store = vj.store({ theme: 'dark' });
Network Direct Pipe vj.fetch(url).into(target) Pipes HTTP response directly into DOM or state vj.fetch('/api/user').into(state.user);
SPA Router new vj.PageManager(host) Mobile-grade SPA routing with lossless slot snapshots const router = new vj.PageManager('#routerHost');

πŸš€ Quick Start & CDN Usage

VinylJS requires no build tools (npm, vite, webpack). Choose the single bundle script tag that fits your project:

1-Line Bundle CDN Imports HTML
<!-- 1. Standard Production Reactive Engine (Full Core without DevTools) -->
<script src="https://vinyljs.com/vinyl.js"></script>

<!-- 2. Non-Reactive Lightweight Base Engine (Template, Router, HTTP only) -->
<script src="https://vinyljs.com/minivinyl.js"></script>

<!-- 3. Development Suite with DJ Time-Travel DevTools -->
<script src="https://vinyljs.com/vinyl.dev.js"></script>
Complete HTML5 Boilerplate (Standard vinyl.js) index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>VinylJS Quick Start</title>
  <!-- Load VinylJS via CDN -->
  <script src="https://vinyljs.com/vinyl.js"></script>
</head>
<body>
  <div id="app">
    <h1>{{ title }}</h1>
    <input type="text" bind="title:value" placeholder="Type a title...">

    <p>Count: <strong>{{ count }}</strong> (Double: {{ count * 2 }})</p>
    <button onclick="state.count++">+1 Increment</button>
    <button onclick="state.count = 0">Reset</button>
  </div>

  <script>
    const state = vj.bind('#app', {
      title: 'Welcome to VinylJS 🎧',
      count: 0
    });
  </script>
</body>
</html>
Global Edge CDN: Import any of the 3 bundles globally via https://vinyljs.com/vinyl.js with 1-year immutable edge caching.

πŸ—ΊοΈ Template & DOM Mapping (vinyl.map.js)

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

1. 4-Stage Pipeline bind Syntax

The 4-stage pipeline directly connects display formatting and input sanitization:

4-Stage Pipeline Syntax HTML
<!-- <element bind="[property]:[handlerType]:[getFn]:[setFn]"> -->

<!-- Formats display with getFn, sanitizes input with setFn -->
<input bind="price:value:formatWon:setWon"
       formatWon="return Number($val).toLocaleString() + ' KRW';"
       setWon="return Number(String(val).replaceAll(',', '').replace(' KRW', ''));">

<!-- Or using the pickup attribute for input sanitization -->
<input bind="price:value" pickup="cleanNumeric">

2. Repeat Lists (bind="items:list")

Synchronizes array data with DOM lists with built-in {{ $index }} support and $O(1)$ key tracking:

Repeat Lists HTML & JS
<div id="todoApp" bind="todos:list">
  <div class="todoItem">
    <span>#{{ $index + 1 }}</span>
    <input type="checkbox" bind="done:checked">
    <span bind="text: title" style="{{ done ? 'text-decoration:line-through' : '' }}"></span>
    <button onclick="deleteTodo($index)">Delete</button>
  </div>
</div>

<script>
  const state = vj.bind('#todoApp', {
    todos: [
      { title: 'Learn VinylJS', done: false },
      { title: 'Deploy to Cloudflare Pages', done: true }
    ]
  });

  // Standard array methods fully supported (push, pop, splice, sort, reverse)
  state.todos.push({ title: 'Write tests', done: false });
</script>

3. Component Tiles (vjTileSrc) & Scoped CSS

Loads external HTML fragments as modular components with native @scope CSS isolation:

Modular Tiles & Scoped CSS HTML
<!-- index.html -->
<div id="slot" bind="user:tile" vjTileSrc="tiles/userCard.html"></div>

<!-- tiles/userCard.html -->
<style scope>
  .card { padding: 16px; border-radius: 8px; background: #f1f5f9; }
  .name { color: #2563eb; font-weight: bold; }
</style>

<div class="card">
  <h3 class="name">{{ name }}</h3>
  <p>{{ bio }}</p>
</div>

<script>
  function init(el, model) {
    console.log('Tile mounted:', el, model);
  }
  function onDestroy(el) {
    console.log('Tile unmounted & resources cleaned up');
  }
</script>

4. DOM Hydration & Extraction

Hydrates server-side rendered (SSR) or existing static HTML forms into reactive proxies in one line:

Instant Form Hydration JavaScript
// Reads existing inputs and activates 2-way reactive proxy
const state = vj.hydrate('#orderForm');

🧠 Reactive Engine (vinyl.bind.js)

Powered by native JavaScript Proxies, vinyl.bind.js avoids full container re-renders and virtual DOM diffing. Only target DOM text nodes or form inputs are updated via an $O(1)$ direct node pointer (Map<Path, Set<Updater>>).

Reactive Binding & Watchers JavaScript
const state = vj.bind('#userProfile', {
  name: 'Alex',
  age: 28,
  skills: ['JavaScript', 'HTML']
});

// Mutating a property updates target Real DOM nodes instantly (1,876,173 Ops/sec)
state.name = 'Jordan';

// Watch specific property or wildcard '*'
const unwatch = state.$watch('user.profile.age', (newVal, oldVal) => {
  console.log('Age changed:', oldVal, '->', newVal);
});

// Access plain raw object
const plain = state.$raw;

// Global reactive store broadcast across all components
const store = vj.store({
  isLoggedIn: true,
  currentUser: { name: 'Admin' }
});

πŸ“± SPA Page Router (vinyl.page.js)

Provides mobile-grade view transitions, lossless form state restoration (Slot Snapshots), and AbortController-based concurrency protection (Latest-Wins).

Router Setup & Lifecycle Hooks JavaScript
const router = new vj.PageManager('#routerHost', {
  pageParamName: 'page', // Sync with URL search params (?page=home)
  maxStackSize: 20
});

// Register Class with Lifecycle Hooks
class ProfilePage {
  init(element, state, navSignal) {
    console.log('Page initialized with params:', state);
  }
  onFront(element) {
    console.log('Page brought to front (including back navigation)');
  }
  onLeave(element) {
    console.log('Navigating away from page');
  }
  onDestroy(element) {
    console.log('Page destroyed and resources cleaned up');
  }
}
router.register('profile', ProfilePage);

// Navigate & Back (losslessly restores scroll & form inputs)
router.go('profile', { id: 100 });
router.back();

🌐 Network Layer & .into() (vinyl.http.js)

High-performance standalone HTTP client built on native Fetch.

.into(target) Direct Response Pipe JavaScript
// 1. Pipe HTML partial directly into a DOM container
vj.fetch('/partials/userSummary.html').into('#userSummaryBox');

// 2. Pipe JSON response directly into a reactive state object
vj.fetch('/api/currentUser').into(state.user);

// Rapid consecutive clicks are automatically cancelled by AbortController
// guaranteeing Latest-Wins consistency without race conditions.

πŸ› οΈ Built-in DevTools (devtools.js)

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.
  • DOM Highlighter: Clicking a state property highlights its connected Real DOM element with a pulsating neon bounding box.

πŸ’‘ Best Practices & FAQ

01. Write Standard JavaScript
No special setter functions like setState(), ref.value, or signal(). Directly mutate state objects and arrays: state.count++, state.user.name = 'Jordan'. The DOM reacts automatically.
02. Full Array Mutation Support
Standard array methods (push, pop, shift, unshift, splice, sort, reverse) are fully tracked. $O(1)$ key reconciliation ensures optimal DOM element reordering without recreating nodes.
03. Defend Against Race Conditions
Use vj.fetch(url).into(state.target). Built-in AbortController ensures stale in-flight requests are cancelled when new ones fire, guaranteeing Latest-Wins consistency.
04. Instant Hydration of Static / SSR Forms
If your page has pre-rendered HTML form markup, activate it with const state = vj.hydrate('#formId'); in one line instead of creating separate models.
05. Automatic Memory Cleanup (Auto-Disposer)
When tile components are detached, onDestroy hooks trigger, and active timers (setInterval, setTimeout) and listeners are automatically garbage collected. Call el.unmap() or state.$unbind() to manually clean up.