npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dom-helpers-js

v2.10.0

Published

High-performance vanilla JavaScript DOM utilities with intelligent caching, reactive state management, and conditional rendering

Downloads

588

Readme

DOM Helpers JS

High-performance vanilla JavaScript DOM utilities with intelligent caching, reactive state management, conditional rendering, animations, async utilities, and a built-in SPA router.

npm version License: MIT jsDelivr


Features

  • Smart Element Access — ID, class, tag, and name-based DOM access with automatic caching
  • Reactive State — Vue-like reactivity with computed properties, watchers, and stores
  • Chainable Updates — Universal .update() method for updating any property at once
  • High Performance — Intelligent caching, fine-grained change detection, WeakMap tracking
  • Zero Dependencies — Pure vanilla JavaScript, no external libraries
  • Modular — Load only what you need via individual dist files or the full bundle
  • CDN Ready — Drop-in <script> tag or npm install
  • Native Enhance — Patches getElementById, getElementsBy*, querySelector/All to return enhanced elements
  • Form Module — Non-reactive form handling: values, validation, serialization, enhanced submission
  • Animation Module — CSS transition animations: fadeIn/Out, slideUp/Down, transforms, chains, stagger
  • Async Utilities — debounce, throttle, enhanced fetch, parallelAll, raceWithTimeout, sanitize
  • SPA Router — Client-side routing with hash/history mode, guards, transitions, declarative links (v2.9.0)
  • Module Loader — Load only the modules you need from CDN — dependencies resolved automatically, no bundler required (v2.10.0)

Installation

No install, no build step. Pick the approach that fits your project.


1. Classic (simplest)

One script tag, all globals available immediately on window:

<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.full-spa.min.js"></script>

<!-- unpkg -->
<script src="https://unpkg.com/[email protected]/dist/dom-helpers.full-spa.min.js"></script>
<script>
  Elements.title.update({ textContent: 'Hello!' });
  const state = ReactiveUtils.state({ count: 0 });
</script>

Globals: Elements, Collections, Selector, createElement, ReactiveUtils, ReactiveState, StorageUtils, Forms, Animation, AsyncHelpers, Router


2. Deferred (globals + non-blocking)

Same globals as Classic, but the script is deferred — page renders before the library executes. Two equivalent forms:

<!-- inline import -->
<script type="module">
  import 'https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.full-spa.esm.min.js';
</script>

<!-- or shorter — src attribute, identical result -->
<script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.full-spa.esm.min.js"></script>
<script type="module">
  Elements.title.update({ textContent: 'Hello!' });
  const state = ReactiveUtils.state({ count: 0 });
</script>

3. Modular (structured projects)

Build multi-file projects without a bundler. Load the library once in your entry point — all globals are available in every file with no repeated CDN URLs:

<!-- index.html -->
<script type="module" src="./app.js"></script>
// app.js — load library once as side-effect, then import your own modules
import 'https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.full-spa.esm.min.js';

import { initRouter } from './router.js';
import { createStore } from './store.js';

const store = createStore();
initRouter(store);
// store.js — no CDN import needed, ReactiveUtils already on window
export function createStore() {
  return ReactiveUtils.store(
    { user: null, cart: [] },
    { actions: { login(state, user) { state.user = user; } } }
  );
}
// router.js — no CDN import needed, Router already on window
export function initRouter(store) {
  Router.define([
    { path: '/',      view: '#home',  title: 'Home' },
    { path: '/about', view: '#about', title: 'About' },
    { path: '*',      view: '#404',   title: 'Not Found' }
  ]);
  Router.mount('#app').start({ mode: 'hash' });
}
// pages/home.js — any file, any depth — just use globals directly
export function mountHome(store, onCleanup) {
  Elements.title.update({ textContent: 'Home' });
  const unwatch = store.$watch('count', val => {
    Elements.counter.update({ textContent: val });
  });
  onCleanup(() => unwatch?.());
}

One CDN import in the entire project. Every other file stays clean.


4. Named Imports (explicit dependencies)

Import only the names your file actually uses. Each import comes from its own individual module file — only those files are downloaded.

<!-- index.html -->
<script type="module" src="./app.js"></script>
// app.js — import exactly what you need from each module file
import { Elements, Collections, Selector, createElement }
  from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.core.esm.min.js';
import { ReactiveUtils }
  from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.reactive.esm.min.js';
import { Router }
  from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.spa.esm.min.js';
import { StorageUtils }
  from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.storage.esm.min.js';

Pass them explicitly to the modules that need them:

