ScopeDom is a Reactive DOM Orchestrator.
No Virtual DOM. No build step. Standard HTML attributes ($on-click, $if, $repeat) and text interpolation ({{expression}}) become reactive through native Web APIs — Proxies, WeakMaps, and MutationObservers. It works the moment the script tag loads.
- The DOM is the Source of Truth — No Virtual DOM layer. Your HTML is your application state.
- No Build Step — Plain JavaScript and HTML. No compiler. No transpiler. No bundler configuration.
- Declarative Reactivity — Expressions on real HTML elements and text.
$on-click,$if,$repeat,{{value}}. - Hierarchical Scopes —
$scopedeclares variable boundaries that walk up the DOM tree. Child elements inherit from their ancestors. - Immediate Observation — Uses MutationObserver to scan and connect elements as they're added to the DOM. No DOMContentLoaded wait.
Pronounced similarly to "Kingdom"
- Deep Reactivity — Objects, arrays, Maps, and Sets become reactive automatically. Infinite proxy depth with WeakRef cleanup prevents memory leaks.
- Hierarchical Scoping — Scope variables and methods cascade down the DOM tree. Both the
$scopeattribute and plugins create scoped contexts that child elements inherit from. - Zero Build — Plain HTML + JS. No compiler, no transpiler. Works from a single
<script>tag. - Plugin System — Auto-registering hooks, custom attributes (
$cloak,$if,$repeat,$parse), and reusable<template>fragments with identity-based DOM reconciliation.
<!-- The data-scopedom-init attribute auto-activates the engine -->
<script src="scopedom.umd.js" data-scopedom-init></script>
<!-- Optional: Enable {{expression}} syntax via the parse plugin -->
<script src="parse.umd.js"></script>
<!-- The $scope attribute defines encapsulated scope variables -->
<div $scope="{ count: 0 }">
<!-- Use $parse:text to enable interpolation within text nodes -->
<p $parse:text>Count is: {{count}}</p>
<!-- Increment count on click event -->
<button $on-click="count++">Increment</button>
</div>WIP
Find everything you need to master ScopeDom:
WIP
| Feature | Status |
|---|---|
| Core Engine | 🧪 Experimental / PoC |
| Core Plugins | 🧪 Experimental |
| Commercial Use | ❌ Not Ready |
| Hobbyist Use | 🧪 Experimental |
| Unit Tests | 🚧 In Progress |
ScopeDom is currently in an experimental / proof-of-concept stage. It is intended for research and hobbyist use and is not yet ready for production environments.
-
signalProxyAll=true(default) — auto-reactive objects, arrays, Maps and Sets. -
Infinite proxy depth — nested values get their own
signalProxywith WeakRef cleanup. -
Method wrappers (
push/pop/splice) trigger updates on mutation. -
Disable auto-proxification:
ScopeDom.init({ signalProxyAll: false }). -
Auto-reactive push:
cart.push({ name:'Milk', qty:2 });— reactivity fires automatically, no$update()call needed. EveryArray.prototypemutator is wrapped to auto-triggerchanged(). Push, pop, splice become reactive triggers for free. -
Deep nesting:
settings.user.pref.theme = 'light';— oneproxySignal()wraps the entire tree, no declarations per level. Every nested access creates its own signal on demand, so deeply nested objects get full reactivity with a single wrapping call.
-
$this(element),$parent,$previous,$next— DOM navigation. -
$("#nav")(document) /$$(".title")(element) — query selectors. -
$scope/$scopeParent/$scopeTop— scope chain access. -
$on/$off/$emit— event dispatch on scope or element. -
Scope inheritance:
<div $scope="{ user:{ name:'Alice Johnson' } }"><span $scope="{ firstName:user.name.split(' ')[0] }">{{firstName}}</span></div>. Child scope inheritsuserfrom parent via the scope chain, then computes localfirstName. Scope-walk-up means parent data flows down automatically — child locals stay isolated, no prop-drilling or$scopeParentqualification needed.
-
:rafexpression option batches DOM writes to 60fps. -
Compiled expressions cached per source element via WeakMap.
-
DOM caches use WeakMap / WeakSet — no leaks in long-running apps.
-
:rafkeystroke debounce:<input $on-input:raf="filter($this.value)" placeholder="Type to search...">— rapid keystrokes collapse into one 60fps update, no layout thrashing.:rafdefers events into a single requestAnimationFrame on every update.
-
$cloak— CSS cloak with anchor comment swapping. -
$if— Conditional rendering with match-case and sibling chains. -
$parse— Text interpolation ({{expression}}) and attribute binding. -
$repeat— Data-driven element repetition with identity-based DOM reconciliation. -
pipeExp— Pipes (item | $name(value)) transpiled to function calls. -
Register via
window.ScopeDomPluginsorScopeDom.pluginAdd(instance, PluginClass). -
$cloakapp loading:<div $cloak:dom="plugins('parse','if','repeat')">App content</div>. Eliminates FOUC — CSS hides the element until plugins boot, then swaps it in with anchor comments, so users never see raw or unstyled DOM. -
$repeattemplate:<template $repeat="items" $repeat:item="row"><div>{{row.name}}</div></template>. List rendering with identity-based DOM caching — ScopeDom usesmoveBeforeto reorder, reuses cached nodes, and only re-outputs removed/added items to keep scroll and focus intact. -
pipeExpinline:{{ item.price | $fmt.currency }}or{{ dateDue | $utils.formatDate }}. Developer-defined scope variables ($fmt,$utils, etc.) are callable via pipe syntax — bring your own helpers (underscore, moment, custom utilities) and use them in templates without manual function calls. ScopeDom doesn't provide built-in transform functions; the pipe is just syntactic bridge for whatever helpers you define in scope.
To submit a contribution, please create an issue or a pull request on the GitHub repository.
Note: Please ensure you run all existing tests after making any changes. All help, from code to documentation improvements, is greatly appreciated!
Copyright (c) 2026 Jason Sheppard @Jashepp.
All rights reserved. Licensing will transition to an open-source model once the project reaches a stable milestone.
Github Repository: https://github.com/Jashepp/ScopeDom