hookable

repository·main·Indexed 21 days ago

https://github.com/unjs/hookable

An awaitable hook system for registering, triggering, and managing lifecycle hooks in applications or libraries. It provides features for serial and parallel hook execution, bulk registration via addHooks, one-time hooks with hookOnce, and a debugging system via createDebugger. The library supports hook deprecation, global lifecycle callbacks with beforeEach and afterEach, and type-safe instance creation using createHooks.

Tokens
4K
Snippets
20
Records
25
Agent score
74%

What's inside hookable

  1. Register and unregister hooks

    main

    Hooks can be registered individually using .hook() or in bulk using .addHooks().

    To unregister hooks, you can:

    1. Use the unregister function returned by .hook() or .addHooks().
    2. Use .removeHook(name, fn) to remove a specific handler.
    3. Use .removeHooks(configHooks) to remove multiple handlers at once.
    4. Use .removeAllHooks() to clear everything.
    const lib = new FooLib();
    
    const hook0 = async () => { /* ... */ };
    const hook1 = async () => { /* ... */ };
    const hook2 = async () => { /* ... */ };
    
    // The hook() method returns an "unregister" function
    const unregisterHook0 = lib.hook("hook0", hook0);
    const unregisterHooks1and2 = lib.addHooks({ hook1, hook2 });
    
    /* ... */
    
    unregisterHook0();
    unregisterHooks1and2();
    
    // or
    
    lib.removeHooks({ hook0, hook1 });
    lib.removeHook("hook2", hook2);
  2. Migrate from Hookable 4.x to 5.x

    main

    When upgrading from version 4 to 5, be aware of the following breaking changes:

    • Named Exports: Import { Hookable } instead of the default export.
    • Error Handling: In v5, if a hook throws an error, callHook rejects. In v4, it handled errors globally and resolved the promise. You must now wrap callHook in try/catch blocks.
    • Types: Use Hookable<T> or createHooks<T>() for improved type checking.
    • Standalone Utils: mergeHooks is now a standalone export; replace Hookable.mergeHooks or this.mergeHooks with it.
    • No IE11 UMD: The IE11 compatible UMD build is removed. Use an ESM-aware bundler (Webpack, Rollup) if needed.
    • Logger: The logger parameter in the constructor is dropped; console.warn is used for deprecations.
  3. Initialize hooks with createHooks()

    main

    To create a new instance of a hook manager with type safety, use the createHooks<T>() function. You should pass an interface T that defines the shape of your hooks, where each key is a hook name and the value is the function signature for that hook.

    import { createHooks } from 'hookable';
    
    interface MyHooks {
      render: (data: string) => void;
      onComplete: (success: boolean) => Promise<void>;
    }
    
    const hooks = createHooks<MyHooks>();
  4. Create a hookable instance

    main

    You can use Hookable directly by creating a new instance. This is useful for standalone hook management.

    Note: If you only need basic hook and callHook functionality and want a smaller bundle/runtime footprint, consider using HookableCore instead.

    import { Hookable } from "hookable";
    
    // Create a hookable instance
    const hooks = new Hookable();
    
    // Hook on 'hello'
    hooks.hook("hello", () => {
      console.log("Hello World");
    });
    
    // Call 'hello' hook
    hooks.callHook("hello");
  5. Extend Hookable in a base class

    main

    You can extend the Hookable class to integrate hooks directly into your own library or class logic. Call super() in the constructor to initialize the hook system.

    import { Hookable } from "hookable";
    
    export default class FooLib extends Hookable {
      constructor() {
        // Call to parent to initialize
        super();
      }
    
      async someFunction() {
        // Call and wait for `hook1` hooks (if any) sequentially
        await this.callHook("hook1");
      }
    }
  6. Register lifecycle callbacks with beforeEach and afterEach

    main

    You can register synchronous callbacks that run before or after every single hook call in the system using beforeEach(syncCallback) and afterEach(syncCallback). The callback receives an event object containing the hook name and its args.

    hookable.beforeEach((event) => {
      console.log(`${event.name} hook is being called with ${event.args}`);
    });
    
    hookable.hook("test", () => {
      console.log("running test hook");
    });
    
    // test hook is being called with []
    // running test hook
    await hookable.callHook("test");
  7. Register hooks in bulk with addHooks

    main

    The addHooks(configHooks) method allows you to register multiple hooks at once. It supports flattening nested objects to create prefixed hooks (e.g., { test: { before: fn } } becomes test:before). It returns an unregister function that removes all registered handlers in that batch.

    // Register multiply handlers at once
    lib.addHooks({
      hook1: async () => {
        /* ... */
      },
      hook2: [/* can be also an array */],
    });
    
    // Flattening nested objects
    hookable.addHooks({
      test: {
        before: () => {},
        after: () => {},
      },
    });
  8. Debug hooks with createDebugger

    main

    The createDebugger(hooks, options) method automatically logs every hook call and the time it took to execute. Options include a tag for identifying logs. Use debug.close() to stop the debugger.

    const debug = hookable.createDebugger(hooks, { tag: "something" });
    
    hooks.callHook("some-hook", "some-arg");
    // [something] some-hook: 0.21ms
    
    debug.close();
  9. Deprecate hooks

    main
    Use deprecateHook(old, name) to deprecate a specific hook in favor of a new one, or deprecateHooks(deprecatedHooks) to deprecate multiple hooks at once. The deprecatedHooks object should map old hook names to their new replacements.
  10. Custom hook execution with callHookWith

    main

    If you need custom control over how hooks are executed, use callHookWith(name, callerFn). The callerFn is a callback that receives three arguments:

    • hooks: Array of user hooks to be called
    • args: Array of arguments to pass to each hook
    • name: The name of the hook
  11. Use hookOnce to trigger a handler exactly once

    main

    The hookOnce(name, fn) method registers a handler that automatically unregisters itself after its first execution. You can also manually unregister a hook from within its own handler by calling the unregister function returned by .hook().

    const lib = new FooLib();
    
    const unregister = lib.hook("hook0", async () => {
      // Unregister as soon as the hook is executed
      unregister();
    
      /* ... */
    });