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

@wcstack/autoloader

v1.3.17

Published

Auto-detect and dynamically import undefined custom elements via Import Maps. Zero dependencies, buildless.

Downloads

472

Readme

@wcstack/autoloader

What if custom elements loaded themselves?

Imagine a future where you just write a custom element tag and the browser figures out where to find it. No import, no customElements.define(), no registration boilerplate. You write the tag, it loads.

That's what <wcs-autoloader> explores. One CDN import, zero dependencies, powered by Import Maps.

Features

Basic Features

  • Auto Detection & Loading: Detects undefined custom element tags and automatically import()s them.
  • Dynamic Content Support: Instantly detects elements added later via innerHTML or appendChild.
  • Zero Config / Buildless: Works with browser standard features only; no bundler configuration required.
  • Zero Dependencies: Lightweight with no external dependencies.

Unique Features

  • Import Map Extension: A standards-compliant approach that defines @components/ rules within standard Import Maps.
  • Namespace Prefix Auto-Resolution: No need to register components one by one. Just define a prefix like @components/ui/, and it auto-resolves <ui-button> to button.js.
  • Inline Loader Specification: Specify loaders in Import Map keys like @components/ui|lit/. Easily mix multiple frameworks.
  • Advanced is Attribute Support: Automatically loads extended built-in elements. Infers extends from class definitions and calls define appropriately.
  • Abstracted Loaders: The file loading logic itself is pluggable, allowing customization of extensions and processing systems.

Usage

1. Setup Import Map

Define the autoloader path using the @wcstack/autoloader key. Define your component paths in an import map using the @components/ prefix.

<script type="importmap">
  {
    "imports": {
      "@wcstack/autoloader": "/path/to/autoloader",
      "@components/ui/": "./components/ui/",
      "@components/app/": "./components/app/"
    }
  }
</script>

2. Load the Autoloader

Load the autoloader script via <script> tag, or import and call bootstrapAutoloader manually.

<!-- Option A: Zero-config script (recommended) -->
<script type="module" src="https://esm.run/@wcstack/autoloader/auto"></script>

<!-- Option B: Manual initialization -->
<script type="module">
  import { bootstrapAutoloader } from "@wcstack/autoloader";
  bootstrapAutoloader();
</script>

3. Place the <wcs-autoloader> Element

Add <wcs-autoloader> to your HTML. This element triggers the loading lifecycle — eager loading starts on element creation, and lazy loading starts when the element is connected to the DOM.

<body>
  <wcs-autoloader></wcs-autoloader>
  <!-- your app components -->
</body>

4. Use Components

Just use your custom elements in HTML. @wcstack/autoloader will automatically import the matching file.

<!-- Loads ./components/ui/button.js -->
<ui-button></ui-button>

<!-- Loads ./components/app/header.js -->
<app-header></app-header>

Import Map Syntax

@wcstack/autoloader parses keys in the import map starting with @components/.

Lazy Loading (Namespaces)

To enable lazy loading for a group of components, use a key ending with /.

Format: "@components/<prefix>[|<loader>]/": "<path>"

  • Prefix: The tag prefix. Slashes are converted to dashes.
  • Loader (Optional): The loader to use (e.g., vanilla, lit). Defaults to vanilla.

Examples:

{
  "imports": {
    // Maps <my-component> to ./components/component.js
    "@components/my/": "./components/",

    // Maps <ui-button> to ./ui/button.js (using 'lit' loader if configured)
    "@components/ui|lit/": "./ui/"
  }
}

Eager Loading

To load a specific component immediately, use a key that does NOT end with /.

Format: "@components/<tagName>[|<loader>[,<extends>]]": "<path>"

  • Loader (Optional): If omitted, it is automatically resolved based on the file extension (e.g., .js -> default loader, .lit.js -> lit-loader).
  • Extends (Optional): If omitted, it is automatically detected if the component class extends a built-in HTML element (e.g., HTMLButtonElement -> extends: 'button').

Examples:

{
  "imports": {
    // Eager loads <my-button> from ./my-button.js
    // Loader: Auto-detected (.js)
    // Extends: Auto-detected (e.g. if class extends HTMLButtonElement)
    "@components/my-button": "./my-button.js",

    // Explicitly specifying loader and extends
    "@components/fancy-input|vanilla,input": "./fancy-input.js",
    
    // Auto-detect loader for Lit element (if lit-loader is configured)
    "@components/my-lit-button": "./my-button.lit.js"
  }
}

