Merge pull request #21652 from backstage/camilaibs/app-components-followups

[DI] App components migration follow-up refactors
This commit is contained in:
Camila Belo
2023-12-05 11:28:50 +01:00
committed by GitHub
24 changed files with 149 additions and 294 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-plugin-api': patch
---
Update alpha component ref type to be more specific than any, delete boot page component and use new plugin type for error boundary component extensions.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': patch
---
Use the new plugin type for error boundary components.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-compat-api': patch
---
Delete alpha DI compatibility helper for components, migrating components should be simple without a helper.
+3 -10
View File
@@ -17,7 +17,7 @@
import React from 'react';
import { createApp } from '@backstage/frontend-app-api';
import { pagesPlugin } from './examples/pagesPlugin';
import { CustomNotFoundErrorPage } from './examples/notFoundErrorPageExtension';
import notFoundErrorPage from './examples/notFoundErrorPageExtension';
import graphiqlPlugin from '@backstage/plugin-graphiql/alpha';
import techRadarPlugin from '@backstage/plugin-tech-radar/alpha';
import userSettingsPlugin from '@backstage/plugin-user-settings/alpha';
@@ -33,10 +33,7 @@ import {
} from '@backstage/frontend-plugin-api';
import techdocsPlugin from '@backstage/plugin-techdocs/alpha';
import { homePage } from './HomePage';
import {
collectLegacyComponents,
collectLegacyRoutes,
} from '@backstage/core-compat-api';
import { collectLegacyRoutes } from '@backstage/core-compat-api';
import { FlatRoutes } from '@backstage/core-app-api';
import { Route } from 'react-router';
import { CatalogImportPage } from '@backstage/plugin-catalog-import';
@@ -118,10 +115,6 @@ const collectedLegacyPlugins = collectLegacyRoutes(
</FlatRoutes>,
);
const legacyAppComponents = collectLegacyComponents({
NotFoundErrorPage: CustomNotFoundErrorPage,
});
const app = createApp({
features: [
graphiqlPlugin,
@@ -137,7 +130,7 @@ const app = createApp({
scmAuthExtension,
scmIntegrationApi,
signInPage,
...legacyAppComponents,
notFoundErrorPage,
],
}),
],
@@ -17,7 +17,7 @@
import React from 'react';
import {
createComponentExtension,
coreComponentsRefs,
coreComponentRefs,
} from '@backstage/frontend-plugin-api';
import { Box, Typography } from '@material-ui/core';
import { Button } from '@backstage/core-components';
@@ -36,15 +36,9 @@ export function CustomNotFoundErrorPage() {
>
<Typography variant="h1">404</Typography>
<Typography color="textSecondary" paragraph style={{ width: 300 }}>
Bowie was unable to locate this page. Please contact your support team
if this page used to exist.
Unable to locate this page. Please contact your support team if this
page used to exist.
</Typography>
<img
alt="Backstage bowie"
src="https://info.backstage.spotify.com/hs-fs/hubfs/Call%20Bowie%202.png"
width="200"
style={{ filter: 'grayscale(50%)' }}
/>
<Button
variant="contained"
to="/"
@@ -57,6 +51,6 @@ export function CustomNotFoundErrorPage() {
}
export default createComponentExtension({
ref: coreComponentsRefs.notFoundErrorPage,
ref: coreComponentRefs.notFoundErrorPage,
component: { sync: () => CustomNotFoundErrorPage },
});
-7
View File
@@ -6,9 +6,7 @@
/// <reference types="react" />
import { AnyRouteRefParams } from '@backstage/core-plugin-api';
import { AppComponents } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/frontend-plugin-api';
import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
import { ExtensionOverrides } from '@backstage/frontend-plugin-api';
import { ExternalRouteRef } from '@backstage/core-plugin-api';
import { ExternalRouteRef as ExternalRouteRef_2 } from '@backstage/frontend-plugin-api';
@@ -19,11 +17,6 @@ import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api';
import { SubRouteRef } from '@backstage/core-plugin-api';
import { SubRouteRef as SubRouteRef_2 } from '@backstage/frontend-plugin-api';
// @public (undocumented)
export function collectLegacyComponents(
components: Partial<AppComponents>,
): ExtensionDefinition<unknown>[];
// @public (undocumented)
export function collectLegacyRoutes(
flatRoutesElement: JSX.Element,
@@ -1,66 +0,0 @@
/*
* Copyright 2023 The Backstage Authors
*
* 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 { collectLegacyComponents } from './collectLegacyComponents';
describe('collectLegacyComponents', () => {
const components = {
Progress: () => <div>Progress</div>,
BootErrorPage: () => <div>BootErrorPage</div>,
NotFoundErrorPage: () => <div>NotFoundErrorPage</div>,
ErrorBoundaryFallback: () => <div>ErrorBoundaryFallback</div>,
};
it('should collect legacy routes', () => {
const collected = collectLegacyComponents(components);
expect(
collected.map(p => ({
kind: p.kind,
namespace: p.namespace,
attachTo: p.attachTo,
disabled: p.disabled,
})),
).toEqual([
{
kind: 'component',
namespace: 'core.components.progress',
attachTo: { id: 'core', input: 'components' },
disabled: false,
},
{
kind: 'component',
namespace: 'core.components.bootErrorPage',
attachTo: { id: 'core', input: 'components' },
disabled: false,
},
{
kind: 'component',
namespace: 'core.components.notFoundErrorPage',
attachTo: { id: 'core', input: 'components' },
disabled: false,
},
{
kind: 'component',
namespace: 'core.components.errorBoundaryFallback',
attachTo: { id: 'core', input: 'components' },
disabled: false,
},
]);
});
});
@@ -1,50 +0,0 @@
/*
* Copyright 2023 The Backstage Authors
*
* 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 {
ComponentRef,
createComponentExtension,
coreComponentsRefs,
ExtensionDefinition,
} from '@backstage/frontend-plugin-api';
import { AppComponents } from '@backstage/core-plugin-api';
type ComponentTypes<T = AppComponents> = T[keyof T];
const refs: Record<string, ComponentRef<ComponentTypes>> = {
Progress: coreComponentsRefs.progress,
BootErrorPage: coreComponentsRefs.bootErrorPage,
NotFoundErrorPage: coreComponentsRefs.notFoundErrorPage,
ErrorBoundaryFallback: coreComponentsRefs.errorBoundaryFallback,
};
/** @public */
export function collectLegacyComponents(components: Partial<AppComponents>) {
return Object.entries(components).reduce<ExtensionDefinition<unknown>[]>(
(extensions, [name, component]) => {
const ref = refs[name];
return ref
? extensions.concat(
createComponentExtension({
ref,
component: { sync: () => component },
}),
)
: extensions;
},
[],
);
}
-1
View File
@@ -16,6 +16,5 @@
export * from './compatWrapper';
export { collectLegacyRoutes } from './collectLegacyRoutes';
export { collectLegacyComponents } from './collectLegacyComponents';
export { convertLegacyApp } from './convertLegacyApp';
export { convertLegacyRouteRef } from './convertLegacyRouteRef';
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { ComponentType } from 'react';
import { ComponentRef, ComponentsApi } from '@backstage/frontend-plugin-api';
/**
@@ -22,13 +23,13 @@ import { ComponentRef, ComponentsApi } from '@backstage/frontend-plugin-api';
* @internal
*/
export class DefaultComponentsApi implements ComponentsApi {
#components: Map<ComponentRef<any>, any>;
#components: Map<ComponentRef<any>, ComponentType<any>>;
constructor(components: Map<ComponentRef<any>, any>) {
this.#components = components;
}
getComponent<T>(ref: ComponentRef<T>): T {
getComponent<T extends {}>(ref: ComponentRef<T>): ComponentType<T> {
const impl = this.#components.get(ref);
if (!impl) {
throw new Error(`No implementation found for component ref ${ref}`);
@@ -19,9 +19,8 @@ import {
createExtension,
coreExtensionData,
createExtensionInput,
coreComponentsRefs,
useApi,
componentsApiRef,
coreComponentRefs,
useComponentRef,
} from '@backstage/frontend-plugin-api';
import { useRoutes } from 'react-router-dom';
@@ -41,9 +40,8 @@ export const CoreRoutes = createExtension({
},
factory({ inputs }) {
const Routes = () => {
const componentsApi = useApi(componentsApiRef);
const NotFoundErrorPage = componentsApi.getComponent(
coreComponentsRefs.notFoundErrorPage,
const NotFoundErrorPage = useComponentRef(
coreComponentRefs.notFoundErrorPage,
);
const element = useRoutes([
@@ -14,30 +14,43 @@
* limitations under the License.
*/
import React from 'react';
// TODO: Dependency on MUI should be removed from core packages
import { Button } from '@material-ui/core';
import {
createComponentExtension,
coreComponentsRefs,
coreComponentRefs,
} from '@backstage/frontend-plugin-api';
import { ErrorPanel } from '@backstage/core-components';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { components as defaultComponents } from '../../../app-defaults/src/defaults';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
export const DefaultProgressComponent = createComponentExtension({
ref: coreComponentsRefs.progress,
ref: coreComponentRefs.progress,
component: { sync: () => defaultComponents.Progress },
});
export const DefaultBootErrorPageComponent = createComponentExtension({
ref: coreComponentsRefs.bootErrorPage,
component: { sync: () => defaultComponents.BootErrorPage },
});
export const DefaultNotFoundErrorPageComponent = createComponentExtension({
ref: coreComponentsRefs.notFoundErrorPage,
ref: coreComponentRefs.notFoundErrorPage,
component: { sync: () => defaultComponents.NotFoundErrorPage },
});
export const DefaultErrorBoundaryComponent = createComponentExtension({
ref: coreComponentsRefs.errorBoundaryFallback,
component: { sync: () => defaultComponents.ErrorBoundaryFallback },
ref: coreComponentRefs.errorBoundaryFallback,
component: {
sync: () => props => {
const { plugin, error, resetError } = props;
const title = `Error in ${plugin?.id}`;
return (
<ErrorPanel title={title} error={error} defaultExpanded>
<Button variant="outlined" onClick={resetError}>
Retry
</Button>
</ErrorPanel>
);
},
},
});
@@ -201,10 +201,9 @@ describe('createApp', () => {
</core/router>
]
components [
<component:core.components.progress out=[component.ref] />
<component:core.components.errorBoundaryFallback out=[component.ref] />
<component:core.components.bootErrorPage out=[component.ref] />
<component:core.components.notFoundErrorPage out=[component.ref] />
<component:core.components.progress out=[core.component] />
<component:core.components.errorBoundaryFallback out=[core.component] />
<component:core.components.notFoundErrorPage out=[core.component] />
]
themes [
<theme:app/light out=[core.theme] />
@@ -88,7 +88,6 @@ import { createAppTree } from '../tree';
import {
DefaultProgressComponent,
DefaultErrorBoundaryComponent,
DefaultBootErrorPageComponent,
DefaultNotFoundErrorPageComponent,
} from '../extensions/components';
import { AppNode } from '@backstage/frontend-plugin-api';
@@ -108,7 +107,6 @@ export const builtinExtensions = [
CoreLayout,
DefaultProgressComponent,
DefaultErrorBoundaryComponent,
DefaultBootErrorPageComponent,
DefaultNotFoundErrorPageComponent,
LightTheme,
DarkTheme,
+26 -34
View File
@@ -22,7 +22,6 @@ import { AuthProviderInfo } from '@backstage/core-plugin-api';
import { AuthRequestOptions } from '@backstage/core-plugin-api';
import { BackstageIdentityApi } from '@backstage/core-plugin-api';
import { BackstageIdentityResponse } from '@backstage/core-plugin-api';
import { BackstagePlugin as BackstagePlugin_2 } from '@backstage/core-plugin-api';
import { BackstageUserIdentity } from '@backstage/core-plugin-api';
import { bitbucketAuthApiRef } from '@backstage/core-plugin-api';
import { bitbucketServerAuthApiRef } from '@backstage/core-plugin-api';
@@ -68,7 +67,6 @@ import { OpenIdConnectApi } from '@backstage/core-plugin-api';
import { PendingOAuthRequest } from '@backstage/core-plugin-api';
import { ProfileInfo } from '@backstage/core-plugin-api';
import { ProfileInfoApi } from '@backstage/core-plugin-api';
import { PropsWithChildren } from 'react';
import { default as React_2 } from 'react';
import { ReactNode } from 'react';
import { SessionApi } from '@backstage/core-plugin-api';
@@ -297,7 +295,7 @@ export type CommonAnalyticsContext = {
};
// @public (undocumented)
export type ComponentRef<T> = {
export type ComponentRef<T extends {} = {}> = {
id: string;
T: T;
};
@@ -305,7 +303,7 @@ export type ComponentRef<T> = {
// @public
export interface ComponentsApi {
// (undocumented)
getComponent<T>(ref: ComponentRef<T>): T;
getComponent<T extends {}>(ref: ComponentRef<T>): ComponentType<T>;
}
// @public
@@ -332,29 +330,18 @@ export interface ConfigurableExtensionDataRef<
}
// @public (undocumented)
export type CoreBootErrorPageComponent = ComponentType<
PropsWithChildren<{
step: 'load-config' | 'load-chunk';
error: Error;
}>
>;
// @public (undocumented)
export const coreComponentsRefs: {
progress: ComponentRef<CoreProgressComponent>;
bootErrorPage: ComponentRef<CoreBootErrorPageComponent>;
notFoundErrorPage: ComponentRef<CoreNotFoundErrorPageComponent>;
errorBoundaryFallback: ComponentRef<CoreErrorBoundaryFallbackComponent>;
export const coreComponentRefs: {
progress: ComponentRef<CoreProgressProps>;
notFoundErrorPage: ComponentRef<CoreNotFoundErrorPageProps>;
errorBoundaryFallback: ComponentRef<CoreErrorBoundaryFallbackProps>;
};
// @public (undocumented)
export type CoreErrorBoundaryFallbackComponent = ComponentType<
PropsWithChildren<{
plugin?: BackstagePlugin_2;
error: Error;
resetError: () => void;
}>
>;
export type CoreErrorBoundaryFallbackProps = {
plugin?: BackstagePlugin;
error: Error;
resetError: () => void;
};
// @public (undocumented)
export const coreExtensionData: {
@@ -367,20 +354,20 @@ export const coreExtensionData: {
logoElements: ConfigurableExtensionDataRef<LogoElements, {}>;
component: ConfigurableExtensionDataRef<
{
ref: ComponentRef<ComponentType<any>>;
impl: ComponentType<any>;
ref: ComponentRef;
impl: ComponentType;
},
{}
>;
};
// @public (undocumented)
export type CoreNotFoundErrorPageComponent = ComponentType<
PropsWithChildren<{}>
>;
export type CoreNotFoundErrorPageProps = {
children?: ReactNode;
};
// @public (undocumented)
export type CoreProgressComponent = ComponentType<PropsWithChildren<{}>>;
export type CoreProgressProps = {};
// @public (undocumented)
export function createApiExtension<
@@ -410,11 +397,11 @@ export { createApiRef };
// @public (undocumented)
export function createComponentExtension<
TRef extends ComponentRef<any>,
TProps extends {},
TConfig extends {},
TInputs extends AnyExtensionInputMap,
>(options: {
ref: TRef;
ref: ComponentRef<TProps>;
name?: string;
disabled?: boolean;
inputs?: TInputs;
@@ -424,13 +411,13 @@ export function createComponentExtension<
lazy: (values: {
config: TConfig;
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}) => Promise<TRef['T']>;
}) => Promise<ComponentType<TProps>>;
}
| {
sync: (values: {
config: TConfig;
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}) => TRef['T'];
}) => ComponentType<TProps>;
};
}): ExtensionDefinition<TConfig>;
@@ -1005,6 +992,11 @@ export { useApi };
export { useApiHolder };
// @public
export function useComponentRef<T extends {}>(
ref: ComponentRef<T>,
): ComponentType<T>;
// @public
export function useRouteRef<
TOptional extends boolean,
@@ -14,7 +14,8 @@
* limitations under the License.
*/
import { createApiRef } from '@backstage/core-plugin-api';
import { ComponentType } from 'react';
import { createApiRef, useApi } from '@backstage/core-plugin-api';
import { ComponentRef } from '../../components';
/**
@@ -24,7 +25,7 @@ import { ComponentRef } from '../../components';
*/
export interface ComponentsApi {
// TODO: Should component refs also provide the default implementation so that we're guaranteed to get a component?
getComponent<T>(ref: ComponentRef<T>): T;
getComponent<T extends {}>(ref: ComponentRef<T>): ComponentType<T>;
}
/**
@@ -35,3 +36,14 @@ export interface ComponentsApi {
export const componentsApiRef = createApiRef<ComponentsApi>({
id: 'core.components',
});
/**
* @public
* Returns the component associated with the given ref.
*/
export function useComponentRef<T extends {}>(
ref: ComponentRef<T>,
): ComponentType<T> {
const componentsApi = useApi(componentsApiRef);
return componentsApi.getComponent<T>(ref);
}
@@ -15,54 +15,44 @@
*/
import {
CoreBootErrorPageComponent,
CoreErrorBoundaryFallbackComponent,
CoreNotFoundErrorPageComponent,
CoreProgressComponent,
CoreErrorBoundaryFallbackProps,
CoreNotFoundErrorPageProps,
CoreProgressProps,
} from '../types';
/** @public */
export type ComponentRef<T> = {
export type ComponentRef<T extends {} = {}> = {
id: string;
T: T;
};
/** @public */
export function createComponentRef<T>(options: {
export function createComponentRef<T extends {} = {}>(options: {
id: string;
}): ComponentRef<T> {
const { id } = options;
return {
id,
get T(): T {
throw new Error(`tried to read ComponentRef.T of ${id}`);
},
};
} as ComponentRef<T>;
}
const coreProgressComponentRef = createComponentRef<CoreProgressComponent>({
const coreProgressComponentRef = createComponentRef<CoreProgressProps>({
id: 'core.components.progress',
});
const coreBootErrorPageComponentRef =
createComponentRef<CoreBootErrorPageComponent>({
id: 'core.components.bootErrorPage',
});
const coreNotFoundErrorPageComponentRef =
createComponentRef<CoreNotFoundErrorPageComponent>({
createComponentRef<CoreNotFoundErrorPageProps>({
id: 'core.components.notFoundErrorPage',
});
const coreErrorBoundaryFallbackComponentRef =
createComponentRef<CoreErrorBoundaryFallbackComponent>({
createComponentRef<CoreErrorBoundaryFallbackProps>({
id: 'core.components.errorBoundaryFallback',
});
/** @public */
export const coreComponentsRefs = {
export const coreComponentRefs = {
progress: coreProgressComponentRef,
bootErrorPage: coreBootErrorPageComponentRef,
notFoundErrorPage: coreNotFoundErrorPageComponentRef,
errorBoundaryFallback: coreErrorBoundaryFallbackComponentRef,
};
@@ -14,35 +14,14 @@
* limitations under the License.
*/
import React, { Component, PropsWithChildren } from 'react';
// TODO: Dependency on MUI should be removed from core packages
import { Button } from '@material-ui/core';
import { ErrorPanel } from '@backstage/core-components';
import React, { Component, ComponentType, PropsWithChildren } from 'react';
import { BackstagePlugin } from '../wiring';
import { CoreErrorBoundaryFallbackProps } from '../types';
type DefaultErrorBoundaryFallbackProps = PropsWithChildren<{
type ErrorBoundaryProps = PropsWithChildren<{
plugin?: BackstagePlugin;
error: Error;
resetError: () => void;
fallback: ComponentType<CoreErrorBoundaryFallbackProps>;
}>;
const DefaultErrorBoundaryFallback = ({
plugin,
error,
resetError,
}: DefaultErrorBoundaryFallbackProps) => {
const title = `Error in ${plugin?.id}`;
return (
<ErrorPanel title={title} error={error} defaultExpanded>
<Button variant="outlined" onClick={resetError}>
Retry
</Button>
</ErrorPanel>
);
};
type ErrorBoundaryProps = PropsWithChildren<{ plugin?: BackstagePlugin }>;
type ErrorBoundaryState = { error?: Error };
/** @internal */
@@ -62,12 +41,11 @@ export class ErrorBoundary extends Component<
render() {
const { error } = this.state;
const { plugin, children } = this.props;
const { plugin, children, fallback: Fallback } = this.props;
if (error) {
// TODO: use a configurable error boundary fallback
return (
<DefaultErrorBoundaryFallback
<Fallback
plugin={plugin}
error={error}
resetError={this.handleErrorReset}
@@ -24,7 +24,8 @@ import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api';
import { ErrorBoundary } from './ErrorBoundary';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker';
import { AppNode } from '../apis';
import { AppNode, useComponentRef } from '../apis';
import { coreComponentRefs } from './ComponentRef';
type RouteTrackerProps = PropsWithChildren<{
disableTracking?: boolean;
@@ -57,6 +58,10 @@ export interface ExtensionBoundaryProps {
export function ExtensionBoundary(props: ExtensionBoundaryProps) {
const { node, routable, children } = props;
const plugin = node.spec.source;
const Progress = useComponentRef(coreComponentRefs.progress);
const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback);
// Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight
const attributes = {
extensionId: node.spec.id,
@@ -64,8 +69,8 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) {
};
return (
<Suspense fallback="Loading...">
<ErrorBoundary plugin={node.spec.source}>
<Suspense fallback={<Progress />}>
<ErrorBoundary plugin={plugin} fallback={fallback}>
<AnalyticsContext attributes={attributes}>
<RouteTracker disableTracking={!routable}>{children}</RouteTracker>
</AnalyticsContext>
@@ -14,7 +14,7 @@
* limitations under the License.
*/
export { coreComponentsRefs, type ComponentRef } from './ComponentRef';
export { coreComponentRefs, type ComponentRef } from './ComponentRef';
export {
ExtensionBoundary,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { lazy } from 'react';
import React, { lazy, ComponentType } from 'react';
import {
AnyExtensionInputMap,
ResolvedExtensionInputs,
@@ -27,11 +27,11 @@ import { ExtensionBoundary, ComponentRef } from '../components';
/** @public */
export function createComponentExtension<
TRef extends ComponentRef<any>,
TProps extends {},
TConfig extends {},
TInputs extends AnyExtensionInputMap,
>(options: {
ref: TRef;
ref: ComponentRef<TProps>;
name?: string;
disabled?: boolean;
inputs?: TInputs;
@@ -41,13 +41,13 @@ export function createComponentExtension<
lazy: (values: {
config: TConfig;
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}) => Promise<TRef['T']>;
}) => Promise<ComponentType<TProps>>;
}
| {
sync: (values: {
config: TConfig;
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}) => TRef['T'];
}) => ComponentType<TProps>;
};
}) {
return createExtension({
@@ -62,15 +62,17 @@ export function createComponentExtension<
component: coreExtensionData.component,
},
factory({ config, inputs, node }) {
let ExtensionComponent: TRef['T'];
let ExtensionComponent: ComponentType<TProps>;
if ('sync' in options.component) {
ExtensionComponent = options.component.sync({ config, inputs });
} else {
const loader = options.component.lazy({ config, inputs });
const lazyLoader = options.component.lazy;
ExtensionComponent = lazy(() =>
loader.then(component => ({ default: component })),
);
lazyLoader({ config, inputs }).then(component => ({
default: component,
})),
) as unknown as ComponentType<TProps>;
}
return {
@@ -78,7 +80,7 @@ export function createComponentExtension<
ref: options.ref,
impl: props => (
<ExtensionBoundary node={node}>
<ExtensionComponent {...props} />
<ExtensionComponent {...(props as TProps)} />
</ExtensionBoundary>
),
},
+3 -4
View File
@@ -32,8 +32,7 @@ export * from './translation';
export * from './wiring';
export type {
CoreProgressComponent,
CoreBootErrorPageComponent,
CoreNotFoundErrorPageComponent,
CoreErrorBoundaryFallbackComponent,
CoreProgressProps,
CoreNotFoundErrorPageProps,
CoreErrorBoundaryFallbackProps,
} from './types';
+11 -21
View File
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { ComponentType, PropsWithChildren } from 'react';
import { ReactNode } from 'react';
import { BackstagePlugin } from './wiring';
// TODO(Rugvip): This might be a quite useful utility type, maybe add to @backstage/types?
/**
@@ -25,26 +25,16 @@ import { ComponentType, PropsWithChildren } from 'react';
export type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
/** @public */
export type CoreProgressComponent = ComponentType<PropsWithChildren<{}>>;
export type CoreProgressProps = {};
/** @public */
export type CoreBootErrorPageComponent = ComponentType<
PropsWithChildren<{
step: 'load-config' | 'load-chunk';
error: Error;
}>
>;
export type CoreNotFoundErrorPageProps = {
children?: ReactNode;
};
/** @public */
export type CoreNotFoundErrorPageComponent = ComponentType<
PropsWithChildren<{}>
>;
/** @public */
export type CoreErrorBoundaryFallbackComponent = ComponentType<
PropsWithChildren<{
plugin?: BackstagePlugin;
error: Error;
resetError: () => void;
}>
>;
export type CoreErrorBoundaryFallbackProps = {
plugin?: BackstagePlugin;
error: Error;
resetError: () => void;
};
@@ -47,7 +47,7 @@ export const coreExtensionData = {
theme: createExtensionDataRef<AppTheme>('core.theme'),
logoElements: createExtensionDataRef<LogoElements>('core.logos'),
component: createExtensionDataRef<{
ref: ComponentRef<ComponentType<any>>;
impl: ComponentType<any>;
}>('component.ref'),
ref: ComponentRef;
impl: ComponentType;
}>('core.component'),
};