diff --git a/packages/frontend-app-api/src/extensions/CoreNav.tsx b/packages/frontend-app-api/src/extensions/CoreNav.tsx
index 3406c1d637..cd12ca129a 100644
--- a/packages/frontend-app-api/src/extensions/CoreNav.tsx
+++ b/packages/frontend-app-api/src/extensions/CoreNav.tsx
@@ -20,8 +20,8 @@ import {
coreExtensionData,
createExtensionInput,
NavTarget,
+ useRouteRef,
} from '@backstage/frontend-plugin-api';
-import { useRouteRef } from '@backstage/core-plugin-api';
import { makeStyles } from '@material-ui/core';
import {
Sidebar,
@@ -66,7 +66,7 @@ const SidebarLogo = () => {
const SidebarNavItem = (props: NavTarget) => {
const { icon: Icon, title, routeRef } = props;
- const to = useRouteRef(routeRef)({});
+ const to = useRouteRef(routeRef)();
// TODO: Support opening modal, for example, the search one
return ;
};
diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromInstanceTree.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromInstanceTree.test.ts
index e70f588e0e..86a071df79 100644
--- a/packages/frontend-app-api/src/routing/extractRouteInfoFromInstanceTree.test.ts
+++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromInstanceTree.test.ts
@@ -15,28 +15,27 @@
*/
import React from 'react';
-import {
- BackstagePlugin,
- RouteRef,
- createRouteRef,
-} from '@backstage/core-plugin-api';
+import { BackstagePlugin } from '@backstage/core-plugin-api';
import { extractRouteInfoFromInstanceTree } from './extractRouteInfoFromInstanceTree';
import {
+ AnyRouteParams,
Extension,
+ RouteRef,
coreExtensionData,
createExtension,
createExtensionInput,
createPlugin,
+ createRouteRef,
} from '@backstage/frontend-plugin-api';
import { createInstances } from '../wiring/createApp';
import { MockConfigApi } from '@backstage/test-utils';
-const ref1 = createRouteRef({ id: 'page1' });
-const ref2 = createRouteRef({ id: 'page2' });
-const ref3 = createRouteRef({ id: 'page3' });
-const ref4 = createRouteRef({ id: 'page4' });
-const ref5 = createRouteRef({ id: 'page5' });
-const refOrder = [ref1, ref2, ref3, ref4, ref5];
+const ref1 = createRouteRef();
+const ref2 = createRouteRef();
+const ref3 = createRouteRef();
+const ref4 = createRouteRef();
+const ref5 = createRouteRef();
+const refOrder: RouteRef[] = [ref1, ref2, ref3, ref4, ref5];
function createTestExtension(options: {
id: string;
diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromInstanceTree.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromInstanceTree.ts
index fa9f87dce0..e6e714e3d1 100644
--- a/packages/frontend-app-api/src/routing/extractRouteInfoFromInstanceTree.ts
+++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromInstanceTree.ts
@@ -14,12 +14,24 @@
* limitations under the License.
*/
-import { RouteRef } from '@backstage/core-plugin-api';
-import { coreExtensionData } from '@backstage/frontend-plugin-api';
+import { RouteRef, coreExtensionData } from '@backstage/frontend-plugin-api';
import { ExtensionInstance } from '../wiring/createExtensionInstance';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
-import { BackstageRouteObject } from '../../../core-app-api/src/routing/types';
import { toLegacyPlugin } from '../wiring/createApp';
+import { BackstagePlugin as LegacyBackstagePlugin } from '@backstage/core-plugin-api';
+
+/**
+ * A duplicate of the react-router RouteObject, but with routeRef added
+ * @internal
+ */
+export interface BackstageRouteObject {
+ caseSensitive: boolean;
+ children?: BackstageRouteObject[];
+ element: React.ReactNode;
+ path: string;
+ routeRefs: Set;
+ plugins: Set;
+}
// We always add a child that matches all subroutes but without any route refs. This makes
// sure that we're always able to match each route no matter how deep the navigation goes.
diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx
index 65f278b49b..0f2a6d38ce 100644
--- a/packages/frontend-app-api/src/wiring/createApp.test.tsx
+++ b/packages/frontend-app-api/src/wiring/createApp.test.tsx
@@ -19,13 +19,13 @@ import {
createExtensionOverrides,
createPageExtension,
createPlugin,
+ createRouteRef,
createThemeExtension,
} from '@backstage/frontend-plugin-api';
import { createApp, createInstances } from './createApp';
import { screen } from '@testing-library/react';
import { MockConfigApi, renderWithEffects } from '@backstage/test-utils';
import React from 'react';
-import { createRouteRef } from '@backstage/core-plugin-api';
const extBaseConfig = {
id: 'test',
@@ -83,14 +83,14 @@ describe('createInstances', () => {
const ExtensionA = createPageExtension({
id: 'A',
defaultPath: '/',
- routeRef: createRouteRef({ id: 'A.route' }),
+ routeRef: createRouteRef(),
loader: async () => Extension A
,
});
const ExtensionB = createPageExtension({
id: 'B',
defaultPath: '/',
- routeRef: createRouteRef({ id: 'B.route' }),
+ routeRef: createRouteRef(),
loader: async () => Extension B
,
});
diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx
index 4b7e4985f3..beb29eacff 100644
--- a/packages/frontend-app-api/src/wiring/createApp.tsx
+++ b/packages/frontend-app-api/src/wiring/createApp.tsx
@@ -21,6 +21,10 @@ import {
coreExtensionData,
ExtensionDataRef,
ExtensionOverrides,
+ ExternalRouteRef,
+ RouteRef,
+ SubRouteRef,
+ useRouteRef,
} from '@backstage/frontend-plugin-api';
import { Core } from '../extensions/Core';
import { CoreRoutes } from '../extensions/CoreRoutes';
@@ -44,11 +48,9 @@ import {
ConfigApi,
configApiRef,
IconComponent,
- RouteRef,
BackstagePlugin as LegacyBackstagePlugin,
featureFlagsApiRef,
attachComponentData,
- useRouteRef,
identityApiRef,
AppTheme,
} from '@backstage/core-plugin-api';
@@ -57,7 +59,6 @@ import {
ApiFactoryRegistry,
ApiProvider,
ApiResolver,
- AppRouteBinder,
AppThemeSelector,
} from '@backstage/core-app-api';
@@ -98,6 +99,57 @@ import {
translationApiRef,
} from '@backstage/core-plugin-api/alpha';
+/**
+ * Extracts a union of the keys in a map whose value extends the given type
+ *
+ * @ignore
+ */
+type KeysWithType = {
+ [key in keyof Obj]: Obj[key] extends Type ? key : never;
+}[keyof Obj];
+
+/**
+ * Takes a map Map required values and makes all keys matching Keys optional
+ *
+ * @ignore
+ */
+type PartialKeys<
+ Map extends { [name in string]: any },
+ Keys extends keyof Map,
+> = Partial> & Required>;
+
+/**
+ * Creates a map of target routes with matching parameters based on a map of external routes.
+ *
+ * @ignore
+ */
+type TargetRouteMap<
+ ExternalRoutes extends { [name: string]: ExternalRouteRef },
+> = {
+ [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef<
+ infer Params,
+ any
+ >
+ ? RouteRef | SubRouteRef
+ : never;
+};
+
+/**
+ * A function that can bind from external routes of a given plugin, to concrete
+ * routes of other plugins. See {@link createApp}.
+ *
+ * @public
+ */
+export type AppRouteBinder = <
+ TExternalRoutes extends { [name: string]: ExternalRouteRef },
+>(
+ externalRoutes: TExternalRoutes,
+ targetRoutes: PartialKeys<
+ TargetRouteMap,
+ KeysWithType>
+ >,
+) => void;
+
/** @public */
export interface ExtensionTreeNode {
id: string;
@@ -312,8 +364,9 @@ export function createApp(options: {
{/* TODO: set base path using the logic from AppRouter */}