Component Requirements

By default (using the vanilla loader), your component files should:

  1. Have a .js extension (configurable).
  2. Export the custom element class as default.
// components/ui/button.js
export default class UiButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' }).innerHTML = '<button><slot></slot></button>';
  }
}

Customized Built-in Elements (is attribute)

The autoloader detects customized built-in elements using the is attribute:

<!-- Autoloader detects and loads "my-button" -->
<button is="my-button">Click me</button>

Lazy loading: The extends value is automatically inferred from the host element tag (e.g., <button>extends: "button").

Eager loading: The extends value is inferred from the component class prototype (e.g., HTMLButtonElementextends: "button"), or can be specified explicitly in the import map:

{
  "imports": {
    "@components/my-button|vanilla,button": "./my-button.js"
  }
}
// my-button.js
export default class MyButton extends HTMLButtonElement {
  connectedCallback() {
    this.style.color = 'red';
  }
}
// Autoloader calls: customElements.define('my-button', MyButton, { extends: 'button' })

Configuration

Initialize the autoloader with optional configuration via bootstrapAutoloader():

interface ILoader {
  postfix: string;
  loader: (path: string) => Promise<CustomElementConstructor | null>;
}

interface IWritableTagNames {
  autoloader?: string;
}

interface IWritableConfig {
  loaders?: Record<string, ILoader | string>;
  observable?: boolean;
  tagNames?: IWritableTagNames;
}

| Option | Type | Default | Description | |--------|------|---------|-------------| | loaders | Record<string, ILoader \| string> | See below | Loader definitions. Values can be ILoader objects or string aliases pointing to other loader keys. | | observable | boolean | true | Enables MutationObserver to detect dynamically added elements. Set to false to disable. | | tagNames | IWritableTagNames | { autoloader: "wcs-autoloader" } | Custom element tag name. Can be changed to avoid naming conflicts. |

Default Configuration

{
  loaders: {
    // Built-in vanilla loader: imports module and returns default export
    vanilla: { postfix: ".js", loader: vanillaLoader },
    // Default key: used as fallback when no loader matches
    "*": "vanilla"
  },
  observable: true
}
  • vanilla: The built-in loader that dynamically imports a module and returns its default export as the custom element constructor.
  • "*" (default key): Fallback loader. Its value is a string alias "vanilla", meaning unmatched components use the vanilla loader.

Loader Resolution

When a component has no explicit loader key (e.g., lazy-loaded namespaces without |loader), the autoloader resolves the loader as follows:

  1. Postfix matching: Checks the file path against all registered loaders' postfix values (longest match first).
  2. Default key fallback: If no postfix matches, uses the loader referenced by the "*" key.

Example

import { bootstrapAutoloader } from "@wcstack/autoloader";

bootstrapAutoloader({
  loaders: {
    // Override vanilla loader's file extension
    vanilla: { postfix: ".vanilla.js" },
    // Add a custom loader for .lit.js files
    lit: {
      postfix: ".lit.js",
      loader: async (path) => {
        const module = await import(path);
        return module.default;
      }
    }
  },
  // Disable MutationObserver (no dynamic content detection)
  observable: false
});

How it Works

Loading Lifecycle

  1. Registration: bootstrapAutoloader() registers the <wcs-autoloader> custom element via customElements.define().
  2. Constructor (on element creation): All <script type="importmap"> elements are parsed for @components/ entries. Eager loading starts immediately for non-namespaced keys (not ending with /).
  3. connectedCallback (on DOM attachment): Waits for DOMContentLoaded if the document is still loading, then scans the DOM using TreeWalker for undefined custom elements matching registered namespaces.
  4. Nested Loading: After each custom element is defined and upgraded, its Shadow DOM (if present) is also scanned for nested custom elements.
  5. Observation (if observable: true): A MutationObserver watches for new elements added to the DOM and triggers lazy loading.
  6. disconnectedCallback (on removal): Disconnects the MutationObserver and releases the singleton instance.

Error Handling

  • Components that fail to load are tracked internally and will not be retried on subsequent scans.
  • Duplicate loading is prevented: if a component is already being loaded, subsequent requests wait for the existing load to complete.

License

MIT