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

@verajs/autoloader

v0.2.0

Published

A super small but powerful autoloader for web components that works great with VeraJs but can be used without.

Readme

@verajs/autoloader

Lazy component loading by tag name — 1.64 KB gzipped, no dependencies, no build step required.

When an undefined custom element appears inside a component marked autoloader, its module is fetched and defined. No manifest, no import list, no bundler plugin: the tag name is the module name.

Discovery is observed, not polled. A marked component is watched once, so an element is found whenever it enters the DOM — put there by a render, by innerHTML, by a third-party widget, or by having been in the HTML file all along.

npm i @verajs/autoloader

Quick start

import { wire } from '@verajs/core';
import { autoloader } from '@verajs/autoloader';

const autoload = autoloader(import.meta.url, 'components');
wire(autoload);            // watch every component as it renders
autoload();                // and scan whatever is already on the page
<my-page autoloader>
  <user-card></user-card>   <!-- fetches components/user-card.js the first time it appears -->
</my-page>

rootDir is almost always import.meta.url — every component URL is resolved relative to it, and omitting componentsDir puts components beside the entry file.

autoloader returns one function with three shapes, plus two helpers:

| | | | --- | --- | | autoload() | scan the page for [autoloader] hosts and watch them | | autoload(element) | watch that component | | autoload(shadowRoot) | watch that root — no attribute needed, handing it over is the opt-in | | autoload.url(tag) | the absolute URL it would fetch — throws if it would resolve outside rootDir | | autoload.retry(element) | forget that this element's tag failed, and try again |

The instance is also its own wire descriptor — it carries on: 'render' at priority 75, so wire([renderer, autoload]) configures and installs in one call, and the scan runs after the render that produced the markup. It replaced setAutoloader, a bespoke registrar that lived in @verajs/inserts; every module now hands wire a descriptor, and this one is no exception.

Watching is idempotent: calling it twice on the same root does nothing the second time, which is why it can be handed every component on every render.

Creating an autoloader does nothing on its own — no scanning, no listeners. autoload() is how a hand-written page works with no framework involved at all, and you can call it again whenever new markup lands:

<script type="module">
  import { autoloader } from '@verajs/autoloader';
  autoloader(import.meta.url, 'components')();
</script>

<div autoloader>
  <user-card></user-card>   <!-- loads, though nothing here renders -->
</div>

A module script is deferred, so it runs after the page has parsed and the markup above is already there.

Attributes

| | | | --- | --- | | autoloader | on a component, scan its tree for undefined elements. Opt-in, per component | | autoload-dir | on an element, load it from a different directory | | autoload-ignore | on an element, leave it alone. Applies to that element only, not its subtree |

Scanning is per-component on purpose: the insert scans the element's own tree, not the shadow roots of its descendants, so each component that hosts lazily-loaded children carries the attribute.

It is autoload-dir, never HTML's global dirdir="rtl" on any internationalised page would otherwise have silently redirected component loading.

All three are watched, not just read once. Marking a component autoloader after it already has a shadow root reaches inside it; repointing autoload-dir after a failed attempt tries the new location; removing autoload-ignore lets an element load. None of that needed a re-insertion, which is not a thing that happens.

Options

autoloader(import.meta.url, 'components', { extension: '.ts' });

extension defaults to .js, with or without the leading dot. Set .ts so a TypeScript dev server can autoload sources directly — it will not serve foo.js when only foo.ts exists.

resolve(tag, dir) replaces URL building entirely, for a layout dir/tag.ext cannot express:

autoloader(import.meta.url, 'components', {
  resolve: (tag, dir) => `${dir}/${tag}/${tag}.js`,   // components/user-card/user-card.js
});

dir is the element's autoload-dir if it has one, otherwise componentsDir. The result is still resolved against the entry file and still bounded by it, so a custom layout cannot reach anywhere the default one could not.

Bounding

Every resolved URL must stay inside the entry file's own directory, and one that does not is refused with a console error rather than fetched. Tag names cannot carry a / — the HTML parser will not produce one — but autoload-dir is free text on an element, and turning markup into a module URL is exactly the thing that needs bounding.

<x-y autoload-dir="https://example.com/x"></x-y>   <!-- refused: absolute -->
<x-y autoload-dir="//example.com/x"></x-y>         <!-- refused: protocol-relative -->
<x-y autoload-dir="../../../"></x-y>               <!-- refused: escapes upward -->
<x-y autoload-dir="components?v=2"></x-y>          <!-- refused: ? ends the path -->

A directory cannot contain ? or #. Both end the path, so the tag name would land in the query or the fragment: autoload-dir="components?v=2" requests components with x-y.js inside the query string, and the component file is never asked for. That is a wrong module rather than a missing one, so it is refused rather than left to 404 — and note that containment does not catch it, because such a URL is genuinely inside the entry's directory.

To cache-bust, use resolve, which owns URL building and can put the query where it belongs:

autoloader(import.meta.url, 'components', { resolve: (tag, dir) => `${dir}/${tag}.js?v=2` });

A custom resolve is checked the same way. rootDir is your own code and is trusted; everything derived from the DOM is not.

The check lives in url(), which is the one place a URL is built — so it holds for the loader and for you. autoload.url(tag, element) throws rather than returning something the loader would refuse: it is documented for preloading, and a <link rel="modulepreload"> pointed at another origin is the exact fetch this module declines to make. Discovery catches the throw, reports it once and moves on, so a hostile attribute costs a console line rather than a broken page.