// store.js — receives ReactiveUtils as a parameter
export function createStore(ReactiveUtils) {
  return ReactiveUtils.store(
    { user: null, cart: [] },
    { actions: { login(state, user) { state.user = user; } } }
  );
}
// router.js — receives Router as a parameter
export function initRouter(store, Router) {
  Router.define([
    { path: '/',      view: '#home',  title: 'Home' },
    { path: '/about', view: '#about', title: 'About' },
    { path: '*',      view: '#404',   title: 'Not Found' }
  ]);
  Router.mount('#app').start({ mode: 'hash' });
}

What gets downloaded: only the module files you import — not the full bundle.

Note: Enhancers, Conditions, and Native Enhance extend existing objects in place and have no new named exports. Load them as side-effects:

import 'https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.enhancers.esm.min.js';
import 'https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.conditions.esm.min.js';

For the full list of per-module named exports and combination examples, see ALL-CDN-LINKS.md.


5. Loader (pick-and-mix modules, no bundler)

Load only the modules you actually need — the loader handles dependency resolution, deduplication, and the correct load order automatically.

ESM Loader — for <script type="module"> environments:

<script type="module">
  import { load } from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.loader.esm.min.js';

  // Load only what you need — deps injected automatically
  await load('reactive', 'animation');

  // All requested globals are now available on window
  const state = ReactiveUtils.state({ count: 0 });
  await Elements.hero.fadeIn({ duration: 400 });
</script>

Classic Script Loader — for plain <script> tags (no type="module"):

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.loader.min.js"></script>
<script>
  DOMHelpersLoader.load('reactive', 'animation').then(function() {
    var state = ReactiveUtils.state({ count: 0 });
  });
</script>

Available module names: core, enhancers, conditions, reactive, storage, native-enhance, form, animation, async, spa

Dependency graph — these are resolved and injected automatically:

| Module | Requires | |---|---| | core | (standalone) | | storage | (standalone) | | spa | (standalone) | | enhancers | core | | conditions | core | | reactive | core | | form | core | | animation | core | | async | core | | native-enhance | core, enhancers |

// Argument order never matters — deps always load first
await load('native-enhance');            // auto-loads core → enhancers → native-enhance
await load('animation', 'form');         // auto-loads core once, then both in order
await load('reactive', 'conditions');    // conditions gains reactive features when both are present

Already-loaded detection: if a module was loaded by any other means (direct <script>, a previous load() call, or the full bundle), it is silently skipped — never fetched twice.


Quick Start

Classic

<!DOCTYPE html>
<html>
<body>
  <h1 id="title">Hello</h1>
  <button id="counter">Count: 0</button>
  <div class="card">Card 1</div>
  <div class="card">Card 2</div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.full-spa.min.js"></script>
  <script>
    // Access by ID
    Elements.title.update({ textContent: 'Welcome!', style: { color: 'blue' } });

    // Access by class
    Collections.ClassName.card.update({ style: { padding: '10px', background: '#f0f0f0' } });

    // Reactive counter
    const state = ReactiveUtils.state({ count: 0 });
    Elements.counter.addEventListener('click', () => {
      state.count++;
      Elements.counter.textContent = `Count: ${state.count}`;
    });
  </script>
</body>
</html>

Modular

<!-- index.html — load library once, then your entry point -->
<script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.full-spa.esm.min.js"></script>
<script type="module" src="./app.js"></script>
// app.js — all globals already available, just import your own modules
import { initUI } from './ui.js';

initUI();
// ui.js — no CDN import needed, globals are on window
export function initUI() {
  // Access by ID
  Elements.title.update({ textContent: 'Welcome!', style: { color: 'blue' } });

  // Access by class
  Collections.ClassName.card.update({ style: { padding: '10px', background: '#f0f0f0' } });

  // Reactive counter
  const state = ReactiveUtils.state({ count: 0 });
  Elements.counter.addEventListener('click', () => {
    state.count++;
    Elements.counter.textContent = `Count: ${state.count}`;
  });
}

Named Imports

<!-- index.html -->
<script type="module" src="./app.js"></script>
// app.js — import only what you need, from individual module files
import { Elements, Collections }
  from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.core.esm.min.js';
import { ReactiveUtils }
  from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.reactive.esm.min.js';

// Access by ID
Elements.title.update({ textContent: 'Welcome!', style: { color: 'blue' } });

// Access by class
Collections.ClassName.card.update({ style: { padding: '10px', background: '#f0f0f0' } });

// Reactive counter
const state = ReactiveUtils.state({ count: 0 });
Elements.counter.addEventListener('click', () => {
  state.count++;
  Elements.counter.textContent = `Count: ${state.count}`;
});

SPA in 10 lines

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/dom-helpers.full-spa.min.js"></script>
<script>
  Router.define([
    { path: '/',      view: '#home',  title: 'Home' },
    { path: '/about', view: '#about', title: 'About' },
    { path: '*',      view: '#404',   title: 'Not Found' }
  ]);

  Router.mount('#app').start({ mode: 'hash' });
