frontend-app-api: initial routing system implementation

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Co-authored-by: Philipp Hugenroth <philipph@spotify.com>
Co-authored-by: Johan Haals <johan.haals@gmail.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-09-01 15:05:18 +02:00
parent 91a534142d
commit 4d410e0985
10 changed files with 102 additions and 15 deletions
+1
View File
@@ -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:^",
+36 -2
View File
@@ -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 (
<>
<RoutingProvider routePaths={routePaths}>
{rootComponents.map((Component, i) => (
<Component key={i} />
))}
</>
</RoutingProvider>
);
},
};
}
export function extractRouteInfoFromInstanceTree(
roots: ExtensionInstance[],
): Map<RouteRef, string> {
const results = new Map<RouteRef, string>();
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;
}
@@ -28,6 +28,7 @@ export const RouteExtension = createExtension({
routes: {
extensionData: {
path: coreExtensionData.routePath,
ref: coreExtensionData.routeRef,
component: coreExtensionData.reactComponent,
},
},
@@ -13,13 +13,40 @@
* 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';
import React, { createContext, ReactNode } from 'react';
export interface RoutingContextType {
resolve(routeRef: RouteRef, options: { pathname: string }): () => string;
resolve(
routeRef: RouteRef,
options: { pathname: string },
): (() => string) | undefined;
}
export const RoutingContext = createContext<RoutingContextType>({
resolve: () => () => '',
});
export class RouteResolver {
constructor(private readonly routePaths: Map<RouteRef, string>) {}
resolve(anyRouteRef: RouteRef<{}>): (() => string) | undefined {
const basePath = this.routePaths.get(anyRouteRef);
if (!basePath) {
return undefined;
}
return () => basePath;
}
}
export function RoutingProvider(props: {
routePaths: Map<RouteRef, string>;
children?: ReactNode;
}) {
return (
<RoutingContext.Provider value={new RouteResolver(props.routePaths)}>
{props.children}
</RoutingContext.Provider>
);
}