One attempt per URL, one module per tag

A URL is tried once per page load: a component that 404s logs once and is not retried until reload.

A tag is loaded once too, which is a different question. <x-y> and <x-y autoload-dir="alt"> are two URLs for one tag; both used to import, and the second module's customElements.define('x-y') threw. A tag can only be defined once, so the second location could never have helped — it is tried only if the first attempt fails.

When a component never arrives

A failed load logs, and dispatches vera:autoload-error on the element — bubbling and composed, with { tag, src, error, element } on detail. That is the hook for rendering around a component that is not coming:

document.addEventListener('vera:autoload-error', ({ detail }) => {
  report(detail.error);
  detail.element.replaceWith(fallback());
});

An element that has not arrived yet needs no hook — it is simply un-upgraded, which is what :not(:defined) in your CSS is for.

Warming and recovering

autoload.url(tag) gives you the URL and gets out of the way, so warming is whatever you want it to be — modulepreload for a component you know is coming, a lower-priority prefetch, priming a service worker cache:

const link = document.createElement('link');
link.rel = 'modulepreload';
link.href = autoload.url('user-card');
document.head.appendChild(link);

It is also the fastest answer to why is it fetching that? — the question this module gets asked most.

autoload.retry(element) forgets that an element's tag failed and tries it again. A failed load is otherwise permanent for the page, which is right for a component that does not exist and wrong for one lost to a dropped connection. vera:autoload-error hands you the element:

addEventListener('vera:autoload-error', ({ detail }) => {
  if (navigator.onLine) return;
  addEventListener('online', () => autoload.retry(detail.element), { once: true });
});

Reaching a shadow root nothing marked

An observer cannot cross a shadow boundary, so a component that never marks itself is out of reach — including a third-party one holding tags of yours. Hand the root over directly and it is watched; passing it is the opt-in, so no attribute is required:

autoload(someWidget.shadowRoot);

Server-rendered pages

Markup from @verajs/ssr arrives as declarative shadow DOM, parsed before any script runs, with nothing rendering it. autoload() reaches through those shadow roots the same as any other, so a server-rendered page loads its components with no framework involvement:

<ssr-shell autoloader>
  <template shadowrootmode="open">
    <ssr-child></ssr-child>       <!-- found by autoload() -->
  </template>
</ssr-shell>

What it does not do

Watching does not cross into a child component's shadow root on its own — a MutationObserver cannot, by design — so a child that hosts lazily-loaded elements of its own marks itself autoloader, or has its root handed over as above. Vera components get this automatically, because the 'render' insert offers each one up as it renders.

What it costs

One MutationObserver object watches every marked root, and a mutation only notifies observers on its own ancestor chain — so watched subtrees that are not the ones changing cost nothing. Measured in Chromium: 1 000 registrations left unrelated DOM work at 0.900 µs against 0.933 µs with none, and watching a root adds ~0.6 µs per mutation batch into it.

Observers are never disconnected, and do not need to be: a removed node observed by a live observer is still collectable — measured in Chromium with --expose-gc, and pinned by tests/browser/memory.test.js. (jsdom disagrees, and disagrees even after disconnect(), which is its own bookkeeping rather than the observer contract.)

This replaced a rescan of each marked component's whole tree on every render, which cost 0.46 µs for a 10-node component, 3.4 µs at 100 nodes and 32.5 µs at 1 000 — on every render, for the life of the page, long after everything had loaded. Watching document instead of each marked root would have been the expensive shape: it taxes every DOM mutation in the app by ~47%, because every mutation is inside it.

For AI assistants — and anyone who wants the whole API on one page

The repository root's llms.txt is the complete, hand-maintained API reference for every package, written to be pasted into a model's context window: full export tables, the buildless CDN and JSX recipes, semantics that differ from other frameworks, and the mistakes that come up most. Its recipes are executed by the test suite, so they stay honest.

License

MIT

directiveLoader — lazy directives by convention

The same idea as component autoloading, pointed at @verajs/directives: an unknown data-vd-<name> resolves to one module URL — {base}/{dir}/{name}.js — and the module registers itself, exactly as an autoloaded component calls customElements.define.

import { wire } from '@verajs/core';
import { directiveLoader } from '@verajs/autoloader';

wire([directiveLoader(import.meta.url, 'directives')]);

A lazy module:

// directives/sparkle.js
import { wireDirectives } from '@verajs/directives';
wireDirectives({ name: 'sparkle', value: 'none', setup(el) { /* … */ } });

Module caching is the contract: the bare @verajs/directives import resolves to the same URL the page loaded (bundler or import map), so the module wires the page's own registry.

Options — all factory-only JavaScript, never markup: extension (default .js), alias (name → module path, for grouped layouts: { paint: 'motion.js', split: 'motion.js' }), and resolve (replaces path building; containment against the entry's directory still applies). The name itself is markup input and is allowlisted to [a-z][a-z0-9-]* before any URL exists — a name outside the grammar is declined, never fetched. directiveLoader(...).url(name) returns the URL it would fetch, for <link rel="modulepreload"> warming and for answering "why is it fetching that". One attempt per name per page load; the engine memoizes the refusals.