/hooks-plugin

A plugin system built through various hooks.

Primary LanguageTypeScriptMIT LicenseMIT

hooks-plugin

NPM version

Plugin system built through various hooks, inspired by tapable. it is very small, but it has fully-fledged and powerful TypeScript type hinting/type checking. If your project has a microkernel architecture, then this library will be very suitable for you.

Debugging platform

https://imtaotao.github.io/hooks-plugin/

Hook list

  • SyncHook
  • SyncWaterfallHook
  • AsyncHook
  • AsyncWaterfallHook
  • AsyncParallelHook

Apis

  • plSys.use
  • plSys.useRefine
  • plSys.remove
  • plSys.create
  • plSys.isUsed
  • plSys.beforeEach
  • plSys.afterEach
  • plSys.lock
  • plSys.unlock
  • plSys.listenLock
  • plSys.listenError
  • plSys.getPluginApis
  • plSys.pickLifyCycle
  • plSys.clone
  • plSys.debug
  • plSys.performance
  • plSys.removeAllDebug
  • plSys.removeAllPerformance

Usage

Simple example

import { SyncHook, PluginSystem } from "hooks-plugin";

// Create a plugin and declare hooks
const plSys = new PluginSystem({
  a: new SyncHook<[string, number]>(),
});

// Register plugin
plSys.use({
  name: "testPlugin",
  hooks: {
    a(a, b) {
      console.log(a, b); // 'str', 1
    },
  },
});

// Register via function
plSys.use(function(plSys) {
  return {
    name: "testPlugin",
    ...
  }
})

// If you want to simplify registering plugins
const plugin = plSys.useRefine({
  a(a, b) {
    console.log(a, b); // 'str', 1
  },
})
console.log(plugin.name); // Plugin name is a uuid created automatically

// Trigger hook
plSys.lifecycle.a.emit("str", 1);

More complex example

import { AsyncHook, PluginSystem } from "hooks-plugin";

const plSys = new PluginSystem({
  // 1. The first generic is the parameter type received by the hook
  // 2. The second generic is the `this`` type of the hook function
  a: new AsyncHook<[number, number], string>("context"),

  // The parameter type of `AsyncWaterfallHook` and `SyncWaterfallHook` must be an object
  b: new AsyncWaterfallHook<{ value: number }, string>("context"),
});

plSys.use({
  name: "testPlugin",
  version: "1.0.0", // Optional
  hooks: {
    async a(a, b) {
      console.log(this); // 'context'
      console.log(a, b); // 1, 2
    },

    async b(data) {
      console.log(this); // 'context'
      console.log(data); // { value: 1 }
      return data;  // Must return values of the same type
    },
  },
  // The order is after `hooks` and will only be called once
  onceHooks: {
    async a(a, b) {
      console.log(this); // 'context'
      console.log(a, b); // 1, 2
    },
  }
});

plSys.lifecycle.a.emit(1, 2);
plSys.lifecycle.b.emit({ value: 1 });

Interact with other plugins

import { SyncHook, PluginSystem } from "hooks-plugin";

// Declare your plugin `api` type
declare module "hooks-plugin" {
  export interface PluginApis {
    testApis: (typeof plugin)["apis"];
  }
}

const plSys = new PluginSystem({});

const plugin = plSys.use({
  name: "testApis",
  apis: {
    get(key: string) {},
    set(key: string, value: unknown) {},
  },
});

const apis = plSys.getPluginApis("testApis");

apis.get("a");
apis.set("a", 1);

beforeEach and afterEach.

import { SyncHook, PluginSystem } from "hooks-plugin";

const plSys = new PluginSystem({
  a: new SyncHook(),
});

plSys.use({
  name: "test",
  hooks: {
    a(data) {},
  },
});

// Registers a (sync) callback to be called before each hook is being called.
// `id`` is an arbitrary value, used to maintain consistency with `afterEach`
const unsubscribeBefore = plSys.beforeEach((e) => {
  console.log("id:", e.id);
  console.log("name:", e.name);
  console.log("type:", e.type);
  console.log("args:", e.args);
  console.log("context:", e.context);
});

// Registers a (sync) callback to be called after each hook is being called.
const unsubscribeAfter = plSys.afterEach((e) => {
  console.log("id:", e.id);
  console.log("name:", e.name);
  console.log("type:", e.type);
  console.log("args:", e.args);
  console.log("context:", e.context);
  console.log("pluginExecTime:", e.pluginExecTime);
});

plSys.lifecycle.a.emit(1);

unsubscribeBefore();
unsubscribeAfter();

// Listening will no longer be triggered
plSys.lifecycle.a.emit(2);

Debug

import { SyncHook, PluginSystem } from "hooks-plugin";

const plSys = new PluginSystem({
  a: new AsyncParallelHook("ctx"),
});

plSys.use({
  name: "test",
  hooks: {
    a() {
      return new Promise((resolve) => {
        setTimeout(resolve, 300);
      });
    },
  },
});

const close = plSys.debug({ tag: "tag" });

plSys.lifecycle.a.emit(1, 2); // [tag]: a_1(t, args, ctx, pt): 309.36ms [1, 2] 'ctx' { test: 308.51ms }

// Close debug mode
close();

// You can also pass a `receiver` to handle log data yourself.
const close = plSys.debug({
  tag: "tag",
  receiver(data) {
    console.log(data); // { e, tag, time }
  },
  // filter(data) { ... },
});

plSys.lifecycle.a.emit(1, 2);

Performance

import { SyncHook, PluginSystem } from "hooks-plugin";

const plSys = new PluginSystem({
  a: new SyncHook(),
  b: new AsyncHook(),
});

// Take the `name` prop from the `0` parameter for matching
const p = plSys.performance("[0].name");
p.monitor("a", "a").on((e) => console.log(e.time)); // 200.400
p.monitor("a", "b").on((e) => console.log(e.time)); // 0.199

plSys.lifecycle.a.emit({ name: "n" });

setTimeout(() => {
  plSys.lifecycle.a.emit({ name: "n" });
  plSys.lifecycle.b.emit({ name: "n" });
}, 200);

CDN

<!DOCTYPE html>
<html lang='en'>
<body>
  <script src='https://unpkg.com/hooks-plugin/dist/hooks.umd.js'></script>
  <script>
    const {
      PluginSystem,
      SyncHook,
      AsyncHook,
      SyncWaterfallHook,
      AsyncParallelHook,
      AsyncWaterfallHook,
    } = window.HooksPlugin;

    // ...
  </script>
</body>
</html>