</script>

Module Reference

Module 01 — Core

Foundation for all other modules. Provides Elements, Collections, Selector, createElement.

// Access by ID
Elements.myButton.update({
  textContent: 'Submit',
  style: { color: 'white', backgroundColor: '#007bff' },
  disabled: false,
  addEventListener: ['click', handleClick]
});

// Bulk update multiple IDs at once
Elements.update({
  title:    { textContent: 'New Title', style: { fontSize: '24px' } },
  subtitle: { textContent: 'New Subtitle' },
  button:   { disabled: false }
});

// Access by class name
Collections.ClassName.card.forEach(el => console.log(el.textContent));
Collections.ClassName.card.update({ style: { border: '1px solid #ccc' } });

// Access by tag
Collections.TagName.div.update({ style: { margin: '0' } });

// Access by name attribute
Collections.Name.username.update({ placeholder: 'Enter username' });

// querySelector / querySelectorAll
const header = Selector.query('#header');
header.update({ textContent: 'Updated' });

const inputs = Selector.queryAll('input[required]');
inputs.update({ classList: { add: 'required-field' } });

// createElement
const btn = createElement('button', {
  textContent: 'Click Me',
  className: 'btn',
  addEventListener: ['click', () => alert('Hi')]
});

Module 02 — Enhancers

Bulk DOM updates, shortcuts, indexed operations. Requires Core.

// Bulk text update for multiple IDs
Elements.textContent({ title: 'New Title', desc: 'New Description' });

// Bulk style update
Elements.style({ title: { color: 'blue' }, desc: { color: 'gray' } });

// Index-based updates
Collections.ClassName.items[0].update({ style: { fontWeight: 'bold' } });
Collections.ClassName.items[-1].update({ style: { opacity: '0.5' } }); // last item

Module 03 — Reactive

Vue-like reactivity: state, computed properties, watchers, forms, stores. Requires Core.

// Basic state
const state = ReactiveUtils.state({ count: 0, name: 'John' });

state.$watch('count', (newVal, oldVal) => {
  Elements.counter.update({ textContent: `Count: ${newVal}` });
});

state.count++; // triggers watcher

// Computed properties
state.$computed('doubled', function() { return this.count * 2; });
console.log(state.doubled); // auto-updated

// Store pattern
const store = ReactiveUtils.store(
  { user: null, cart: [] },
  {
    getters:  { isLoggedIn() { return this.user !== null; } },
    actions:  { login(state, user) { state.user = user; } }
  }
);
store.login({ name: 'Alice' });
console.log(store.isLoggedIn); // true

Module 04 — Conditions

Conditional rendering and state-based DOM visibility. Requires Core.

ConditionalRendering.apply(element, {
  condition: state.isLoggedIn,
  true:  { style: { display: 'block' } },
  false: { style: { display: 'none' } }
});

Module 05 — Storage

StorageUtils — localStorage/sessionStorage with namespacing, watching, and auto-save. Standalone.

StorageUtils.set('user', { name: 'Alice', role: 'admin' });
const user = StorageUtils.get('user');
StorageUtils.watch('theme', (newVal) => applyTheme(newVal));

Module 06 — Native Enhance

Patches native DOM methods to return enhanced elements. Requires Core + Enhancers.

// After loading native-enhance, native methods return enhanced elements:
const btn = document.getElementById('submit-btn');
btn.update({ textContent: 'Submit', disabled: false }); // .update() available

const cards = document.getElementsByClassName('card');
cards.update({ classList: { add: 'loaded' } });

const inputs = document.querySelectorAll('input[required]');
inputs.update({ classList: { add: 'required-field' } });

Module 07 — Form

Forms — proxy-based form access, values, validation, serialization, enhanced submission. Requires Core.

const form = Forms.contactForm; // access by id="contactForm"

// Read / write values
const data = form.values; // { name: 'Alice', email: '...', ... }
form.values = { name: 'Bob' };

// Validate
const result = form.validate({
  email:   { required: true, email: true },
  message: { required: true, minLength: 10 }
});
if (!result.isValid) return;

// Submit via fetch
await form.submitData({
  url:            '/api/contact',
  successMessage: 'Message sent!',
  resetOnSuccess: true,
  retryAttempts:  2
});

// Declarative — zero JS required
// <form id="contactForm" data-enhanced data-submit-url="/api/contact" data-success-message="Done!">

Module 08 — Animation

CSS transition animations on every element and collection. Requires Core.

