Skip to content

Commit

Permalink
feat: Add defineAllHooksPlugin for plugins working on all hooks (#113)
Browse files Browse the repository at this point in the history
Co-authored-by: Bryce Russell <[email protected]>
Co-authored-by: Florian Lefebvre <[email protected]>
  • Loading branch information
3 people authored Jun 20, 2024
1 parent 619bced commit 25c72f6
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/olive-rabbits-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"astro-integration-kit": minor
---

Adds a new `defineAllHooksPlugin` helper for plugins that provide APIs for any hook, including third-party hooks.
21 changes: 21 additions & 0 deletions docs/src/content/docs/core/define-plugin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ export default defineIntegration({
})
```

## `defineAllHooksPlugin`

`defineAllHooksPlugin` is a variation of `definePlugin` for when you want to provide the same API for every hook defined in the consumer integration.

Using this function has an advantange because it allows your plugin to provide an API even for hooks it doesn't know about, like any Astro hook added after the plugin release or hooks added by integrations like `@astrojs/db`.

Instead of providing a `setup` function returning a map of your API factory for each hook, you provide a function that receives a hook name and returns the API factory for that hook.

```ts
defineAllHooksPlugin({
name: "my-plugin",
setup({ name }) {
return (hookName) => (hookParameters) => ({
doSomething: () => {
// ...
}
});
}
})
```

## Limitations

1. Plugins support overrides. That means that if 2 plugins declare the same `name`, the latest will be kept.
Expand Down
41 changes: 41 additions & 0 deletions package/src/core/define-all-hooks-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { definePlugin } from "./define-plugin.js";
import type { Hooks, Plugin } from "./types.js";

export type AllHooksPluginDefinition<TName extends string, TApi extends Record<string, unknown>> = {
name: TName;
setup: (...params: Parameters<AllHooksPlugin<TName, TApi>['setup']>) =>
<H extends keyof Hooks>(hookName: H) =>
(...hookParams: Parameters<Hooks[H]>) => TApi;
};

/**
* A plugin that exposes the same API for all hooks.
*/
export type AllHooksPlugin<TName extends string, TApi extends Record<string, unknown>> = Plugin<
TName,
Record<keyof Hooks, TApi>
>;

/**
* Allows defining a type-safe plugin that can be used from any Astro hook.
*
* This wraps {@link definePlugin} and receives a factory for the API to be
* called dynamically for each hook. This allows plugins to support any hook
* even those added by new versions of astro or hooks added by other integrations.
*
* @see https://astro-integration-kit.netlify.app/utilities/define-plugin/
*/
export const defineAllHooksPlugin = <TName extends string, TApi extends Record<string, unknown>>(
plugin: AllHooksPluginDefinition<TName, TApi>
): AllHooksPlugin<TName, TApi> =>
definePlugin({
...plugin,
setup: (...params) => {
const hookFactory = plugin.setup(...params);

return new Proxy(Object.freeze({}) as ReturnType<Plugin<any, any>['setup']>, {
has: (_, prop) => typeof prop === 'string',
get: (_, prop) => hookFactory(prop as keyof Hooks),
});
},
});
1 change: 1 addition & 0 deletions package/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { createResolver } from "./create-resolver.js";
export { defineIntegration } from "./define-integration.js";
export { definePlugin } from "./define-plugin.js";
export { defineAllHooksPlugin } from "./define-all-hooks-plugin.js";
export { defineUtility } from "./define-utility.js";
export { withPlugins } from "./with-plugins.js";
export * from "./types.js";
Expand Down

0 comments on commit 25c72f6

Please sign in to comment.