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

xtensible

v0.0.2

Published

Pluggable classes with hooks and a plugin system

Readme

(E)xtensible

A lightweight, nearly zero-dependency* plugin system for JavaScript classes. Add elaborate plugin support to any class (or any JS object) — with automatic dependency resolution and inheritance support.

* Only dependency is get-symbols, a tiny symbol registry utility.

Installation

npm install xtensible

Quick Start

xtensible allows you to make your classes extensible so that consumers can do things like:

import YourClass from "your-class";
import yourClassPlugin from "your-class-plugin";

YourClass.addPlugin(yourClassPlugin);

Plugins

Each plugin can only be installed once per class, but can be installed separately on subclasses.

Defining a plugin

A plugin is a plain object with any combination of these properties:

| Property | Type | Description | | -------------- | ---------- | --------------------------------------------------------------------------------------------------------- | | provides | object | Properties and methods added to the class prototype (instances) or the class itself (under constructor) | | hooks | object | Callbacks keyed by hook name, run during lifecycle events | | dependencies | Plugin[] | Plugins that must be installed first |

import otherPlugin from "other-plugin";

const myPlugin = {
	dependencies: [otherPlugin],
	provides: {
		greet () {
			return `Hi from ${this.constructor.name}`;
		},

		constructor: {
			// Static method!
			create (...args) {
				return new this(...args);
			},
		},
	},
	hooks: {
		setup () {
			/* runs whenever the class calls the "setup" hook */
		},
	},
};

Installing plugins

Use addPlugin() to install one or more plugins on a class. Dependency plugins are installed automatically. Duplicate installations are silently skipped.

import { addPlugin } from "xtensible";

addPlugin(MyClass, pluginA, pluginB);

You can check whether a plugin is already installed (including on superclasses):

import { hasPlugin } from "xtensible";

hasPlugin(MyClass, pluginA); // true or false

You can expose addPlugin() as a static method on the class so that your consumers don't need to know about xtensible at all:

import { addPlugin } from "xtensible";

class MyClass {
	// ...

	static addPlugin (plugin) {
		addPlugin(this, plugin);
	}
}

Hooks

xtensible plugins can contain one or more of the following:

  1. Provided properties, loosely inspired from the first-class protocols proposal.
  2. Lifecycle hooks which allow a class to run code at specific points in its execution lifecycle.

Plugins only using provided members (1) can be installed on any class by simply calling addPlugin(Class, plugin) though classes may want to expose addPlugin() etc as static methods so that consumer don't need to know about xtensible at all.

However, for hooks to make sense, the class needs to actually define when they should run. E.g.:

import { hooks } from "xtensible/hooks";

class MyElement extends HTMLElement {
	connectedCallback () {
		this[hooks].run("connected", this);
	}
}

Generally, hooks are useful when a plugin needs to extend what an existing method does or add side effects to it. For example:

  • Class creation
  • Instance creation
  • Common lifecycle events (e.g. connectedCallback for custom elements)

Some commonly useful hooks are:

  • setup - runs once per class
  • constructor - When the class constructor is called (sync)
  • constructed - runs after an instance is constructed (including any subclass constructors)

xtensible hooks are an evolution of our earlier blissful-hooks package, adapted to work well for deep class hierarchies (superclass hooks run first) and support the more elaborate functionality we needed for nude-element.

Hook name resolution

Hook names are normalized to underscore_case, so all of these are equivalent:

"myHook"; // camelCase
"my-hook"; // kebab-case
"my_hook"; // underscore_case

Once-per-context hooks

Prefix a hook name with first_ to run it only once per context (class or instance):

hooks: {
	first_setup () {
		// Only runs the first time setup() is called on this class
	},
}

Wildcard hooks

Register a "*" hook to run on every hook invocation:

this.hooks.add("*", function (env) {
	console.log(`Hook "${env.hookName}" fired`);
});

Common plugins

Optionally, xtensible ships with a few tiny plugins that are useful for common use cases.

api - Exposing a public API for plugins

xtensible is not very opinionated about how you expose your plugin system and does not add any public API to your class by default. If you want to expose public API for plugins (installedPlugins, hasPlugin(), addPlugin()), you can install the api plugin:

import { addPlugin, pluginsApi } from "xtensible";

class YourClass {
	// ...
}

addPlugin(YourClass, pluginsApi);

YourClass.addPlugin(yourClassPlugin); // now works!

This also allows consumer subclasses to define a plugins array and have these plugins installed automatically when the subclass is constructed (if a setup hook is defined).

$hook - Convenience $hook() method

If you find yourself defining a lot of hooks, you can install the $hook plugin to add a convenience instance and class method for running hooks.

import { addPlugin, $hook } from "xtensible";

class YourClass {
	constructor () {
		// ...
		this.$hook("constructor", this);
	}

	static {
		addPlugin(this, $hook);
	}
}

hooks-common - Common hooks

Supports common hooks for class creation, instance creation, and common lifecycle events:

  • constructor - When the class constructor is called (sync)
  • constructed - runs after an instance is constructed (including any subclass constructors)
  • setup - runs once per class

The installing class does still need to call the provided constructed() method in its constructor and ideally classes should call setup() to trigger the setup hook earlier than the first instance is constructed.

$super - Convenience this.$super property

TBD - Coming soon