diff --git a/.changeset/olive-singers-sparkle.md b/.changeset/olive-singers-sparkle.md
new file mode 100644
index 0000000000..14e45867a0
--- /dev/null
+++ b/.changeset/olive-singers-sparkle.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-plugin-api': patch
+---
+
+Apply fixes to the extension creation API that were mistakenly applied to `@backstage/core-app-api` instead.
diff --git a/.changeset/pink-bags-tickle.md b/.changeset/pink-bags-tickle.md
new file mode 100644
index 0000000000..9c600459e7
--- /dev/null
+++ b/.changeset/pink-bags-tickle.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-app-api': patch
+---
+
+Deprecate and disable the extension creation methods, which were added to this package by mistake and should only exist within `@backstage/core-plugin-api`.
diff --git a/packages/core-app-api/src/extensions/extensions.test.tsx b/packages/core-app-api/src/extensions/extensions.test.tsx
deleted file mode 100644
index e8770dddbb..0000000000
--- a/packages/core-app-api/src/extensions/extensions.test.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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, createRouteRef } from '@backstage/core-plugin-api';
-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 = () =>
;
-
- const extension = createReactExtension({
- component: {
- sync: Component,
- },
- data: {
- myData: { foo: 'bar' },
- },
- });
-
- const ExtensionComponent = plugin.provide(extension);
- const element = ;
-
- expect(getComponentData(element, 'core.plugin')).toBe(plugin);
- expect(getComponentData(element, 'myData')).toEqual({ foo: 'bar' });
- });
-
- it('should create react extensions of different types', () => {
- const Component = () => ;
- const routeRef = createRouteRef({ id: 'foo' });
-
- const extension1 = createComponentExtension({
- component: {
- sync: Component,
- },
- });
-
- const extension2 = createRoutableExtension({
- component: () => Promise.resolve(Component),
- mountPoint: routeRef,
- });
-
- const ExtensionComponent1 = plugin.provide(extension1);
- const ExtensionComponent2 = plugin.provide(extension2);
-
- const element1 = ;
- const element2 = ;
-
- expect(getComponentData(element1, 'core.plugin')).toBe(plugin);
- expect(getComponentData(element2, 'core.plugin')).toBe(plugin);
- expect(getComponentData(element2, 'core.mountPoint')).toBe(routeRef);
- });
-});
diff --git a/packages/core-app-api/src/extensions/extensions.tsx b/packages/core-app-api/src/extensions/extensions.tsx
index 8eab3b905f..a9121c4d15 100644
--- a/packages/core-app-api/src/extensions/extensions.tsx
+++ b/packages/core-app-api/src/extensions/extensions.tsx
@@ -14,15 +14,7 @@
* limitations under the License.
*/
-import React, { lazy, Suspense } from 'react';
-import { attachComponentData } from './componentData';
-import {
- Extension,
- BackstagePlugin,
- RouteRef,
- useRouteRef,
- useApp,
-} from '@backstage/core-plugin-api';
+import { Extension, RouteRef } from '@backstage/core-plugin-api';
type ComponentLoader =
| {
@@ -32,114 +24,31 @@ type ComponentLoader =
sync: T;
};
-// We do not use ComponentType as the return type, since it doesn't let us convey the children prop.
-// ComponentType inserts children as an optional prop whether the inner component accepts it or not,
-// making it impossible to make the usage of children type safe.
+const ERROR_MESSAGE = 'Import this from @backstage/core-plugin-api';
+
+/** @deprecated Import from @backstage/core-plugin-api instead */
export function createRoutableExtension<
T extends (props: any) => JSX.Element | null
->(options: {
+>(_options: {
component: () => Promise;
mountPoint: RouteRef;
}): Extension {
- const { component, mountPoint } = options;
- return createReactExtension({
- component: {
- lazy: () =>
- component().then(
- InnerComponent => {
- const RoutableExtensionWrapper: any = (props: any) => {
- // Validate that the routing is wired up correctly in the App.tsx
- try {
- useRouteRef(mountPoint);
- } catch (error) {
- if (error?.message.startsWith('No path for ')) {
- throw new Error(
- `Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` +
- 'Routable extension components may not be rendered by other components and must be ' +
- 'directly available as an element within the App provider component.',
- );
- }
- throw error;
- }
- return ;
- };
-
- const componentName =
- (InnerComponent as { displayName?: string }).displayName ||
- InnerComponent.name ||
- 'LazyComponent';
-
- RoutableExtensionWrapper.displayName = `RoutableExtension(${componentName})`;
-
- return RoutableExtensionWrapper as T;
- },
- error => {
- const RoutableExtensionWrapper: any = (_: any) => {
- const app = useApp();
- const { BootErrorPage } = app.getComponents();
-
- return ;
- };
- return RoutableExtensionWrapper;
- },
- ),
- },
- data: {
- 'core.mountPoint': mountPoint,
- },
- });
+ throw new Error(ERROR_MESSAGE);
}
-// We do not use ComponentType as the return type, since it doesn't let us convey the children prop.
-// ComponentType inserts children as an optional prop whether the inner component accepts it or not,
-// making it impossible to make the usage of children type safe.
+/** @deprecated Import from @backstage/core-plugin-api instead */
export function createComponentExtension<
T extends (props: any) => JSX.Element | null
->(options: { component: ComponentLoader }): Extension {
- const { component } = options;
- return createReactExtension({ component });
+>(_options: { component: ComponentLoader }): Extension {
+ throw new Error(ERROR_MESSAGE);
}
-// We do not use ComponentType as the return type, since it doesn't let us convey the children prop.
-// ComponentType inserts children as an optional prop whether the inner component accepts it or not,
-// making it impossible to make the usage of children type safe.
+/** @deprecated Import from @backstage/core-plugin-api instead */
export function createReactExtension<
T extends (props: any) => JSX.Element | null
->(options: {
+>(_options: {
component: ComponentLoader;
data?: Record;
}): Extension {
- const { data = {} } = options;
-
- let Component: T;
- if ('lazy' in options.component) {
- const lazyLoader = options.component.lazy;
- Component = (lazy(() =>
- lazyLoader().then(component => ({ default: component })),
- ) as unknown) as T;
- } else {
- Component = options.component.sync;
- }
- const componentName =
- (Component as { displayName?: string }).displayName ||
- Component.name ||
- 'Component';
-
- return {
- expose(plugin: BackstagePlugin) {
- const Result: any = (props: any) => (
-
-
-
- );
-
- attachComponentData(Result, 'core.plugin', plugin);
- for (const [key, value] of Object.entries(data)) {
- attachComponentData(Result, key, value);
- }
-
- Result.displayName = `Extension(${componentName})`;
- return Result;
- },
- };
+ throw new Error(ERROR_MESSAGE);
}
diff --git a/packages/core-plugin-api/src/extensions/extensions.tsx b/packages/core-plugin-api/src/extensions/extensions.tsx
index e56b6901dd..d6597ba8a9 100644
--- a/packages/core-plugin-api/src/extensions/extensions.tsx
+++ b/packages/core-plugin-api/src/extensions/extensions.tsx
@@ -15,6 +15,7 @@
*/
import React, { lazy, Suspense } from 'react';
+import { useApp } from '../app';
import { RouteRef, useRouteRef } from '../routing';
import { attachComponentData } from './componentData';
import { Extension, BackstagePlugin } from '../plugin/types';
@@ -27,8 +28,11 @@ type ComponentLoader =
sync: T;
};
+// We do not use ComponentType as the return type, since it doesn't let us convey the children prop.
+// ComponentType inserts children as an optional prop whether the inner component accepts it or not,
+// making it impossible to make the usage of children type safe.
export function createRoutableExtension<
- T extends (props: any) => JSX.Element
+ T extends (props: any) => JSX.Element | null
>(options: {
component: () => Promise;
mountPoint: RouteRef;
@@ -37,22 +41,44 @@ export function createRoutableExtension<
return createReactExtension({
component: {
lazy: () =>
- component().then(InnerComponent => {
- const RoutableExtensionWrapper = ((props: any) => {
- // Validate that the routing is wired up correctly in the App.tsx
- try {
- useRouteRef(mountPoint);
- } catch {
- throw new Error(
- 'Routable extension component was not discovered in the app element tree. ' +
- 'Routable extension components may not be rendered by other components and must be ' +
- 'directly available as an element within the App provider component.',
- );
- }
- return ;
- }) as T;
- return RoutableExtensionWrapper;
- }),
+ component().then(
+ InnerComponent => {
+ const RoutableExtensionWrapper: any = (props: any) => {
+ // Validate that the routing is wired up correctly in the App.tsx
+ try {
+ useRouteRef(mountPoint);
+ } catch (error) {
+ if (error?.message.startsWith('No path for ')) {
+ throw new Error(
+ `Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` +
+ 'Routable extension components may not be rendered by other components and must be ' +
+ 'directly available as an element within the App provider component.',
+ );
+ }
+ throw error;
+ }
+ return ;
+ };
+
+ const componentName =
+ (InnerComponent as { displayName?: string }).displayName ||
+ InnerComponent.name ||
+ 'LazyComponent';
+
+ RoutableExtensionWrapper.displayName = `RoutableExtension(${componentName})`;
+
+ return RoutableExtensionWrapper as T;
+ },
+ error => {
+ const RoutableExtensionWrapper: any = (_: any) => {
+ const app = useApp();
+ const { BootErrorPage } = app.getComponents();
+
+ return ;
+ };
+ return RoutableExtensionWrapper;
+ },
+ ),
},
data: {
'core.mountPoint': mountPoint,
@@ -60,15 +86,21 @@ export function createRoutableExtension<
});
}
+// We do not use ComponentType as the return type, since it doesn't let us convey the children prop.
+// ComponentType inserts children as an optional prop whether the inner component accepts it or not,
+// making it impossible to make the usage of children type safe.
export function createComponentExtension<
- T extends (props: any) => JSX.Element
+ T extends (props: any) => JSX.Element | null
>(options: { component: ComponentLoader }): Extension {
const { component } = options;
return createReactExtension({ component });
}
+// We do not use ComponentType as the return type, since it doesn't let us convey the children prop.
+// ComponentType inserts children as an optional prop whether the inner component accepts it or not,
+// making it impossible to make the usage of children type safe.
export function createReactExtension<
- T extends (props: any) => JSX.Element
+ T extends (props: any) => JSX.Element | null
>(options: {
component: ComponentLoader;
data?: Record;