core-api: add plugin.provide and extensions + core extension functions
Co-authored-by: Fredrik Adelöw <freben@gmail.com> Co-authored-by: blam <ben@blam.sh>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createPlugin } from '../plugin';
|
||||
import { createRouteRef } from '../routing';
|
||||
import { getComponentData } from './componentData';
|
||||
import {
|
||||
createComponentExtension,
|
||||
createReactExtension,
|
||||
createRoutableExtension,
|
||||
} from './extensions';
|
||||
|
||||
const plugin = createPlugin({
|
||||
id: 'my-plugin',
|
||||
});
|
||||
|
||||
describe('extensions', () => {
|
||||
it('should create a react extension with component data', () => {
|
||||
const Component = () => null;
|
||||
|
||||
const extension = createReactExtension({
|
||||
component: Component,
|
||||
data: {
|
||||
myData: { foo: 'bar' },
|
||||
},
|
||||
});
|
||||
|
||||
const ExtensionComponent = plugin.provide(extension);
|
||||
const element = <ExtensionComponent />;
|
||||
|
||||
expect(getComponentData(element, 'core.plugin')).toBe(plugin);
|
||||
expect(getComponentData(element, 'myData')).toEqual({ foo: 'bar' });
|
||||
});
|
||||
|
||||
it('should create react extensions of different types', () => {
|
||||
const Component = () => null;
|
||||
const routeRef = createRouteRef({ path: '/foo', title: 'Foo' });
|
||||
|
||||
const extension1 = createComponentExtension({
|
||||
component: Component,
|
||||
});
|
||||
|
||||
const extension2 = createRoutableExtension({
|
||||
component: Component,
|
||||
mountPoint: routeRef,
|
||||
});
|
||||
|
||||
const ExtensionComponent1 = plugin.provide(extension1);
|
||||
const ExtensionComponent2 = plugin.provide(extension2);
|
||||
|
||||
const element1 = <ExtensionComponent1 />;
|
||||
const element2 = <ExtensionComponent2 />;
|
||||
|
||||
expect(getComponentData(element1, 'core.plugin')).toBe(plugin);
|
||||
expect(getComponentData(element2, 'core.plugin')).toBe(plugin);
|
||||
expect(getComponentData(element2, 'core.mountPoint')).toBe(routeRef);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ExoticComponent, ComponentType } from 'react';
|
||||
import { RouteRef } from '../routing';
|
||||
import { attachComponentData } from './componentData';
|
||||
import { Extension, BackstagePlugin } from '../plugin/types';
|
||||
|
||||
export function createRoutableExtension<Props extends {}>(options: {
|
||||
component: ComponentType<Props>;
|
||||
mountPoint: RouteRef;
|
||||
}): Extension<ExoticComponent<Props>> {
|
||||
const { component, mountPoint } = options;
|
||||
return createReactExtension({
|
||||
component,
|
||||
data: {
|
||||
'core.mountPoint': mountPoint,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function createComponentExtension<Props extends {}>(options: {
|
||||
component: ComponentType<Props>;
|
||||
}): Extension<ExoticComponent<Props>> {
|
||||
const { component } = options;
|
||||
return createReactExtension({ component });
|
||||
}
|
||||
|
||||
export function createReactExtension<Props extends {}>(options: {
|
||||
component: ComponentType<Props>;
|
||||
data?: Record<string, unknown>;
|
||||
}): Extension<ExoticComponent<Props>> {
|
||||
const { component: Component, data = {} } = options;
|
||||
return {
|
||||
expose(plugin: BackstagePlugin): ExoticComponent<Props> {
|
||||
const Result = (props: Props) => <Component {...props} />;
|
||||
|
||||
attachComponentData(Result, 'core.plugin', plugin);
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
attachComponentData(Result, key, value);
|
||||
}
|
||||
|
||||
return Result as ExoticComponent<Props>;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -14,10 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PluginConfig, PluginOutput, BackstagePlugin } from './types';
|
||||
import {
|
||||
PluginConfig,
|
||||
PluginOutput,
|
||||
BackstagePlugin,
|
||||
Extension,
|
||||
} from './types';
|
||||
import { AnyApiFactory } from '../apis';
|
||||
|
||||
export class PluginImpl {
|
||||
export class PluginImpl implements BackstagePlugin {
|
||||
private storedOutput?: PluginOutput[];
|
||||
|
||||
constructor(private readonly config: PluginConfig) {}
|
||||
@@ -65,6 +70,10 @@ export class PluginImpl {
|
||||
return this.storedOutput;
|
||||
}
|
||||
|
||||
provide<T>(extension: Extension<T>): T {
|
||||
return extension.expose(this);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `plugin{${this.config.id}}`;
|
||||
}
|
||||
|
||||
@@ -66,10 +66,15 @@ export type PluginOutput =
|
||||
| RedirectRouteOutput
|
||||
| FeatureFlagOutput;
|
||||
|
||||
export type Extension<T> = {
|
||||
expose(plugin: BackstagePlugin): T;
|
||||
};
|
||||
|
||||
export type BackstagePlugin = {
|
||||
getId(): string;
|
||||
output(): PluginOutput[];
|
||||
getApis(): Iterable<AnyApiFactory>;
|
||||
provide<T>(extension: Extension<T>): T;
|
||||
};
|
||||
|
||||
export type PluginConfig = {
|
||||
|
||||
Reference in New Issue
Block a user