await Elements.modal.fadeIn({ duration: 300, easing: 'ease-out-cubic' });
await Elements.notification.fadeOut();
await Elements.panel.slideToggle();
await Elements.card.transform({ translateX: '100px', scale: 1.05 });

// Animation chains
await Elements.toast.animate()
  .fadeIn({ duration: 250 })
  .delay(3000)
  .fadeOut({ duration: 200 })
  .play();

// Stagger collections
await Collections.ClassName.card.fadeIn({ duration: 400, stagger: 80 });

// Inside .update()
Elements.card.update({ textContent: 'Updated!', fadeIn: { duration: 500 } });

// Standalone
await Animation.fadeIn(document.querySelector('.hero'), { duration: 600 });

30 named easing curves — ease-out-back, ease-in-expo, ease-in-out-cubic, and more.


Module 09 — Async

AsyncHelpers — debounce, throttle, fetch, sanitize, and more. Requires Core.

// Debounce
const search = AsyncHelpers.debounce(async (q) => {
  const results = await AsyncHelpers.fetchJSON(`/api/search?q=${q}`);
  renderResults(results);
}, 350);

// Throttle
const onScroll = AsyncHelpers.throttle(() => updateProgress(), 16);
window.addEventListener('scroll', onScroll, { passive: true });

// Enhanced fetch — timeout, retry, exponential backoff
const data = await AsyncHelpers.fetch('/api/data', {
  retries: 3, retryDelay: 1000, exponentialBackoff: true, timeout: 10000
});

// Parallel requests
const [users, orders] = await AsyncHelpers.parallelAll([
  AsyncHelpers.fetchJSON('/api/users'),
  AsyncHelpers.fetchJSON('/api/orders')
]);

// Async button handler with automatic loading state
button.addEventListener('click', AsyncHelpers.asyncHandler(async () => {
  await saveData();
}, { errorHandler: (err) => alert(err.message) }));

// Sanitize user input
const safe = AsyncHelpers.sanitize(userInput, { removeScripts: true, removeEvents: true });

Module 10 — SPA Router

Client-side routing with hash/history mode, guards, transitions, and declarative components. Standalone.

Router.define([
  {
    path: '/',
    view: '#view-home',
    title: 'Home',
  },
  {
    path: '/user/:id',
    view: '#view-user',
    title: 'Profile',
    onEnter(params, query, onCleanup) {
      Elements.userName.update({ textContent: params.id });
      const timer = setInterval(tick, 1000);
      onCleanup(() => clearInterval(timer)); // auto-cleanup on leave
    }
  },
  {
    path: '/admin',
    view: '#view-admin',
    guard: () => isLoggedIn() || Router.go('/login'),
  },
  { path: '*', view: '#view-404', title: '404' }
]);

Router.beforeEach((to, from, next) => {
  console.log('Navigating to', to.path);
  next(); // must call next() to proceed
});

Router.afterEach((to) => {
  analytics.track(to.path);
});

Router.mount('#app').start({ mode: 'hash', scrollToTop: true });

// Navigate programmatically
Router.go('/user/42');
Router.back();
Router.replace('/login');

// Declarative links in HTML
// <router-link to="/about">About</router-link>
// <router-view></router-view>

Universal .update() Method

Every element and collection has .update() — a single method that handles any DOM property:

element.update({
  // Content
  textContent: 'Text',
  innerHTML: '<b>HTML</b>',

  // Properties
  value: 'input value',
  disabled: true,
  checked: true,

  // Styles
  style: { color: 'red', backgroundColor: '#f0f0f0' },

  // Classes
  classList: { add: ['a', 'b'], remove: 'old', toggle: 'active' },

  // Attributes
  setAttribute: { 'data-id': '123', 'aria-label': 'Button' },

  // Dataset
  dataset: { userId: '123', role: 'admin' },

  // Events
  addEventListener: ['click', handleClick],

  // Animation keys (when Animation module loaded)
  fadeIn:      { duration: 300 },
  slideDown:   { easing: 'ease-out-cubic' },
  transform:   { translateX: '100px', scale: 1.1 }
});

Size Reference

| Bundle | Gzipped | |--------|---------| | Full Bundle (full-spa) | ~49.7 KB | | Core | ~9.6 KB | | Enhancers | ~8.4 KB | | Reactive | ~11.5 KB | | Conditions | ~7.2 KB | | Storage | ~1.3 KB | | Native Enhance | ~2.2 KB | | Form | ~6.5 KB | | Animation | ~4.8 KB | | Async | ~3.9 KB | | SPA Router | ~3.9 KB |


Browser Support

  • Chrome / Edge — latest 2 versions
  • Firefox — latest 2 versions
  • Safari — latest 2 versions
  • Opera — latest 2 versions

License

MIT © DOMHelpers-Js


Links