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 🙏

© 2024 – Pkg Stats / Ryan Hefner

javascript-plugin-architecture-with-typescript-definitions

v4.1.0

Published

Plugin architecture example with full TypeScript support

Downloads

265

Readme

javascript-plugin-architecture-with-typescript-definitions

Plugin architecture example with full TypeScript support

@latest Build Status

The goal of this repository is to provide a template for a simple plugin Architecture which allows plugin authors to extend the base API as well as extend its constructor options. A custom class can be composed of the core Base class, a set of plugins and default options and distributed as new package, with full TypeScript for added APIs and constructor options.

Usage

Try it in TypeScript's playground editor

Export new class MyBase with two plugins and default options:

// import the Base class
import { Base } from "javascript-plugin-architecture-with-typescript-definitions";

// import a set of plugins
import { myFooPlugin } from "@example/my-foo-plugin";
import { myBarPlugin } from "./my-bar-plugin";

export const MyBase = Base.withPlugins([myFooPlugin, myBarPlugin]).withDefaults(
  {
    foo: "bar",
  }
);

When importing MyBase and instantiating it, the MyBase constructor has type support for the new optional foo option as well as the .foo() and .bar() methods addded by the respective plugins.

import { MyBase } from "@example/my-base";

const myBase = new MyBase({
  // has full TypeScript intellisense
  foo: "bar",
});
myBase.foo(); // has full TypeScript intellisense
myBase.bar(); // has full TypeScript intellisense

Create plugin which extends the API as well as the constructor options type

import { Base } from "javascript-plugin-architecture-with-typescript-definitions";

declare module "javascript-plugin-architecture-with-typescript-definitions" {
  namespace Base {
    interface Options {
      foo?: string;
    }
  }
}

export function myFooPlugin(base: Base, options: Base.options) {
  return {
    foo() => options.foo || "bar",
  }
}

API

static .withPlugins(plugins)

Returns a new class with .plugins added to parent classes .plugins array. All plugins will be applied to instances.

static .withDefaults(options)

Returns a new class with .defaults merged with the parent classes .defaults object. The defaults are applied to the options passed to the constructor when instantiated.

static .plugins

Base.plugins is an empty array by default. It is extended on derived classes using .withPlugins(plugins).

static .defaults

Base.defaults is an empty object by default. It is extended on derived classes using .withDefaults(plugins).

Constructor

The constructor accepts one argument which is optional by default

new Base(options);

If the Base.Options interface has been extended with required keys, then the options argument becomes required, and all required Base.Options keys must be set.

.options

The .options key is set on all instances. It's merged from from the constructor's .defaults object and the options passed to the constructor

const BaseWithOptions = Base.withDefaults({ foo: "bar" });
const instance = new BaseWithOptions();
instance.options; // {foo: 'bar'}

Note that in for TypeScript to recognize the new option, you have to extend the Base.Option intererface.

Other instance propreties and methods

Instance properties and methods can be added using plugins. Example:

function myPlugin(base: Base, options: Base.options) {
  return {
    myMethod() {
      /* do something here */
    },
    myProperty: "", // set to something useful
  };
}
const MyBase = Base.plugins([myPlugin]);
const myBase = new MyBase();

// this method and property is now set
myBase.myMethod();
myBase.myProperty;

TypeScript for a customized Base class

If you write your d.ts files by hand instead of generating them from TypeScript source code, you can use the ExtendBaseWith Generic to create a class with custom defaults and plugins. It can even inherit from another customized class.

import { Base, ExtendBaseWith } from "../../index.js";

import { myPlugin } from "./my-plugin.js";

export const MyBase: ExtendBaseWith<
  Base,
  {
    defaults: {
      myPluginOption: string;
    };
    plugins: [typeof myPlugin];
  }
>;

// support using the `MyBase` import to be used as a class instance type
export type MyBase = typeof MyBase;

The last line is important in order to make MyBase behave like a class type, making the following code possible:

import { MyBase } from "./index.js";

export async function testInstanceType(client: MyBase) {
  // types set correctly on `client`
  client.myPlugin({ myPluginOption: "foo" });
}

Defaults

TypeScript will not complain when chaining .withDefaults() calls endlessly: the static .defaults property will be set correctly. However, when instantiating from a class with 4+ chained .withDefaults() calls, then only the defaults from the first 3 calls are supported. See #57 for details.

Credit

This plugin architecture was extracted from @octokit/core. The implementation was made possible by help from @karol-majewski, @dragomirtitian, StackOverflow user "hackape", and @JoshuaKGoldberg.

LICENSE

ISC