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

@lekoala/web-utils

v0.3.0

Published

Small zero-dependency browser utilities

Readme

@lekoala/web-utils

Small, zero-runtime-dependency browser utilities shared by lightweight web components and demos.

The package is intentionally not a general-purpose utility library.

Principles

  • Pure ESM.
  • Zero runtime dependencies.
  • No prototype patches, globals, observers, or import-time DOM access.
  • Source-first: consumers bundle/transpile for their own browser target.
  • JSDoc is the source of truth; .d.ts files are generated with TypeScript.
  • Explicit subpath exports: there is deliberately no root @lekoala/web-utils barrel.
  • sideEffects: false so bundlers can safely tree-shake.
  • DOM helpers follow native argument order: target first.
  • No implicit passive event listeners: behavior that can affect preventDefault() is explicit.
  • Prefer small native wrappers over mini-frameworks.

API policy

The public API is only the subpaths declared in package.json exports and the symbols each module exports. Everything else is internal and can change at any time.

New helpers should be generic, useful across projects, keep a small surface, follow the existing conventions, and come with tests. Contextual helpers and third-party adapters stay in their consumer project.

Browser vs SSR

Importing any module is SSR-safe: no module touches the DOM, globals, or observers at import time. A few helpers are browser-only at call time when their implicit default is used.

| Subpath | Browser-only when calling without | |---------------------|-------------------------------------------------------| | dom/* | an explicit root / doc / parent where supported | | custom-elements | an explicit registry | | url.isExternalUrl | an explicit base (needs location) | | form | works in any runtime with FormData |

Install

bun add @lekoala/web-utils

or:

npm install @lekoala/web-utils

Usage

import { debounce } from "@lekoala/web-utils/timing";
import { camelToDash, interpolate } from "@lekoala/web-utils/string";
import { appendParams } from "@lekoala/web-utils/url";
import { qs, qsa } from "@lekoala/web-utils/dom/query";
import { on, dispatch } from "@lekoala/web-utils/dom/events";

For a component that uses several DOM primitives, a DOM-only convenience barrel is available:

import { ce, html, on, qs, setAttr } from "@lekoala/web-utils/dom";

Prefer the narrower subpath when a module only needs one domain.

Safe HTML templates

html is deliberately much smaller than a rendering library. It exists for the common case where a handful of document.createElement() calls make simple markup hard to read.

import { html } from "@lekoala/web-utils/dom/html";

const row = html.one`
  <span class="person">
    <span>${initials(item.label)}</span>
    <strong>${item.label}</strong>
    <span>${item.data.role} · ${item.data.team}</span>
  </span>
`;

The static template is parsed once and cached. Dynamic ${...} values never enter innerHTML: they are inserted afterwards as text, DOM nodes, or flattened iterables of those values.

const value = '<img src=x onerror="alert(1)">';
const fragment = html`<strong>${value}</strong>`;
// value is text, not markup

Supported child values are intentionally boring:

  • strings, numbers and other scalar values become text;
  • Node / DocumentFragment values are inserted directly;
  • arrays and other iterables are flattened recursively;
  • null, undefined and false render nothing.

The normal tag returns a DocumentFragment:

container.append(html`
  <ul>
    ${items.map((item) => html`<li>${item.label}</li>`)}
  </ul>
`);

html.one returns the single root Element and rejects multiple roots:

const button = html.one`<button>${label}</button>`;
button.disabled = disabled;
button.addEventListener("click", save);

That last split is intentional: templates describe structure and child content; native DOM APIs still handle attributes, properties and events. Interpolations inside tags or attributes are rejected instead of introducing a second template grammar.

// Not supported on purpose:
html`<button class="${variant}">${label}</button>`;

// Keep it native:
const button = html.one`<button>${label}</button>`;
button.className = variant;

html.one is honestly typed as Element: html.one`<svg>…</svg>` is valid. When the caller needs an HTMLElement (dataset, hidden, focus()…), use html.element, which checks the root at runtime and throws otherwise — the return type is a guarantee, not a cast:

const card = html.element`<article>${title}</article>`;
card.dataset.state = state;

Embedded JSON payloads

readJsonScript reads server-embedded data, typically <script type="application/json">, with one uniform behavior:

import { readJsonScript } from "@lekoala/web-utils/dom/json";

const payload = readJsonScript(root.querySelector("[data-events]")); // unknown
  • a missing element or blank content returns undefined (distinct from an explicit null payload);
  • invalid JSON throws a SyntaxError naming the element, with the parser error as cause;
  • the result is unknown on purpose: validate it where its shape is known.

Public subpaths

| Subpath | Main helpers | |-------------------|---------------------------------------------------------------------------------------| | array | toArray | | string | camelize, dashToCamel, camelToDash, interpolate, stripDiacritics, slugify | | value | toBool, toInt, toFloat, stringToValue | | timing | debounce (cancel, flush), debounceLeading, throttle, debounceFrame | | url | appendParams, isExternalUrl | | form | formDataToObject | | dom/query | qs, qsa, byId | | dom/create | ce, insertAfter, parseHTML | | dom/html | html, html.one, html.element | | dom/json | readJsonScript | | dom/attrs | attribute/dataset helpers plus declarative value parsers | | dom/classes | class helpers | | dom/events | on, off, once, dispatch | | dom/ready | domReady | | custom-elements | defineCustomElement |

Attribute parsing

The DOM attribute module also contains the small parsers duplicated by consumer components:

import {
  parseBooleanAttribute,
  parseEnumAttribute,
  parseIntegerListAttribute,
} from "@lekoala/web-utils/dom/attrs";

They remain pure functions: they parse values but do not impose component defaults or read component state.

String conversion edge cases

The naming helpers are intentionally narrow. Verified behavior:

| Input | camelize | dashToCamel | camelToDash | |-------------|------------|---------------|---------------| | foo-bar | fooBar | fooBar | — | | foo_bar | fooBar | foo_bar | — | | foo.bar | fooBar | foo.bar | — | | URL value | urlValue | — | — | | hello- | hello- | — | — | | fooBar | — | — | foo-bar | | fooBAR | — | — | foo-bar | | URLValue | — | — | urlvalue | | fooBarURL | — | — | foo-bar-url |

Development

The repository follows the same lightweight Bun + Biome + checked-JS setup as the sibling component repositories.

bun install
bun run verify

Useful individual commands:

bun run test
bun run typecheck
bun run types
bun run lint
bun run format

bun run types generates the published declarations in types/ from the JSDoc-annotated JavaScript source.