diff --git a/packages/app-next/src/examples/notFoundErrorPageExtension.tsx b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx
index a636b2b220..67746f6c68 100644
--- a/packages/app-next/src/examples/notFoundErrorPageExtension.tsx
+++ b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-import {
- createComponentExtension,
- coreComponentRefs,
-} from '@backstage/frontend-plugin-api';
+import { AdaptableComponentBlueprint } from '@backstage/frontend-plugin-api';
import Box from '@material-ui/core/Box';
import Typography from '@material-ui/core/Typography';
import { Button } from '@backstage/core-components';
+import { NotFoundErrorPage } from '@backstage/plugin-app';
export function CustomNotFoundErrorPage() {
return (
@@ -50,8 +48,11 @@ export function CustomNotFoundErrorPage() {
);
}
-export default createComponentExtension({
+export default AdaptableComponentBlueprint.make({
name: 'not-found-error-page',
- ref: coreComponentRefs.notFoundErrorPage,
- loader: { sync: () => CustomNotFoundErrorPage },
+ params: define =>
+ define({
+ component: NotFoundErrorPage,
+ loader: () => CustomNotFoundErrorPage,
+ }),
});
diff --git a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx
index 0a5dd68aa3..2f0cd451e2 100644
--- a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx
+++ b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx
@@ -24,8 +24,6 @@ import {
createFrontendPlugin as createNewPlugin,
FrontendPlugin as NewFrontendPlugin,
appTreeApiRef,
- componentsApiRef,
- coreComponentRefs,
iconsApiRef,
useApi,
routeResolutionApiRef,
@@ -44,6 +42,9 @@ import {
} from '@backstage/version-bridge';
import { convertLegacyRouteRef } from '../convertLegacyRouteRef';
+import { ErrorBoundary, NotFoundErrorPage } from '@backstage/plugin-app';
+import { Progress } from '@backstage/core-components';
+
// Make sure that we only convert each new plugin instance to its legacy equivalent once
const legacyPluginStore = getOrCreateGlobalSingleton(
'legacy-plugin-compatibility-store',
@@ -92,7 +93,6 @@ function toNewPlugin(plugin: LegacyBackstagePlugin): NewFrontendPlugin {
// Recreates the old AppContext APIs using the various new APIs that replaced it
function LegacyAppContextProvider(props: { children: ReactNode }) {
const appTreeApi = useApi(appTreeApiRef);
- const componentsApi = useApi(componentsApiRef);
const iconsApi = useApi(iconsApiRef);
const appContext = useMemo(() => {
@@ -100,15 +100,9 @@ function LegacyAppContextProvider(props: { children: ReactNode }) {
let gatheredPlugins: LegacyBackstagePlugin[] | undefined = undefined;
- const ErrorBoundaryFallback = componentsApi.getComponent(
- coreComponentRefs.errorBoundaryFallback,
- );
const ErrorBoundaryFallbackWrapper: AppComponents['ErrorBoundaryFallback'] =
({ plugin, ...rest }) => (
-
+
);
return {
@@ -141,15 +135,13 @@ function LegacyAppContextProvider(props: { children: ReactNode }) {
getComponents(): AppComponents {
return {
- NotFoundErrorPage: componentsApi.getComponent(
- coreComponentRefs.notFoundErrorPage,
- ),
+ NotFoundErrorPage: NotFoundErrorPage,
BootErrorPage() {
throw new Error(
'The BootErrorPage app component should not be accessed by plugins',
);
},
- Progress: componentsApi.getComponent(coreComponentRefs.progress),
+ Progress: Progress,
Router() {
throw new Error(
'The Router app component should not be accessed by plugins',
@@ -159,7 +151,7 @@ function LegacyAppContextProvider(props: { children: ReactNode }) {
};
},
};
- }, [appTreeApi, componentsApi, iconsApi]);
+ }, [appTreeApi, iconsApi]);
return (
diff --git a/packages/core-compat-api/src/convertLegacyAppOptions.tsx b/packages/core-compat-api/src/convertLegacyAppOptions.tsx
index f6c4e163ad..4895f3234c 100644
--- a/packages/core-compat-api/src/convertLegacyAppOptions.tsx
+++ b/packages/core-compat-api/src/convertLegacyAppOptions.tsx
@@ -16,10 +16,9 @@
import { ComponentType } from 'react';
import {
+ AdaptableComponentBlueprint,
ApiBlueprint,
- coreComponentRefs,
CoreErrorBoundaryFallbackProps,
- createComponentExtension,
createExtension,
createFrontendModule,
ExtensionDefinition,
@@ -39,6 +38,11 @@ import {
} from '@backstage/core-plugin-api';
import { toLegacyPlugin } from './compatWrapper/BackwardsCompatProvider';
import { compatWrapper } from './compatWrapper';
+import { Progress as AdaptableProgress } from '@backstage/core-components';
+import {
+ NotFoundErrorPage as AdaptableNotFoundErrorPage,
+ ErrorBoundary as AdaptableErrorBoundary,
+} from '@backstage/plugin-app';
function componentCompatWrapper(
Component: ComponentType,
@@ -154,20 +158,27 @@ export function convertLegacyAppOptions(
}
if (Progress) {
extensions.push(
- createComponentExtension({
- ref: coreComponentRefs.progress,
- loader: { sync: () => componentCompatWrapper(Progress) },
+ AdaptableComponentBlueprint.make({
+ params: define =>
+ define({
+ component: AdaptableProgress,
+ loader: () => componentCompatWrapper(Progress),
+ }),
}),
);
}
if (NotFoundErrorPage) {
extensions.push(
- createComponentExtension({
- ref: coreComponentRefs.notFoundErrorPage,
- loader: { sync: () => componentCompatWrapper(NotFoundErrorPage) },
+ AdaptableComponentBlueprint.make({
+ params: define =>
+ define({
+ component: AdaptableNotFoundErrorPage,
+ loader: () => componentCompatWrapper(NotFoundErrorPage),
+ }),
}),
);
}
+
if (ErrorBoundaryFallback) {
const WrappedErrorBoundaryFallback = (
props: CoreErrorBoundaryFallbackProps,
@@ -178,12 +189,16 @@ export function convertLegacyAppOptions(
plugin={props.plugin && toLegacyPlugin(props.plugin)}
/>,
);
+
extensions.push(
- createComponentExtension({
- ref: coreComponentRefs.errorBoundaryFallback,
- loader: {
- sync: () => componentCompatWrapper(WrappedErrorBoundaryFallback),
- },
+ AdaptableComponentBlueprint.make({
+ params: define =>
+ define({
+ component: AdaptableErrorBoundary,
+ loader: () =>
+ // todo: types + props lols
+ componentCompatWrapper(WrappedErrorBoundaryFallback),
+ }),
}),
);
}
diff --git a/packages/core-components/src/components/Progress/Progress.tsx b/packages/core-components/src/components/Progress/Progress.tsx
index b7176a5825..27706cb7d1 100644
--- a/packages/core-components/src/components/Progress/Progress.tsx
+++ b/packages/core-components/src/components/Progress/Progress.tsx
@@ -21,7 +21,10 @@ import LinearProgress, {
import { useTheme } from '@material-ui/core/styles';
import { PropsWithChildren, useEffect, useState } from 'react';
-export function Progress(props: PropsWithChildren) {
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+import { createAdaptableComponent } from '../../../../frontend-plugin-api/src/components/createAdaptableComponent';
+
+function ProgressComponent(props: PropsWithChildren) {
const theme = useTheme();
const [isVisible, setIsVisible] = useState(false);
@@ -39,3 +42,8 @@ export function Progress(props: PropsWithChildren) {
);
}
+
+export const Progress = createAdaptableComponent({
+ id: 'core.components.progress',
+ loader: () => ProgressComponent,
+});
diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx
index 7d06d55a92..e7491308b8 100644
--- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx
+++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx
@@ -14,24 +14,25 @@
* limitations under the License.
*/
-import { createComponentRef } from '@backstage/frontend-plugin-api';
+import { createAdaptableComponent } from '@backstage/frontend-plugin-api';
import { DefaultComponentsApi } from './DefaultComponentsApi';
import { render, screen } from '@testing-library/react';
-const testRefA = createComponentRef({ id: 'test.a' });
-const testRefB1 = createComponentRef({ id: 'test.b' });
-const testRefB2 = createComponentRef({ id: 'test.b' });
+const { ref: testRefA } = createAdaptableComponent({ id: 'test.a' });
+const { ref: testRefB1 } = createAdaptableComponent({ id: 'test.b' });
+const { ref: testRefB2 } = createAdaptableComponent({ id: 'test.b' });
describe('DefaultComponentsApi', () => {
it('should provide components', () => {
const api = DefaultComponentsApi.fromComponents([
{
ref: testRefA,
- impl: () => test.a
,
+ loader: () => () => test.a
,
},
]);
- const ComponentA = api.getComponent(testRefA);
+ const ComponentA = api.getComponent(testRefA)?.() as () => JSX.Element;
+
render( );
expect(screen.getByText('test.a')).toBeInTheDocument();
@@ -41,12 +42,12 @@ describe('DefaultComponentsApi', () => {
const api = DefaultComponentsApi.fromComponents([
{
ref: testRefB1,
- impl: () => test.b
,
+ loader: () => () => test.b
,
},
]);
- const ComponentB1 = api.getComponent(testRefB1);
- const ComponentB2 = api.getComponent(testRefB2);
+ const ComponentB1 = api.getComponent(testRefB1)?.() as () => JSX.Element;
+ const ComponentB2 = api.getComponent(testRefB2)?.() as () => JSX.Element;
expect(ComponentB1).toBe(ComponentB2);
diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts
index 317233d797..88265955d6 100644
--- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts
+++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-import { ComponentType } from 'react';
import {
ComponentRef,
ComponentsApi,
- createComponentExtension,
+ AdaptableComponentBlueprint,
} from '@backstage/frontend-plugin-api';
/**
@@ -27,13 +26,18 @@ import {
* @internal
*/
export class DefaultComponentsApi implements ComponentsApi {
- #components: Map>;
+ #components: Map<
+ string,
+ | (() => (props: object) => JSX.Element | null)
+ | (() => Promise<(props: object) => JSX.Element | null>)
+ | undefined
+ >;
static fromComponents(
- components: Array,
+ components: Array,
) {
return new DefaultComponentsApi(
- new Map(components.map(entry => [entry.ref.id, entry.impl])),
+ new Map(components.map(entry => [entry.ref.id, entry.loader])),
);
}
@@ -41,11 +45,13 @@ export class DefaultComponentsApi implements ComponentsApi {
this.#components = components;
}
- getComponent(ref: ComponentRef): ComponentType {
+ getComponent(
+ ref: ComponentRef,
+ ):
+ | (() => (props: object) => JSX.Element | null)
+ | (() => Promise<(props: object) => JSX.Element | null>)
+ | undefined {
const impl = this.#components.get(ref.id);
- if (!impl) {
- throw new Error(`No implementation found for component ref ${ref}`);
- }
return impl;
}
}
diff --git a/packages/frontend-internal/src/wiring/InternalComponentRef.ts b/packages/frontend-internal/src/wiring/InternalComponentRef.ts
index 00d4a253d5..edd1355b76 100644
--- a/packages/frontend-internal/src/wiring/InternalComponentRef.ts
+++ b/packages/frontend-internal/src/wiring/InternalComponentRef.ts
@@ -21,12 +21,10 @@ export const OpaqueComponentRef = OpaqueType.create<{
public: ComponentRef;
versions: {
readonly version: 'v1';
- readonly options: {
- transformProps?: (props: object) => object;
- loader?:
- | (() => (props: object) => JSX.Element | null)
- | (() => Promise<(props: object) => JSX.Element | null>);
- };
+ readonly transformProps?: (props: object) => object;
+ readonly loader?:
+ | (() => (props: object) => JSX.Element | null)
+ | (() => Promise<(props: object) => JSX.Element | null>);
};
}>({
versions: ['v1'],
diff --git a/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts
index c5e9d2c44f..0a46e1d018 100644
--- a/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts
+++ b/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-import { ComponentType } from 'react';
-import { createApiRef, useApi } from '@backstage/core-plugin-api';
import { ComponentRef } from '../../components';
+import { createApiRef } from '../system/ApiRef';
/**
* API for looking up components based on component refs.
@@ -24,8 +23,15 @@ import { ComponentRef } from '../../components';
* @public
*/
export interface ComponentsApi {
- // TODO: Should component refs also provide the default implementation so that we're guaranteed to get a component?
- getComponent(ref: ComponentRef): ComponentType;
+ getComponent<
+ TInnerComponentProps extends {},
+ TExternalComponentProps extends {} = TInnerComponentProps,
+ >(
+ ref: ComponentRef,
+ ):
+ | (() => (props: TInnerComponentProps) => JSX.Element | null)
+ | (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>)
+ | undefined;
}
/**
@@ -36,14 +42,3 @@ export interface ComponentsApi {
export const componentsApiRef = createApiRef({
id: 'core.components',
});
-
-/**
- * @public
- * Returns the component associated with the given ref.
- */
-export function useComponentRef(
- ref: ComponentRef,
-): ComponentType {
- const componentsApi = useApi(componentsApiRef);
- return componentsApi.getComponent(ref);
-}
diff --git a/packages/frontend-plugin-api/src/blueprints/ComponentImplementationBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AdaptableComponentBlueprint.test.tsx
similarity index 79%
rename from packages/frontend-plugin-api/src/blueprints/ComponentImplementationBlueprint.test.tsx
rename to packages/frontend-plugin-api/src/blueprints/AdaptableComponentBlueprint.test.tsx
index cd109a6562..e52076896f 100644
--- a/packages/frontend-plugin-api/src/blueprints/ComponentImplementationBlueprint.test.tsx
+++ b/packages/frontend-plugin-api/src/blueprints/AdaptableComponentBlueprint.test.tsx
@@ -14,23 +14,22 @@
* limitations under the License.
*/
import { renderInTestApp } from '@backstage/frontend-test-utils';
-import { createComponentRef } from '../components';
-import { makeComponentFromRef } from '../components/makeComponentFromRef';
-import { ComponentImplementationBlueprint } from './ComponentImplementationBlueprint';
+import { createAdaptableComponent } from '../components';
+import { AdaptableComponentBlueprint } from './AdaptableComponentBlueprint';
import { PageBlueprint } from './PageBlueprint';
import { waitFor, screen } from '@testing-library/react';
-describe('ComponentImplementationBlueprint', () => {
+describe('AdaptableComponentBlueprint', () => {
it('should allow defining a component override for a component ref', () => {
- const componentRef = createComponentRef({
+ const Component = createAdaptableComponent({
id: 'test.component',
loader: () => (props: { hello: string }) => {props.hello}
,
});
- const extension = ComponentImplementationBlueprint.make({
+ const extension = AdaptableComponentBlueprint.make({
params: define =>
define({
- ref: componentRef,
+ component: Component,
loader: () => props => {
// @ts-expect-error
const t: number = props.hello;
@@ -44,13 +43,11 @@ describe('ComponentImplementationBlueprint', () => {
});
it('should render default component refs in the app', async () => {
- const testComponentRef = createComponentRef({
+ const TestComponent = createAdaptableComponent({
id: 'test.component',
loader: () => (props: { hello: string }) => {props.hello}
,
});
- const TestComponent = makeComponentFromRef({ ref: testComponentRef });
-
renderInTestApp(
, {
extensions: [
PageBlueprint.make({
@@ -69,12 +66,10 @@ describe('ComponentImplementationBlueprint', () => {
});
it('should render a component ref without a default implementation', async () => {
- const testComponentRef = createComponentRef({
+ const TestComponent = createAdaptableComponent({
id: 'test.component',
});
- const TestComponent = makeComponentFromRef({ ref: testComponentRef });
-
renderInTestApp(
, {
extensions: [
PageBlueprint.make({
@@ -94,14 +89,12 @@ describe('ComponentImplementationBlueprint', () => {
});
it('should render a component ref with an async loader implementation', async () => {
- const testComponentRef = createComponentRef({
+ const TestComponent = createAdaptableComponent({
id: 'test.component',
loader: async () => (props: { hello: string }) =>
{props.hello}
,
});
- const TestComponent = makeComponentFromRef({ ref: testComponentRef });
-
renderInTestApp(
, {
extensions: [
PageBlueprint.make({
@@ -120,17 +113,15 @@ describe('ComponentImplementationBlueprint', () => {
});
it('should allow overriding a component ref with the blueprint', async () => {
- const testComponentRef = createComponentRef({
+ const TestComponent = createAdaptableComponent({
id: 'test.component',
loader: () => (props: { hello: string }) => {props.hello}
,
});
- const TestComponent = makeComponentFromRef({ ref: testComponentRef });
-
- const extension = ComponentImplementationBlueprint.make({
+ const extension = AdaptableComponentBlueprint.make({
params: define =>
define({
- ref: testComponentRef,
+ component: TestComponent,
loader: () => props => Override {props.hello}
,
}),
});
diff --git a/packages/frontend-plugin-api/src/blueprints/ComponentImplementationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/AdaptableComponentBlueprint.ts
similarity index 85%
rename from packages/frontend-plugin-api/src/blueprints/ComponentImplementationBlueprint.ts
rename to packages/frontend-plugin-api/src/blueprints/AdaptableComponentBlueprint.ts
index 5d2ddea25e..457a04327c 100644
--- a/packages/frontend-plugin-api/src/blueprints/ComponentImplementationBlueprint.ts
+++ b/packages/frontend-plugin-api/src/blueprints/AdaptableComponentBlueprint.ts
@@ -27,7 +27,7 @@ export const componentDataRef = createExtensionDataRef<{
| (() => Promise<(props: {}) => JSX.Element | null>);
}>().with({ id: 'core.component.component' });
-export const ComponentImplementationBlueprint = createExtensionBlueprint({
+export const AdaptableComponentBlueprint = createExtensionBlueprint({
kind: 'component',
attachTo: { id: 'api:app/components', input: 'components' },
output: [componentDataRef],
@@ -35,7 +35,9 @@ export const ComponentImplementationBlueprint = createExtensionBlueprint({
component: componentDataRef,
},
defineParams[>(params: {
- ref: Ref;
+ component: Ref extends ComponentRef]
+ ? { ref: Ref } & ((props: IExternalComponentProps) => JSX.Element)
+ : never;
loader: Ref extends ComponentRef
?
| (() => (props: IInnerComponentProps) => JSX.Element | null)
@@ -46,7 +48,7 @@ export const ComponentImplementationBlueprint = createExtensionBlueprint({
},
factory: params => [
componentDataRef({
- ref: params.ref,
+ ref: params.component.ref,
loader: params.loader,
}),
],
diff --git a/packages/frontend-plugin-api/src/blueprints/index.ts b/packages/frontend-plugin-api/src/blueprints/index.ts
index 0060571ca2..b7164beab3 100644
--- a/packages/frontend-plugin-api/src/blueprints/index.ts
+++ b/packages/frontend-plugin-api/src/blueprints/index.ts
@@ -33,3 +33,4 @@ export { RouterBlueprint } from './RouterBlueprint';
export { SignInPageBlueprint } from './SignInPageBlueprint';
export { ThemeBlueprint } from './ThemeBlueprint';
export { TranslationBlueprint } from './TranslationBlueprint';
+export { AdaptableComponentBlueprint } from './AdaptableComponentBlueprint';
diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx
index f653fa1742..e4e7e05b0f 100644
--- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx
+++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx
@@ -25,8 +25,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, useComponentRef } from '../apis';
-import { coreComponentRefs } from './coreComponentRefs';
+import { AppNode } from '../apis';
+import { Progress } from '@backstage/core-components';
import { coreExtensionData } from '../wiring';
import { AppNodeProvider } from './AppNodeProvider';
@@ -66,8 +66,9 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) {
);
const plugin = node.spec.plugin;
- const Progress = useComponentRef(coreComponentRefs.progress);
- const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback);
+
+ // todo: fallback
+ const fallback = () => Fallback
;
// Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight
const attributes = {
diff --git a/packages/frontend-plugin-api/src/components/coreComponentRefs.ts b/packages/frontend-plugin-api/src/components/coreComponentRefs.ts
deleted file mode 100644
index 64412b8710..0000000000
--- a/packages/frontend-plugin-api/src/components/coreComponentRefs.ts
+++ /dev/null
@@ -1,43 +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 {
- CoreErrorBoundaryFallbackProps,
- CoreNotFoundErrorPageProps,
- CoreProgressProps,
-} from '../types';
-import { createComponentRef } from './createComponentRef';
-
-const coreProgressComponentRef = createComponentRef({
- id: 'core.components.progress',
-});
-
-const coreNotFoundErrorPageComponentRef =
- createComponentRef({
- id: 'core.components.notFoundErrorPage',
- });
-
-const coreErrorBoundaryFallbackComponentRef =
- createComponentRef({
- id: 'core.components.errorBoundaryFallback',
- });
-
-/** @public */
-export const coreComponentRefs = {
- progress: coreProgressComponentRef,
- notFoundErrorPage: coreNotFoundErrorPageComponentRef,
- errorBoundaryFallback: coreErrorBoundaryFallbackComponentRef,
-};
diff --git a/packages/frontend-plugin-api/src/components/makeComponentFromRef.test.tsx b/packages/frontend-plugin-api/src/components/createAdaptableComponent.test.tsx
similarity index 65%
rename from packages/frontend-plugin-api/src/components/makeComponentFromRef.test.tsx
rename to packages/frontend-plugin-api/src/components/createAdaptableComponent.test.tsx
index a35ede3e09..0712b4a5e0 100644
--- a/packages/frontend-plugin-api/src/components/makeComponentFromRef.test.tsx
+++ b/packages/frontend-plugin-api/src/components/createAdaptableComponent.test.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2025 The Backstage Authors
+ * 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.
@@ -13,14 +13,61 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { render, screen } from '@testing-library/react';
-import { createComponentRef } from './createComponentRef';
-import { makeComponentFromRef } from './makeComponentFromRef';
-describe('makeComponentFromRef', () => {
+import { render, screen } from '@testing-library/react';
+import { createAdaptableComponent } from './createAdaptableComponent';
+
+describe('createAdaptableComponent', () => {
+ it('can be created and read', () => {
+ const { ref } = createAdaptableComponent({ id: 'foo' });
+ expect(ref.id).toBe('foo');
+ expect(String(ref)).toBe('ComponentRef{id=foo}');
+ });
+
+ it('should allow defining a default component implementation', () => {
+ const Test = () => test
;
+
+ createAdaptableComponent<{ foo: string }, { bar: string }>({
+ id: 'foo',
+ loader:
+ () =>
+ ({ foo }) =>
+ ,
+ });
+
+ createAdaptableComponent<{ foo: string }, { bar: string }>({
+ id: 'foo',
+ loader:
+ async () =>
+ ({ foo }) =>
+ ,
+ });
+
+ createAdaptableComponent<{ foo: string }, { bar: string }>({
+ id: 'foo',
+ });
+
+ expect(Test).toBeDefined();
+ });
+
+ it('should allow transformings props', () => {
+ createAdaptableComponent<{ foo: string }, { bar: string }>({
+ id: 'foo',
+ transformProps: props => ({ foo: props.bar }),
+ });
+
+ createAdaptableComponent<{ foo: string }, { bar: string }>({
+ id: 'foo',
+ // @ts-expect-error - this should be an error as foo is not a string
+ transformProps: props => ({ foo: 1 }),
+ });
+
+ expect(true).toBe(true);
+ });
+
describe('sync', () => {
it('should create a component from a ref for sync component', () => {
- const ref = createComponentRef({
+ const Component = createAdaptableComponent({
id: 'random',
loader: () => (props: { name: string }) => {
return {props.name}
;
@@ -30,26 +77,23 @@ describe('makeComponentFromRef', () => {
}),
});
- const Component = makeComponentFromRef({ ref });
render( );
expect(screen.getByTestId('test')).toHaveTextContent('test');
});
it('should render a fallback when theres no default implementation provided', () => {
- const ref = createComponentRef({
+ const Component = createAdaptableComponent({
id: 'random',
});
- const Component = makeComponentFromRef({ ref });
-
render( );
expect(screen.getByTestId('random')).toBeInTheDocument();
});
it('should map props from external to internal', () => {
- const ref = createComponentRef({
+ const Component = createAdaptableComponent({
id: 'random',
transformProps: (props: { name: string }) => ({
uppercase: props.name.toUpperCase(),
@@ -62,8 +106,6 @@ describe('makeComponentFromRef', () => {
},
});
- const Component = makeComponentFromRef({ ref });
-
render( );
expect(screen.getByTestId('test')).toHaveTextContent('TEST');
@@ -72,34 +114,30 @@ describe('makeComponentFromRef', () => {
describe('async', () => {
it('should create a component from a ref for async component', async () => {
- const ref = createComponentRef({
+ const Component = createAdaptableComponent({
id: 'random',
loader: async () => (props: { name: string }) => {
return {props.name}
;
},
});
- const Component = makeComponentFromRef({ ref });
-
render( );
await expect(screen.findByTestId('test')).resolves.toBeInTheDocument();
});
it('should render a fallback when theres no default implementation provided', async () => {
- const ref = createComponentRef({
+ const Component = createAdaptableComponent({
id: 'random',
});
- const Component = makeComponentFromRef({ ref });
-
render( );
await expect(screen.findByTestId('random')).resolves.toBeInTheDocument();
});
it('should map props from external to internal', async () => {
- const ref = createComponentRef({
+ const Component = createAdaptableComponent({
id: 'random',
transformProps: (props: { name: string }) => ({
uppercase: props.name.toUpperCase(),
@@ -112,8 +150,6 @@ describe('makeComponentFromRef', () => {
},
});
- const Component = makeComponentFromRef({ ref });
-
render( );
await expect(screen.findByTestId('test')).resolves.toHaveTextContent(
diff --git a/packages/frontend-plugin-api/src/components/createAdaptableComponent.tsx b/packages/frontend-plugin-api/src/components/createAdaptableComponent.tsx
new file mode 100644
index 0000000000..3a454d5c7e
--- /dev/null
+++ b/packages/frontend-plugin-api/src/components/createAdaptableComponent.tsx
@@ -0,0 +1,119 @@
+/*
+ * 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 { OpaqueComponentRef } from '@internal/frontend';
+import { componentsApiRef, useApi } from '../apis';
+import { lazy, Suspense } from 'react';
+
+/** @public */
+export type ComponentRef<
+ TInnerComponentProps extends {} = {},
+ TExternalComponentProps extends {} = TInnerComponentProps,
+> = {
+ id: string;
+ TProps: TInnerComponentProps;
+ TExternalProps: TExternalComponentProps;
+ $$type: '@backstage/ComponentRef';
+};
+
+export type ComponentRefOptions<
+ TInnerComponentProps extends {},
+ TExternalComponentProps extends {} = TInnerComponentProps,
+> = {
+ id: string;
+ loader?:
+ | (() => (props: TInnerComponentProps) => JSX.Element | null)
+ | (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>);
+ transformProps?: (props: TExternalComponentProps) => TInnerComponentProps;
+};
+
+const useComponentRefApi = () => {
+ try {
+ return useApi(componentsApiRef);
+ } catch (e) {
+ return undefined;
+ }
+};
+
+function makeComponentFromRef<
+ InternalComponentProps extends {},
+ ExternalComponentProps extends {},
+>({
+ ref,
+}: {
+ ref: ComponentRef;
+}): (props: ExternalComponentProps) => JSX.Element {
+ const internalRef = OpaqueComponentRef.toInternal(ref);
+ const FallbackComponent = (p: JSX.IntrinsicAttributes) => (
+
+ );
+
+ const ComponentRefImpl = (props: ExternalComponentProps) => {
+ const api = useComponentRefApi();
+ const ComponentOrPromise =
+ api?.getComponent(ref)?.() ??
+ internalRef.loader?.() ??
+ FallbackComponent;
+
+ const innerProps = internalRef.transformProps?.(props) ?? props;
+
+ if ('then' in ComponentOrPromise) {
+ const DefaultImplementation = lazy(() =>
+ ComponentOrPromise.then(c => {
+ return { default: c };
+ }),
+ );
+
+ return (
+
+
+
+ );
+ }
+
+ return ;
+ };
+
+ return ComponentRefImpl;
+}
+
+export function createAdaptableComponent<
+ TInnerComponentProps extends {},
+ TExternalComponentProps extends {} = TInnerComponentProps,
+>(
+ options: ComponentRefOptions,
+): ((props: TExternalComponentProps) => JSX.Element) & {
+ ref: ComponentRef;
+} {
+ const ref = OpaqueComponentRef.createInstance('v1', {
+ id: options.id,
+ TProps: null as unknown as TInnerComponentProps,
+ TExternalProps: null as unknown as TExternalComponentProps,
+ toString() {
+ return `ComponentRef{id=${options.id}}`;
+ },
+ loader: options.loader as (typeof OpaqueComponentRef.TInternal)['loader'],
+ transformProps:
+ options.transformProps as (typeof OpaqueComponentRef.TInternal)['transformProps'],
+ });
+
+ const component = makeComponentFromRef({ ref });
+ Object.assign(component, { ref });
+
+ return component as {
+ ref: ComponentRef;
+ } & ((props: object) => JSX.Element);
+}
diff --git a/packages/frontend-plugin-api/src/components/createComponentRef.test.tsx b/packages/frontend-plugin-api/src/components/createComponentRef.test.tsx
deleted file mode 100644
index b1bcd66c17..0000000000
--- a/packages/frontend-plugin-api/src/components/createComponentRef.test.tsx
+++ /dev/null
@@ -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 { createComponentRef } from './createComponentRef';
-
-describe('createComponentRef', () => {
- it('can be created and read', () => {
- const ref = createComponentRef({ id: 'foo' });
- expect(ref.id).toBe('foo');
- expect(String(ref)).toBe('ComponentRef{id=foo}');
- });
-
- it('should allow defining a default component implementation', () => {
- const Test = () => test
;
-
- createComponentRef<{ foo: string }, { bar: string }>({
- id: 'foo',
- loader:
- () =>
- ({ foo }) =>
- ,
- });
-
- createComponentRef<{ foo: string }, { bar: string }>({
- id: 'foo',
- loader:
- async () =>
- ({ foo }) =>
- ,
- });
-
- createComponentRef<{ foo: string }, { bar: string }>({
- id: 'foo',
- });
-
- expect(Test).toBeDefined();
- });
-
- it('should allow transformings props', () => {
- createComponentRef<{ foo: string }, { bar: string }>({
- id: 'foo',
- transformProps: props => ({ foo: props.bar }),
- });
-
- createComponentRef<{ foo: string }, { bar: string }>({
- id: 'foo',
- // @ts-expect-error - this should be an error as foo is not a string
- transformProps: props => ({ foo: 1 }),
- });
-
- expect(true).toBe(true);
- });
-});
diff --git a/packages/frontend-plugin-api/src/components/createComponentRef.tsx b/packages/frontend-plugin-api/src/components/createComponentRef.tsx
deleted file mode 100644
index 7c4f027fa6..0000000000
--- a/packages/frontend-plugin-api/src/components/createComponentRef.tsx
+++ /dev/null
@@ -1,59 +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 { OpaqueComponentRef } from '@internal/frontend';
-
-/** @public */
-export type ComponentRef<
- TInnerComponentProps extends {} = {},
- TExternalComponentProps extends {} = TInnerComponentProps,
-> = {
- id: string;
- TProps: TInnerComponentProps;
- TExternalProps: TExternalComponentProps;
- $$type: '@backstage/ComponentRef';
-};
-
-export type ComponentRefOptions<
- TInnerComponentProps extends {},
- TExternalComponentProps extends {} = TInnerComponentProps,
-> = {
- id: string;
- loader?:
- | (() => (props: TInnerComponentProps) => JSX.Element | null)
- | (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>);
- transformProps?: (props: TExternalComponentProps) => TInnerComponentProps;
-};
-
-export function createComponentRef<
- TInnerComponentProps extends {},
- TExternalComponentProps extends {} = TInnerComponentProps,
->(
- options: ComponentRefOptions,
-): ComponentRef {
- return OpaqueComponentRef.createInstance('v1', {
- id: options.id,
- TProps: null as unknown as TInnerComponentProps,
- TExternalProps: null as unknown as TExternalComponentProps,
- toString() {
- return `ComponentRef{id=${options.id}}`;
- },
- options: {
- loader: options.loader,
- transformProps: options.transformProps,
- } as (typeof OpaqueComponentRef.TInternal)['options'],
- });
-}
diff --git a/packages/frontend-plugin-api/src/components/index.ts b/packages/frontend-plugin-api/src/components/index.ts
index 31a53be24c..9c50ba0c43 100644
--- a/packages/frontend-plugin-api/src/components/index.ts
+++ b/packages/frontend-plugin-api/src/components/index.ts
@@ -18,6 +18,8 @@ export {
ExtensionBoundary,
type ExtensionBoundaryProps,
} from './ExtensionBoundary';
-export { coreComponentRefs } from './coreComponentRefs';
-export { createComponentRef, type ComponentRef } from './createComponentRef';
+export {
+ createAdaptableComponent,
+ type ComponentRef,
+} from './createAdaptableComponent';
export { useAppNode } from './AppNodeProvider';
diff --git a/packages/frontend-plugin-api/src/components/makeComponentFromRef.tsx b/packages/frontend-plugin-api/src/components/makeComponentFromRef.tsx
deleted file mode 100644
index 7953549fbc..0000000000
--- a/packages/frontend-plugin-api/src/components/makeComponentFromRef.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2025 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 { lazy, Suspense } from 'react';
-import { ComponentRef } from './createComponentRef';
-import { OpaqueComponentRef } from '@internal/frontend';
-import { componentsApiRef, useApi } from '../apis';
-
-export function makeComponentFromRef<
- InternalComponentProps extends {},
- ExternalComponentProps extends {},
->({
- ref,
-}: {
- ref: ComponentRef;
-}): (props: ExternalComponentProps) => JSX.Element {
- const { options } = OpaqueComponentRef.toInternal(ref);
- const FallbackComponent = (p: JSX.IntrinsicAttributes) => (
-
- );
-
- const ComponentRefImpl = (props: ExternalComponentProps) => {
- // todo(blam): use the component that's in the API ref instead if it's defined.
- // otherwise use the fallback..
- const api = useApi(componentsApiRef);
- const innerProps = options.transformProps?.(props) ?? props;
-
- const ComponentOrPromise = options.loader?.() ?? FallbackComponent;
-
- if ('then' in ComponentOrPromise) {
- const DefaultImplementation = lazy(() =>
- ComponentOrPromise.then(c => {
- return { default: c };
- }),
- );
-
- return (
- // todo: is this necessary? can we remove this?
-
-
-
- );
- }
-
- return ;
- };
-
- return ComponentRefImpl;
-}
diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx
deleted file mode 100644
index f561a73dc6..0000000000
--- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx
+++ /dev/null
@@ -1,72 +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 { lazy, ComponentType } from 'react';
-import { createExtension, createExtensionDataRef } from '../wiring';
-import { ComponentRef } from '../components';
-
-/** @public */
-export function createComponentExtension(options: {
- ref: ComponentRef;
- name?: string;
- disabled?: boolean;
- loader:
- | {
- lazy: () => Promise>;
- }
- | {
- sync: () => ComponentType;
- };
-}) {
- return createExtension({
- kind: 'component',
- name: options.name ?? options.ref.id,
- attachTo: { id: 'api:app/components', input: 'components' },
- disabled: options.disabled,
- output: [createComponentExtension.componentDataRef],
- factory() {
- if ('sync' in options.loader) {
- return [
- createComponentExtension.componentDataRef({
- ref: options.ref,
- impl: options.loader.sync() as ComponentType,
- }),
- ];
- }
- const lazyLoader = options.loader.lazy;
- const ExtensionComponent = lazy(() =>
- lazyLoader().then(Component => ({
- default: Component,
- })),
- ) as unknown as ComponentType;
-
- return [
- createComponentExtension.componentDataRef({
- ref: options.ref,
- impl: ExtensionComponent,
- }),
- ];
- },
- });
-}
-
-/** @public */
-export namespace createComponentExtension {
- export const componentDataRef = createExtensionDataRef<{
- ref: ComponentRef;
- impl: ComponentType;
- }>().with({ id: 'core.component.component' });
-}
diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/packages/frontend-plugin-api/src/extensions/index.ts
deleted file mode 100644
index f75c7474ac..0000000000
--- a/packages/frontend-plugin-api/src/extensions/index.ts
+++ /dev/null
@@ -1,17 +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.
- */
-
-export { createComponentExtension } from './createComponentExtension';
diff --git a/packages/frontend-plugin-api/src/index.ts b/packages/frontend-plugin-api/src/index.ts
index 957ca50bba..f3c633c959 100644
--- a/packages/frontend-plugin-api/src/index.ts
+++ b/packages/frontend-plugin-api/src/index.ts
@@ -24,7 +24,6 @@ export * from './analytics';
export * from './apis';
export * from './blueprints';
export * from './components';
-export * from './extensions';
export * from './icons';
export * from './routing';
export * from './schema';
diff --git a/plugins/app/src/extensions/AppRoutes.tsx b/plugins/app/src/extensions/AppRoutes.tsx
index 1f15ced3ea..6291f24949 100644
--- a/plugins/app/src/extensions/AppRoutes.tsx
+++ b/plugins/app/src/extensions/AppRoutes.tsx
@@ -18,10 +18,9 @@ import {
createExtension,
coreExtensionData,
createExtensionInput,
- coreComponentRefs,
- useComponentRef,
} from '@backstage/frontend-plugin-api';
import { useRoutes } from 'react-router-dom';
+import { NotFoundErrorPage } from './components';
export const AppRoutes = createExtension({
name: 'routes',
@@ -36,10 +35,6 @@ export const AppRoutes = createExtension({
output: [coreExtensionData.reactElement],
factory({ inputs }) {
const Routes = () => {
- const NotFoundErrorPage = useComponentRef(
- coreComponentRefs.notFoundErrorPage,
- );
-
const element = useRoutes([
...inputs.routes.map(route => ({
path: `${route
diff --git a/plugins/app/src/extensions/ComponentsApi.tsx b/plugins/app/src/extensions/ComponentsApi.tsx
index ffba964bc1..1717872a22 100644
--- a/plugins/app/src/extensions/ComponentsApi.tsx
+++ b/plugins/app/src/extensions/ComponentsApi.tsx
@@ -15,7 +15,7 @@
*/
import {
- createComponentExtension,
+ AdaptableComponentBlueprint,
createExtensionInput,
ApiBlueprint,
componentsApiRef,
@@ -30,7 +30,7 @@ export const ComponentsApi = ApiBlueprint.makeWithOverrides({
name: 'components',
inputs: {
components: createExtensionInput(
- [createComponentExtension.componentDataRef],
+ [AdaptableComponentBlueprint.dataRefs.component],
{ replaces: [{ id: 'app', input: 'components' }] },
),
},
@@ -42,7 +42,7 @@ export const ComponentsApi = ApiBlueprint.makeWithOverrides({
factory: () =>
DefaultComponentsApi.fromComponents(
inputs.components.map(i =>
- i.get(createComponentExtension.componentDataRef),
+ i.get(AdaptableComponentBlueprint.dataRefs.component),
),
),
}),
diff --git a/plugins/app/src/extensions/components.tsx b/plugins/app/src/extensions/components.tsx
index c5fa8411c7..6dd46bcd6e 100644
--- a/plugins/app/src/extensions/components.tsx
+++ b/plugins/app/src/extensions/components.tsx
@@ -14,40 +14,14 @@
* limitations under the License.
*/
-import Button from '@material-ui/core/Button';
+import { createAdaptableComponent } from '@backstage/frontend-plugin-api';
-import {
- createComponentExtension,
- 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 '../../../../packages/app-defaults/src/defaults';
-
-export const DefaultProgressComponent = createComponentExtension({
- ref: coreComponentRefs.progress,
- loader: { sync: () => defaultComponents.Progress },
+export const NotFoundErrorPage = createAdaptableComponent({
+ id: 'core.components.notFoundErrorPage',
+ // todo: implementation
});
-export const DefaultNotFoundErrorPageComponent = createComponentExtension({
- ref: coreComponentRefs.notFoundErrorPage,
- loader: { sync: () => defaultComponents.NotFoundErrorPage },
-});
-
-export const DefaultErrorBoundaryComponent = createComponentExtension({
- ref: coreComponentRefs.errorBoundaryFallback,
- loader: {
- sync: () => props => {
- const { plugin, error, resetError } = props;
- const title = `Error in ${plugin?.id}`;
-
- return (
-
-
- Retry
-
-
- );
- },
- },
+export const ErrorBoundary = createAdaptableComponent({
+ id: 'core.components.errorBoundaryFallback',
+ // todo: implementation
});
diff --git a/plugins/app/src/extensions/index.ts b/plugins/app/src/extensions/index.ts
index 090e7c2cae..487494601b 100644
--- a/plugins/app/src/extensions/index.ts
+++ b/plugins/app/src/extensions/index.ts
@@ -26,11 +26,8 @@ export { FeatureFlagsApi } from './FeatureFlagsApi';
export { TranslationsApi } from './TranslationsApi';
export { DefaultSignInPage } from './DefaultSignInPage';
export { dialogDisplayAppRootElement } from './DialogDisplay';
-export {
- DefaultProgressComponent,
- DefaultErrorBoundaryComponent,
- DefaultNotFoundErrorPageComponent,
-} from './components';
+// todo: move these
+export { ErrorBoundary, NotFoundErrorPage } from './components';
export {
oauthRequestDialogAppRootElement,
alertDisplayAppRootElement,
diff --git a/plugins/app/src/index.ts b/plugins/app/src/index.ts
index 24f65df618..d2614dcf66 100644
--- a/plugins/app/src/index.ts
+++ b/plugins/app/src/index.ts
@@ -15,3 +15,5 @@
*/
export { appPlugin as default } from './plugin';
+// todo: remove
+export * from './extensions/components';
diff --git a/plugins/app/src/plugin.ts b/plugins/app/src/plugin.ts
index a1e6602410..09596a7385 100644
--- a/plugins/app/src/plugin.ts
+++ b/plugins/app/src/plugin.ts
@@ -29,9 +29,6 @@ import {
IconsApi,
FeatureFlagsApi,
TranslationsApi,
- DefaultProgressComponent,
- DefaultNotFoundErrorPageComponent,
- DefaultErrorBoundaryComponent,
oauthRequestDialogAppRootElement,
alertDisplayAppRootElement,
DefaultSignInPage,
@@ -58,9 +55,6 @@ export const appPlugin = createFrontendPlugin({
IconsApi,
FeatureFlagsApi,
TranslationsApi,
- DefaultProgressComponent,
- DefaultNotFoundErrorPageComponent,
- DefaultErrorBoundaryComponent,
DefaultSignInPage,
oauthRequestDialogAppRootElement,
alertDisplayAppRootElement,