docs/frontend-system: initial docs for creating extension blueprints

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-08-14 15:41:21 +02:00
parent 285e2b0b4b
commit 634a1202cd
@@ -60,3 +60,83 @@ const myPageExtension = PageBlueprint.makeWithOverrides({
When using `makeWithOverrides`, we no longer pass the blueprint parameters directly. Instead, we provide a `factory` function that receives the original blueprint factory as the first argument, and the extension factory context as the second. We can then call the original blueprint factory with the blueprint parameters and forward the result as the return value of out factory. Notice that when passing the blueprint parameters using this pattern we have access to a lot more information than when using the `make` method, at the cost of being more complex.
Apart from the addition of the blueprint parameters of the first argument to the original factory function, the `makeWithOverrides` method works the same way as [extension overrides](./25-extension-overrides.md). All the same options and rules apply, including the ability to define additional inputs, override outputs, and so on. We therefore defer to the [extension overrides](./25-extension-overrides.md) documentation for more information on how to use the `makeWithOverrides` method.
## Creating an extension blueprint
To create a new extension blueprint, you use the `createExtensionBlueprint` function. At the surface it is very similar to `createExtension`, but with a few key differences. Firstly you must provide a `kind` option, which will be the kind of all extensions created with the blueprint. See the [naming patterns section](./50-naming-patterns.md) for more information about how to select a good extension kind. Secondly, the `factory` function has a new signature where the first parameter is the blueprint parameters, and the second is the factory context. And finally, rather than returning an extension, `createExtensionBlueprint` returns a blueprint object with the `make` method and friends, which is used as is described above.
The following is an example of how one might create a new extension blueprint:
```tsx
export interface MyWidgetBlueprintParams {
defaultTitle: string;
element: JSX.Element;
}
export const MyWidgetBlueprint = createExtensionBlueprint({
kind: 'my-widget',
attachTo: { id: 'page:my-plugin', input: 'widgets' },
config: {
schema: {
title: z.string().optional(),
},
},
output: [coreExtensionData.reactElement],
factory(params: MyWidgetBlueprintParams, { config }) {
return [
// Note that while this is a valid pattern, you might often want to
// return separate pieces of data instead, more on that below.
coreExtensionData.reactElement(
<MyWidgetContainer title={config.title ?? params.defaultTitle}>
{params.element}
</MyWidgetContainer>,
),
];
},
});
```
This is of course a quite bare-bones example blueprint, but still a very real example. Blueprints can be very simple, there's already a lot of value in encapsulating the extension kind, attachment point, and output in a blueprint.
Most of the options provided to `createExtensionBlueprint` can be overridden when using `makeWithOverrides` to create an extension from the blueprint. These overrides work the same way as [extension overrides](./25-extension-overrides.md), and we defer to that documentation for more information on how overrides work.
### Blueprint-specific extension data references
In some cases you may want to define and provide [extension data reference](./20-extensions.md#extension-data-references) that are specific to your blueprint. In the above example we might want to forward the `title` as data for example, rather than encapsulating it into the `MyWidgetContainer` component. This gives the parent extension more flexibility in the rendering for our example widget extensions.
To do that, we create a new extension data reference for our widget title. This references is provided via the `dataRefs` options when we create the blueprint, which makes it available for use via `MyWidgetBlueprint.dataRefs.widgetTitle`.
```tsx
export interface MyWidgetBlueprintParams {
defaultTitle: string;
element: JSX.Element;
}
const widgetTitleRef = createExtensionDataRef<string>().with({
id: 'my-plugin.widget.title',
});
export const MyWidgetBlueprint = createExtensionBlueprint({
kind: 'my-widget',
attachTo: { id: 'page:my-plugin', input: 'widgets' },
config: {
schema: {
title: z.string().optional(),
},
},
output: [widgetTitleRef, coreExtensionData.reactElement],
factory(params: MyWidgetBlueprintParams, { config }) {
return [
widgetTitleRef(config.title ?? params.defaultTitle),
coreExtensionData.reactElement(params.element),
];
},
dataRefs: {
widgetTitle: widgetTitleRef,
},
});
```
### Extension Blueprints in libraries
If you are publishing a plugin, the extension creators should always be exported from frontend library packages (e.g. `*-react`) rather than plugin packages.