frontend-plugin-api: swtich extension factory to bind all outputs at once
Co-authored-by: Johan Haals <johan.haals@gmail.com> Co-authored-by: Fredrik Adelöw <freben@gmail.com> Co-authored-by: Philipp Hugenroth <philipph@spotify.com> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -54,9 +54,17 @@ export function createExtensionInstance(options: {
|
||||
extension.factory({
|
||||
source,
|
||||
config: parsedConfig,
|
||||
bind: mapValues(extension.output, ref => {
|
||||
return (value: unknown) => extensionData.set(ref.id, value);
|
||||
}),
|
||||
bind: namedOutputs => {
|
||||
for (const [name, output] of Object.entries(namedOutputs)) {
|
||||
const ref = extension.output[name];
|
||||
if (!ref) {
|
||||
throw new Error(
|
||||
`Extension instance '${extension.id}' tried to bind unknown output '${name}'`,
|
||||
);
|
||||
}
|
||||
extensionData.set(ref.id, output);
|
||||
}
|
||||
},
|
||||
inputs: mapValues(
|
||||
extension.inputs,
|
||||
({ extensionData: pointData }, inputName) => {
|
||||
|
||||
@@ -47,10 +47,12 @@ export const CoreRouter = createExtension({
|
||||
|
||||
return element;
|
||||
};
|
||||
bind.component(() => (
|
||||
<BrowserRouter>
|
||||
<Routes />
|
||||
</BrowserRouter>
|
||||
));
|
||||
bind({
|
||||
component: () => (
|
||||
<BrowserRouter>
|
||||
<Routes />
|
||||
</BrowserRouter>
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -63,9 +63,9 @@ export function createApiExtension<
|
||||
},
|
||||
factory({ bind, config, inputs }) {
|
||||
if (typeof factory === 'function') {
|
||||
bind.api(factory({ config, inputs }));
|
||||
bind({ api: factory({ config, inputs }) });
|
||||
} else {
|
||||
bind.api(factory);
|
||||
bind({ api: factory });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -82,17 +82,18 @@ export function createPageExtension<
|
||||
.component({ config, inputs })
|
||||
.then(element => ({ default: () => element })),
|
||||
);
|
||||
bind.path(config.path);
|
||||
bind.component(() => (
|
||||
<ExtensionBoundary source={source}>
|
||||
<React.Suspense fallback="...">
|
||||
<LazyComponent />
|
||||
</React.Suspense>
|
||||
</ExtensionBoundary>
|
||||
));
|
||||
if (options.routeRef) {
|
||||
bind.routeRef!(options.routeRef);
|
||||
}
|
||||
|
||||
bind({
|
||||
path: config.path,
|
||||
component: () => (
|
||||
<ExtensionBoundary source={source}>
|
||||
<React.Suspense fallback="...">
|
||||
<LazyComponent />
|
||||
</React.Suspense>
|
||||
</ExtensionBoundary>
|
||||
),
|
||||
routeRef: options.routeRef,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,21 +25,26 @@ describe('createExtension', () => {
|
||||
id: 'test',
|
||||
at: 'root',
|
||||
output: {
|
||||
foo: coreExtensionData.title,
|
||||
foo2: stringData,
|
||||
foo: stringData,
|
||||
},
|
||||
factory({ bind }) {
|
||||
// Make it work well with required and optional output
|
||||
// HOH - High Order Helper
|
||||
bind({
|
||||
foo: 'bar',
|
||||
});
|
||||
bind({
|
||||
// @ts-expect-error
|
||||
foo: 3,
|
||||
});
|
||||
bind({
|
||||
// @ts-expect-error
|
||||
bar: 'bar',
|
||||
});
|
||||
// @ts-expect-error
|
||||
bind.foo(3);
|
||||
bind({});
|
||||
// @ts-expect-error
|
||||
bind.foo();
|
||||
bind();
|
||||
// @ts-expect-error
|
||||
bind.bar('bar');
|
||||
bind('bar');
|
||||
},
|
||||
});
|
||||
expect(extension.id).toBe('test');
|
||||
|
||||
@@ -19,9 +19,9 @@ import { BackstagePlugin } from './createPlugin';
|
||||
import { AnyExtensionDataMap, Extension } from './types';
|
||||
|
||||
/** @public */
|
||||
export type ExtensionDataBind<TData extends AnyExtensionDataMap> = {
|
||||
[K in keyof TData]: (value: TData[K]['T']) => void;
|
||||
};
|
||||
export type ExtensionDataBind<TOutput extends AnyExtensionDataMap> = (outputs: {
|
||||
[K in keyof TOutput]: TOutput[K]['T'];
|
||||
}) => void;
|
||||
|
||||
/** @public */
|
||||
export type ExtensionDataValue<TData extends AnyExtensionDataMap> = {
|
||||
@@ -30,23 +30,23 @@ export type ExtensionDataValue<TData extends AnyExtensionDataMap> = {
|
||||
|
||||
/** @public */
|
||||
export interface CreateExtensionOptions<
|
||||
TData extends AnyExtensionDataMap,
|
||||
TPoint extends Record<string, { extensionData: AnyExtensionDataMap }>,
|
||||
TOutput extends AnyExtensionDataMap,
|
||||
TInputs extends Record<string, { extensionData: AnyExtensionDataMap }>,
|
||||
TConfig,
|
||||
> {
|
||||
id: string;
|
||||
at: string;
|
||||
disabled?: boolean;
|
||||
inputs?: TPoint;
|
||||
output: TData;
|
||||
inputs?: TInputs;
|
||||
output: TOutput;
|
||||
configSchema?: PortableSchema<TConfig>;
|
||||
factory(options: {
|
||||
source?: BackstagePlugin;
|
||||
bind: ExtensionDataBind<TData>;
|
||||
bind: ExtensionDataBind<TOutput>;
|
||||
config: TConfig;
|
||||
inputs: {
|
||||
[pointName in keyof TPoint]: ExtensionDataValue<
|
||||
TPoint[pointName]['extensionData']
|
||||
[pointName in keyof TInputs]: ExtensionDataValue<
|
||||
TInputs[pointName]['extensionData']
|
||||
>[];
|
||||
};
|
||||
}): void;
|
||||
@@ -54,14 +54,28 @@ export interface CreateExtensionOptions<
|
||||
|
||||
/** @public */
|
||||
export function createExtension<
|
||||
TData extends AnyExtensionDataMap,
|
||||
TPoint extends Record<string, { extensionData: AnyExtensionDataMap }>,
|
||||
TOutput extends AnyExtensionDataMap,
|
||||
TInputs extends Record<string, { extensionData: AnyExtensionDataMap }>,
|
||||
TConfig = never,
|
||||
>(options: CreateExtensionOptions<TData, TPoint, TConfig>): Extension<TConfig> {
|
||||
>(
|
||||
options: CreateExtensionOptions<TOutput, TInputs, TConfig>,
|
||||
): Extension<TConfig> {
|
||||
return {
|
||||
...options,
|
||||
disabled: options.disabled ?? false,
|
||||
$$type: 'extension',
|
||||
inputs: options.inputs ?? {},
|
||||
factory({ bind, config, inputs }) {
|
||||
// TODO: Simplify this, but TS wouldn't infer the input type for some reason
|
||||
return options.factory({
|
||||
bind,
|
||||
config,
|
||||
inputs: inputs as {
|
||||
[pointName in keyof TInputs]: ExtensionDataValue<
|
||||
TInputs[pointName]['extensionData']
|
||||
>[];
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ const TechRadarPage = createExtension({
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
factory({ bind }) {
|
||||
bind.name('TechRadar');
|
||||
bind({ name: 'TechRadar' });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@ const CatalogPage = createExtension({
|
||||
z.object({ name: z.string().default('Catalog') }),
|
||||
),
|
||||
factory({ bind, config }) {
|
||||
bind.name(config.name);
|
||||
bind({ name: config.name });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -61,7 +61,7 @@ const TechDocsAddon = createExtension({
|
||||
z.object({ name: z.string().default('TechDocsAddon') }),
|
||||
),
|
||||
factory({ bind, config }) {
|
||||
bind.name(config.name);
|
||||
bind({ name: config.name });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -79,7 +79,7 @@ const TechDocsPage = createExtension({
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind.name(`TechDocs-${inputs.addons.map(n => n.name).join('-')}`);
|
||||
bind({ name: `TechDocs-${inputs.addons.map(n => n.name).join('-')}` });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -97,11 +97,12 @@ const outputExtension = createExtension({
|
||||
component: coreExtensionData.reactComponent,
|
||||
},
|
||||
factory({ bind, inputs }) {
|
||||
bind.component(() =>
|
||||
React.createElement('span', {}, [
|
||||
`Names: ${inputs.names.map(n => n.name).join(', ')}`,
|
||||
]),
|
||||
);
|
||||
bind({
|
||||
component: () =>
|
||||
React.createElement('span', {}, [
|
||||
`Names: ${inputs.names.map(n => n.name).join(', ')}`,
|
||||
]),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user