diff --git a/packages/app-next/src/examples/graphiqlPlugin.tsx b/packages/app-next/src/examples/graphiqlPlugin.tsx
index e80e137ae5..4e3768dafa 100644
--- a/packages/app-next/src/examples/graphiqlPlugin.tsx
+++ b/packages/app-next/src/examples/graphiqlPlugin.tsx
@@ -23,7 +23,6 @@ import React from 'react';
export const GraphiqlPage = createPageExtension({
id: 'graphiql.page',
defaultPath: '/graphiql',
- disabled: true,
component: () =>
import('@backstage/plugin-graphiql').then(({ Router }) => ),
});
diff --git a/packages/app-next/src/examples/pagesPlugin.tsx b/packages/app-next/src/examples/pagesPlugin.tsx
index 4551f0c8f7..4bf41b772a 100644
--- a/packages/app-next/src/examples/pagesPlugin.tsx
+++ b/packages/app-next/src/examples/pagesPlugin.tsx
@@ -21,7 +21,7 @@ import {
createPlugin,
useRouteRef,
} from '@backstage/frontend-plugin-api';
-import { createRouteRef, createSubRouteRef } from '@backstage/core-plugin-api';
+import { createRouteRef } from '@backstage/core-plugin-api';
import { Route, Routes } from 'react-router-dom';
const indexRouteRef = createRouteRef({ id: 'index' });
@@ -35,7 +35,7 @@ const page1RouteRef = createRouteRef({ id: 'page1' });
const IndexPage = createPageExtension({
id: 'index',
defaultPath: '/',
- // indexRouteRef
+ routeRef: indexRouteRef,
component: async () => {
const Component = () => {
const page1Link = useRouteRef(page1RouteRef);
diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json
index b1f0097a9d..76a7830707 100644
--- a/packages/frontend-app-api/package.json
+++ b/packages/frontend-app-api/package.json
@@ -33,6 +33,7 @@
],
"dependencies": {
"@backstage/config": "workspace:^",
+ "@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/plugin-graphiql": "workspace:^",
"@backstage/types": "workspace:^",
diff --git a/packages/frontend-app-api/src/createApp.tsx b/packages/frontend-app-api/src/createApp.tsx
index 192e2485c6..a52b5c95d7 100644
--- a/packages/frontend-app-api/src/createApp.tsx
+++ b/packages/frontend-app-api/src/createApp.tsx
@@ -30,6 +30,8 @@ import {
mergeExtensionParameters,
readAppExtensionParameters,
} from './wiring/parameters';
+import { RoutingProvider } from './routing/RoutingContext';
+import { RouteRef } from '@backstage/core-plugin-api';
/** @public */
export function createApp(options: { plugins: BackstagePlugin[] }): {
@@ -109,6 +111,8 @@ export function createApp(options: { plugins: BackstagePlugin[] }): {
createInstance(instanceParams),
);
+ const routePaths = extractRouteInfoFromInstanceTree(rootInstances);
+
return {
createRoot() {
const rootComponents = rootInstances.map(
@@ -118,12 +122,42 @@ export function createApp(options: { plugins: BackstagePlugin[] }): {
) as typeof coreExtensionData.reactComponent.T,
);
return (
- <>
+
{rootComponents.map((Component, i) => (
))}
- >
+
);
},
};
}
+
+export function extractRouteInfoFromInstanceTree(
+ roots: ExtensionInstance[],
+): Map {
+ const results = new Map();
+
+ function visit(current: ExtensionInstance, basePath: string) {
+ const routePath = current.data.get(coreExtensionData.routePath.id) ?? '';
+ const routeRef = current.data.get(
+ coreExtensionData.routeRef.id,
+ ) as RouteRef;
+
+ // TODO: join paths in a more robust way
+ const fullPath = basePath + routePath;
+ if (routeRef) {
+ results.set(routeRef, fullPath);
+ }
+
+ for (const children of current.attachments.values()) {
+ for (const child of children) {
+ visit(child, fullPath);
+ }
+ }
+ }
+
+ for (const root of roots) {
+ visit(root, '');
+ }
+ return results;
+}
diff --git a/packages/frontend-app-api/src/extensions/RouteExtension.tsx b/packages/frontend-app-api/src/extensions/RouteExtension.tsx
index dfa5874d70..ffbe802413 100644
--- a/packages/frontend-app-api/src/extensions/RouteExtension.tsx
+++ b/packages/frontend-app-api/src/extensions/RouteExtension.tsx
@@ -28,6 +28,7 @@ export const RouteExtension = createExtension({
routes: {
extensionData: {
path: coreExtensionData.routePath,
+ ref: coreExtensionData.routeRef,
component: coreExtensionData.reactComponent,
},
},
diff --git a/packages/frontend-app-api/src/routing/RoutingContext.tsx b/packages/frontend-app-api/src/routing/RoutingContext.tsx
new file mode 100644
index 0000000000..237c8aa5df
--- /dev/null
+++ b/packages/frontend-app-api/src/routing/RoutingContext.tsx
@@ -0,0 +1,52 @@
+/*
+ * 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 { RouteRef } from '@backstage/core-plugin-api';
+import React, { createContext, ReactNode } from 'react';
+
+export interface RoutingContextType {
+ resolve(
+ routeRef: RouteRef,
+ options: { pathname: string },
+ ): (() => string) | undefined;
+}
+
+export const RoutingContext = createContext({
+ resolve: () => () => '',
+});
+
+export class RouteResolver {
+ constructor(private readonly routePaths: Map) {}
+
+ resolve(anyRouteRef: RouteRef<{}>): (() => string) | undefined {
+ const basePath = this.routePaths.get(anyRouteRef);
+ if (!basePath) {
+ return undefined;
+ }
+ return () => basePath;
+ }
+}
+
+export function RoutingProvider(props: {
+ routePaths: Map;
+ children?: ReactNode;
+}) {
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/packages/frontend-plugin-api/src/index.ts b/packages/frontend-plugin-api/src/index.ts
index 8c87986a14..16f226c483 100644
--- a/packages/frontend-plugin-api/src/index.ts
+++ b/packages/frontend-plugin-api/src/index.ts
@@ -37,3 +37,4 @@ export {
type ExtensionDataValue,
} from './types';
export * from './wiring';
+export * from './routing';
diff --git a/packages/frontend-app-api/src/routing/RoutingContext.ts b/packages/frontend-plugin-api/src/routing/index.ts
similarity index 66%
rename from packages/frontend-app-api/src/routing/RoutingContext.ts
rename to packages/frontend-plugin-api/src/routing/index.ts
index 968cb82d5f..4506538fa0 100644
--- a/packages/frontend-app-api/src/routing/RoutingContext.ts
+++ b/packages/frontend-plugin-api/src/routing/index.ts
@@ -13,13 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { RouteRef } from '@backstage/core-plugin-api';
-import { createContext } from 'react';
-export interface RoutingContextType {
- resolve(routeRef: RouteRef, options: { pathname: string }): () => string;
-}
-
-export const RoutingContext = createContext({
- resolve: () => () => '',
-});
+export { useRouteRef } from './useRouteRef';
diff --git a/packages/frontend-plugin-api/src/routing/useRouteRef.ts b/packages/frontend-plugin-api/src/routing/useRouteRef.ts
index f828240966..fa50522bca 100644
--- a/packages/frontend-plugin-api/src/routing/useRouteRef.ts
+++ b/packages/frontend-plugin-api/src/routing/useRouteRef.ts
@@ -15,11 +15,12 @@
*/
import { RouteRef } from '@backstage/core-plugin-api';
+// eslint-disable-next-line @backstage/no-forbidden-package-imports
import { RoutingContext } from '@backstage/frontend-app-api/src/routing/RoutingContext';
import { useContext, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
-export function useRouteRef(routeRef: RouteRef): () => string | undefined {
+export function useRouteRef(routeRef: RouteRef): () => string {
const { pathname } = useLocation();
const resolver = useContext(RoutingContext);
@@ -28,5 +29,9 @@ export function useRouteRef(routeRef: RouteRef): () => string | undefined {
[resolver, routeRef, pathname],
);
+ if (!routeFunc) {
+ throw new Error(`Failed to resolve routeRef ${routeRef}`);
+ }
+
return routeFunc;
}
diff --git a/yarn.lock b/yarn.lock
index 2e94de61cb..472549f1f8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4311,6 +4311,7 @@ __metadata:
dependencies:
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
+ "@backstage/core-plugin-api": "workspace:^"
"@backstage/frontend-plugin-api": "workspace:^"
"@backstage/plugin-graphiql": "workspace:^"
"@backstage/types": "workspace:^"
@@ -4337,6 +4338,7 @@ __metadata:
zod-to-json-schema: ^3.21.4
peerDependencies:
react: ^16.13.1 || ^17.0.0
+ react-router-dom: 6.0.0-beta.0 || ^6.3.0
languageName: unknown
linkType: soft