refactor: lazy load core components

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2023-10-27 09:48:12 +02:00
parent c26c409566
commit 04c8b48fb9
11 changed files with 327 additions and 119 deletions
@@ -0,0 +1,27 @@
/*
* 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';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { useApp } from '../../../core-plugin-api/src/app/useApp';
/** @public */
export function ExtensionError(props: { error: Error }) {
const { error } = props;
const app = useApp();
const { BootErrorPage } = app.getComponents();
return <BootErrorPage step="load-chunk" error={error} />;
}
@@ -14,6 +14,8 @@
* limitations under the License.
*/
export { ExtensionError } from './ExtensionError';
export {
ExtensionBoundary,
type ExtensionBoundaryProps,
@@ -14,81 +14,194 @@
* limitations under the License.
*/
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { AppComponents } from '../../../core-app-api/src/app/types';
import { coreExtensionData, createExtension } from '../wiring';
import React, { lazy } from 'react';
import {
AnyExtensionInputMap,
ExtensionInputValues,
coreExtensionData,
createExtension,
} from '../wiring';
import {
Expand,
CoreProgressComponent,
CoreBootErrorPageComponent,
CoreNotFoundErrorPageComponent,
CoreErrorBoundaryFallbackComponent,
} from '../types';
import { PortableSchema } from '../schema';
import { ExtensionError, ExtensionBoundary } from '../components';
/** @public */
export function createBootErrorPageExtension(options: {
component: AppComponents['BootErrorPage'];
export function createProgressExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
>(options: {
disabled?: boolean;
inputs?: TInputs;
configSchema?: PortableSchema<TConfig>;
component: (options: {
config: TConfig;
inputs: Expand<ExtensionInputValues<TInputs>>;
}) => Promise<CoreProgressComponent>;
}) {
const id = 'core.components.progress';
return createExtension({
id: `core.components.bootErrorPage`,
attachTo: { id: 'core.components', input: 'bootErrorPage' },
inputs: {},
output: {
component: coreExtensionData.components.bootErrorPage,
},
factory({ bind }) {
bind({
component: options.component,
});
},
});
}
/** @public */
export function createNotFoundErrorPageExtension(options: {
component: AppComponents['NotFoundErrorPage'];
}) {
return createExtension({
id: `core.components.notFoundErrorPage`,
attachTo: { id: 'core.components', input: 'notFoundErrorPage' },
inputs: {},
output: {
component: coreExtensionData.components.notFoundErrorPage,
},
factory({ bind }) {
bind({
component: options.component,
});
},
});
}
/** @public */
export function createErrorBoundaryFallbackExtension(options: {
component: AppComponents['ErrorBoundaryFallback'];
}) {
return createExtension({
id: `core.components.errorBoundaryFallback`,
attachTo: { id: 'core.components', input: 'errorBoundaryFallback' },
inputs: {},
output: {
component: coreExtensionData.components.errorBoundaryFallback,
},
factory({ bind }) {
bind({
component: options.component,
});
},
});
}
/** @public */
export function createProgressExtension(options: {
component: AppComponents['Progress'];
}) {
return createExtension({
id: `core.components.progress`,
id,
attachTo: { id: 'core.components', input: 'progress' },
inputs: {},
inputs: options.inputs,
disabled: options.disabled,
configSchema: options.configSchema,
output: {
component: coreExtensionData.components.progress,
},
factory({ bind }) {
factory({ bind, config, inputs, source }) {
const ExtensionComponent = lazy(() =>
options
.component({ config, inputs })
.then(component => ({ default: component }))
.catch(error => ({
default: () => <ExtensionError error={error} />,
})),
);
bind({
component: options.component,
component: props => (
<ExtensionBoundary id={id} source={source}>
<ExtensionComponent {...props} />
</ExtensionBoundary>
),
});
},
});
}
/** @public */
export function createBootErrorPageExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
>(options: {
disabled?: boolean;
inputs?: TInputs;
configSchema?: PortableSchema<TConfig>;
component: (options: {
config: TConfig;
inputs: Expand<ExtensionInputValues<TInputs>>;
}) => Promise<CoreBootErrorPageComponent>;
}) {
const id = 'core.components.bootErrorPage';
return createExtension({
id,
attachTo: { id: 'core.components', input: 'bootErrorPage' },
inputs: options.inputs,
disabled: options.disabled,
configSchema: options.configSchema,
output: {
component: coreExtensionData.components.bootErrorPage,
},
factory({ bind, config, inputs, source }) {
const ExtensionComponent = lazy(() =>
options
.component({ config, inputs })
.then(component => ({ default: component }))
.catch(error => ({
default: () => <ExtensionError error={error} />,
})),
);
bind({
component: props => (
<ExtensionBoundary id={id} source={source}>
<ExtensionComponent {...props} />
</ExtensionBoundary>
),
});
},
});
}
/** @public */
export function createNotFoundErrorPageExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
>(options: {
disabled?: boolean;
inputs?: TInputs;
configSchema?: PortableSchema<TConfig>;
component: (options: {
config: TConfig;
inputs: Expand<ExtensionInputValues<TInputs>>;
}) => Promise<CoreNotFoundErrorPageComponent>;
}) {
const id = 'core.components.notFoundErrorPage';
return createExtension({
id,
attachTo: { id: 'core.components', input: 'notFoundErrorPage' },
inputs: options.inputs,
disabled: options.disabled,
configSchema: options.configSchema,
output: {
component: coreExtensionData.components.notFoundErrorPage,
},
factory({ bind, config, inputs, source }) {
const ExtensionComponent = lazy(() =>
options
.component({ config, inputs })
.then(component => ({ default: component }))
.catch(error => ({
default: () => <ExtensionError error={error} />,
})),
);
bind({
component: props => (
<ExtensionBoundary id={id} source={source}>
<ExtensionComponent {...props} />
</ExtensionBoundary>
),
});
},
});
}
/** @public */
export function createErrorBoundaryFallbackExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
>(options: {
disabled?: boolean;
inputs?: TInputs;
configSchema?: PortableSchema<TConfig>;
component: (options: {
config: TConfig;
inputs: Expand<ExtensionInputValues<TInputs>>;
}) => Promise<CoreErrorBoundaryFallbackComponent>;
}) {
const id = 'core.components.errorBoundaryFallback';
return createExtension({
id,
attachTo: { id: 'core.components', input: 'errorBoundaryFallback' },
inputs: options.inputs,
disabled: options.disabled,
configSchema: options.configSchema,
output: {
component: coreExtensionData.components.errorBoundaryFallback,
},
factory({ bind, config, inputs, source }) {
const ExtensionComponent = lazy(() =>
options
.component({ config, inputs })
.then(component => ({ default: component }))
.catch(error => ({
default: () => <ExtensionError error={error} />,
})),
);
bind({
component: props => (
<ExtensionBoundary id={id} source={source}>
<ExtensionComponent {...props} />
</ExtensionBoundary>
),
});
},
});
@@ -15,7 +15,7 @@
*/
import React, { lazy } from 'react';
import { ExtensionBoundary } from '../components';
import { ExtensionError, ExtensionBoundary } from '../components';
import { createSchemaFromZod, PortableSchema } from '../schema';
import {
coreExtensionData,
@@ -79,7 +79,10 @@ export function createPageExtension<
const ExtensionComponent = lazy(() =>
options
.loader({ config, inputs })
.then(element => ({ default: () => element })),
.then(element => ({ default: () => element }))
.catch(error => ({
default: () => <ExtensionError error={error} />,
})),
);
return {
@@ -29,3 +29,10 @@ export * from './routing';
export * from './schema';
export * from './apis/system';
export * from './wiring';
export type {
CoreProgressComponent,
CoreBootErrorPageComponent,
CoreNotFoundErrorPageComponent,
CoreErrorBoundaryFallbackComponent,
} from './types';
+28
View File
@@ -14,9 +14,37 @@
* limitations under the License.
*/
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { ComponentType, PropsWithChildren } from 'react';
// TODO(Rugvip): This might be a quite useful utility type, maybe add to @backstage/types?
/**
* Utility type to expand type aliases into their equivalent type.
* @ignore
*/
export type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
/** @public */
export type CoreProgressComponent = ComponentType<PropsWithChildren<{}>>;
/** @public */
export type CoreBootErrorPageComponent = ComponentType<
PropsWithChildren<{
step: 'load-config' | 'load-chunk';
error: Error;
}>
>;
/** @public */
export type CoreNotFoundErrorPageComponent = ComponentType<
PropsWithChildren<{}>
>;
/** @public */
export type CoreErrorBoundaryFallbackComponent = ComponentType<
PropsWithChildren<{
plugin?: BackstagePlugin;
error: Error;
resetError: () => void;
}>
>;
@@ -20,9 +20,13 @@ import {
AppTheme,
IconComponent,
} from '@backstage/core-plugin-api';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { AppComponents } from '../../../core-app-api/src/app/types';
import { RouteRef } from '../routing';
import {
CoreProgressComponent,
CoreBootErrorPageComponent,
CoreNotFoundErrorPageComponent,
CoreErrorBoundaryFallbackComponent,
} from '../types';
import { createExtensionDataRef } from './createExtensionDataRef';
/** @public */
@@ -51,17 +55,18 @@ export const coreExtensionData = {
theme: createExtensionDataRef<AppTheme>('core.theme'),
logoElements: createExtensionDataRef<LogoElements>('core.logos'),
components: {
progress: createExtensionDataRef<AppComponents['Progress']>(
progress: createExtensionDataRef<CoreProgressComponent>(
'core.components.progress',
),
bootErrorPage: createExtensionDataRef<AppComponents['BootErrorPage']>(
bootErrorPage: createExtensionDataRef<CoreBootErrorPageComponent>(
'core.components.bootErrorPage',
),
notFoundErrorPage: createExtensionDataRef<
AppComponents['NotFoundErrorPage']
>('core.components.notFoundErrorPage'),
errorBoundaryFallback: createExtensionDataRef<
AppComponents['ErrorBoundaryFallback']
>('core.components.errorBoundary'),
notFoundErrorPage: createExtensionDataRef<CoreNotFoundErrorPageComponent>(
'core.components.notFoundErrorPage',
),
errorBoundaryFallback:
createExtensionDataRef<CoreErrorBoundaryFallbackComponent>(
'core.components.errorBoundary',
),
},
};