frontend-*: add and use RouteResolutionsApi

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-01-23 10:19:06 +01:00
parent 086294bda1
commit bc621aaa3d
19 changed files with 448 additions and 272 deletions
@@ -31,7 +31,6 @@ import {
createSignInPageExtension,
} from '@backstage/frontend-plugin-api';
import {
ConfigApi,
IdentityApi,
SignInPageProps,
configApiRef,
@@ -42,6 +41,7 @@ import { InternalAppContext } from '../wiring/InternalAppContext';
import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';
import { BrowserRouter } from 'react-router-dom';
import { RouteTracker } from '../routing/RouteTracker';
import { getBasePath } from '../routing/getBasePath';
export const AppRoot = createExtension({
namespace: 'app',
@@ -97,20 +97,6 @@ export const AppRoot = createExtension({
},
});
/**
* Read the configured base path.
*
* The returned path does not have a trailing slash.
*/
function getBasePath(configApi: ConfigApi) {
let { pathname } = new URL(
configApi.getOptionalString('app.baseUrl') ?? '/',
'http://sample.dev', // baseUrl can be specified as just a path
);
pathname = pathname.replace(/\/*$/, '');
return pathname;
}
// This wraps the sign-in page and waits for sign-in to be completed before rendering the app
function SignInPageWrapper({
component: Component,
@@ -45,20 +45,26 @@ const externalRef2 = createExternalRouteRef({ optional: true });
const externalRef3 = createExternalRouteRef({ params: ['x'] });
const externalRef4 = createExternalRouteRef({ optional: true, params: ['x'] });
function src(sourcePath: string) {
return { sourcePath };
}
describe('RouteResolver', () => {
it('should not resolve anything with an empty resolver', () => {
const r = new RouteResolver(new Map(), new Map(), [], new Map(), '');
expect(r.resolve(ref1, '/')?.()).toBe(undefined);
expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined);
expect(r.resolve(subRef1, '/')?.()).toBe(undefined);
expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe(undefined);
expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined);
expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined);
expect(r.resolve(externalRef1, '/')?.()).toBe(undefined);
expect(r.resolve(externalRef2, '/')?.()).toBe(undefined);
expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined);
expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined);
expect(r.resolve(ref1, src('/'))?.()).toBe(undefined);
expect(r.resolve(ref2, src('/'))?.({ x: '1x' })).toBe(undefined);
expect(r.resolve(subRef1, src('/'))?.()).toBe(undefined);
expect(r.resolve(subRef2, src('/'))?.({ a: '2a' })).toBe(undefined);
expect(r.resolve(subRef3, src('/'))?.({ x: '3x' })).toBe(undefined);
expect(r.resolve(subRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe(
undefined,
);
expect(r.resolve(externalRef1, src('/'))?.()).toBe(undefined);
expect(r.resolve(externalRef2, src('/'))?.()).toBe(undefined);
expect(r.resolve(externalRef3, src('/'))?.({ x: '5x' })).toBe(undefined);
expect(r.resolve(externalRef4, src('/'))?.({ x: '6x' })).toBe(undefined);
});
it('should resolve an absolute route', () => {
@@ -70,16 +76,20 @@ describe('RouteResolver', () => {
'',
);
expect(r.resolve(ref1, '/')?.()).toBe('/my-route');
expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined);
expect(r.resolve(subRef1, '/')?.()).toBe('/my-route/foo');
expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe('/my-route/foo/2a');
expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined);
expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined);
expect(r.resolve(externalRef1, '/')?.()).toBe(undefined);
expect(r.resolve(externalRef2, '/')?.()).toBe(undefined);
expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined);
expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined);
expect(r.resolve(ref1, src('/'))?.()).toBe('/my-route');
expect(r.resolve(ref2, src('/'))?.({ x: '1x' })).toBe(undefined);
expect(r.resolve(subRef1, src('/'))?.()).toBe('/my-route/foo');
expect(r.resolve(subRef2, src('/'))?.({ a: '2a' })).toBe(
'/my-route/foo/2a',
);
expect(r.resolve(subRef3, src('/'))?.({ x: '3x' })).toBe(undefined);
expect(r.resolve(subRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe(
undefined,
);
expect(r.resolve(externalRef1, src('/'))?.()).toBe(undefined);
expect(r.resolve(externalRef2, src('/'))?.()).toBe(undefined);
expect(r.resolve(externalRef3, src('/'))?.({ x: '5x' })).toBe(undefined);
expect(r.resolve(externalRef4, src('/'))?.({ x: '6x' })).toBe(undefined);
});
it('should resolve an absolute route and sub route with an app base path', () => {
@@ -104,37 +114,39 @@ describe('RouteResolver', () => {
'/base',
);
expect(r.resolve(ref1, '/my-parent/1x')?.()).toBe(
expect(r.resolve(ref1, src('/my-parent/1x'))?.()).toBe(
'/base/my-parent/1x/my-route',
);
expect(r.resolve(ref1, '/base/my-parent/1x')?.()).toBe(
expect(r.resolve(ref1, src('/base/my-parent/1x'))?.()).toBe(
'/base/my-parent/1x/my-route',
);
expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe('/base/my-parent/1x');
expect(r.resolve(ref2, '/base')?.({ x: '1x' })).toBe('/base/my-parent/1x');
expect(r.resolve(ref3, '/')?.({ y: '1y' })).toBe(undefined);
expect(r.resolve(subRef1, '/my-parent/2x')?.()).toBe(
expect(r.resolve(ref2, src('/'))?.({ x: '1x' })).toBe('/base/my-parent/1x');
expect(r.resolve(ref2, src('/base'))?.({ x: '1x' })).toBe(
'/base/my-parent/1x',
);
expect(r.resolve(ref3, src('/'))?.({ y: '1y' })).toBe(undefined);
expect(r.resolve(subRef1, src('/my-parent/2x'))?.()).toBe(
'/base/my-parent/2x/my-route/foo',
);
expect(r.resolve(subRef1, '/base/my-parent/2x')?.()).toBe(
expect(r.resolve(subRef1, src('/base/my-parent/2x'))?.()).toBe(
'/base/my-parent/2x/my-route/foo',
);
expect(r.resolve(subRef2, '/my-parent/3x')?.({ a: '2a' })).toBe(
expect(r.resolve(subRef2, src('/my-parent/3x'))?.({ a: '2a' })).toBe(
'/base/my-parent/3x/my-route/foo/2a',
);
expect(r.resolve(subRef2, '/base/my-parent/3x')?.({ a: '2a' })).toBe(
expect(r.resolve(subRef2, src('/base/my-parent/3x'))?.({ a: '2a' })).toBe(
'/base/my-parent/3x/my-route/foo/2a',
);
expect(r.resolve(subRef3, '/')?.({ x: '5x' })).toBe(
expect(r.resolve(subRef3, src('/'))?.({ x: '5x' })).toBe(
'/base/my-parent/5x/bar',
);
expect(r.resolve(subRef4, '/')?.({ x: '6x', a: '4a' })).toBe(
expect(r.resolve(subRef4, src('/'))?.({ x: '6x', a: '4a' })).toBe(
'/base/my-parent/6x/bar/4a',
);
expect(r.resolve(externalRef1, '/')?.()).toBe(undefined);
expect(r.resolve(externalRef2, '/')?.()).toBe(undefined);
expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined);
expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined);
expect(r.resolve(externalRef1, src('/'))?.()).toBe(undefined);
expect(r.resolve(externalRef2, src('/'))?.()).toBe(undefined);
expect(r.resolve(externalRef3, src('/'))?.({ x: '5x' })).toBe(undefined);
expect(r.resolve(externalRef4, src('/'))?.({ x: '6x' })).toBe(undefined);
});
it('should resolve an absolute route with a param and with a parent', () => {
@@ -163,22 +175,26 @@ describe('RouteResolver', () => {
'',
);
expect(r.resolve(ref1, '/')?.()).toBe('/my-route');
expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe('/my-route/my-parent/1x');
expect(r.resolve(subRef1, '/')?.()).toBe('/my-route/foo');
expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe('/my-route/foo/2a');
expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(
expect(r.resolve(ref1, src('/'))?.()).toBe('/my-route');
expect(r.resolve(ref2, src('/'))?.({ x: '1x' })).toBe(
'/my-route/my-parent/1x',
);
expect(r.resolve(subRef1, src('/'))?.()).toBe('/my-route/foo');
expect(r.resolve(subRef2, src('/'))?.({ a: '2a' })).toBe(
'/my-route/foo/2a',
);
expect(r.resolve(subRef3, src('/'))?.({ x: '3x' })).toBe(
'/my-route/my-parent/3x/bar',
);
expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(
expect(r.resolve(subRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe(
'/my-route/my-parent/4x/bar/4a',
);
expect(r.resolve(externalRef1, '/')?.()).toBe('/my-route');
expect(r.resolve(externalRef2, '/')?.()).toBe(undefined);
expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(
expect(r.resolve(externalRef1, src('/'))?.()).toBe('/my-route');
expect(r.resolve(externalRef2, src('/'))?.()).toBe(undefined);
expect(r.resolve(externalRef3, src('/'))?.({ x: '5x' })).toBe(
'/my-route/my-parent/5x',
);
expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(
expect(r.resolve(externalRef4, src('/'))?.({ x: '6x' })).toBe(
'/my-route/my-parent/6x/bar',
);
});
@@ -221,18 +237,20 @@ describe('RouteResolver', () => {
'',
);
expect(r.resolve(ref2, '/')?.({ x: 'x' })).toBe('/root/x');
expect(r.resolve(ref3, '/root/x')?.({ y: 'y' })).toBe('/root/x/sub/y');
expect(r.resolve(ref2, src('/'))?.({ x: 'x' })).toBe('/root/x');
expect(r.resolve(ref3, src('/root/x'))?.({ y: 'y' })).toBe('/root/x/sub/y');
expect(() => r.resolve(ref1, '/')?.()).toThrow(
expect(() => r.resolve(ref1, src('/'))?.()).toThrow(
/^Cannot route.*with parent.*as it has parameters$/,
);
expect(() => r.resolve(ref1, '/root/x')?.()).toThrow(
expect(() => r.resolve(ref1, src('/root/x'))?.()).toThrow(
/^Cannot route.*with parent.*as it has parameters$/,
);
expect(r.resolve(ref1, '/root/x/sub/y')?.()).toBe('/root/x/sub/y/deep');
expect(r.resolve(ref1, src('/root/x/sub/y'))?.()).toBe(
'/root/x/sub/y/deep',
);
// Without the MATCH_ALL_ROUTE, we wouldn't properly match the route here
expect(r.resolve(ref1, '/root/x/sub/y/any/nested/path/here')?.()).toBe(
expect(r.resolve(ref1, src('/root/x/sub/y/any/nested/path/here'))?.()).toBe(
'/root/x/sub/y/deep',
);
});
@@ -276,62 +294,62 @@ describe('RouteResolver', () => {
);
const l = '/my-grandparent/my-y/my-parent/my-x';
expect(r.resolve(ref1, l)?.()).toBe(
expect(r.resolve(ref1, src(l))?.()).toBe(
'/my-grandparent/my-y/my-parent/my-x/my-route',
);
expect(() => r.resolve(ref1, '/')?.()).toThrow(
expect(() => r.resolve(ref1, src('/'))?.()).toThrow(
/^Cannot route.*with parent.*as it has parameters$/,
);
expect(r.resolve(ref2, l)?.({ x: '1x' })).toBe(
expect(r.resolve(ref2, src(l))?.({ x: '1x' })).toBe(
'/my-grandparent/my-y/my-parent/1x',
);
expect(r.resolve(ref2, '/my-grandparent/my-y')?.({ x: '1x' })).toBe(
expect(r.resolve(ref2, src('/my-grandparent/my-y'))?.({ x: '1x' })).toBe(
'/my-grandparent/my-y/my-parent/1x',
);
expect(() => r.resolve(ref2, '/')?.({ x: '1x' })).toThrow(
expect(() => r.resolve(ref2, src('/'))?.({ x: '1x' })).toThrow(
/^Cannot route.*with parent.*as it has parameters$/,
);
expect(r.resolve(subRef1, l)?.()).toBe(
expect(r.resolve(subRef1, src(l))?.()).toBe(
'/my-grandparent/my-y/my-parent/my-x/my-route/foo',
);
expect(() => r.resolve(subRef1, '/')?.()).toThrow(
expect(() => r.resolve(subRef1, src('/'))?.()).toThrow(
/^Cannot route.*with parent.*as it has parameters$/,
);
expect(r.resolve(subRef2, l)?.({ a: '2a' })).toBe(
expect(r.resolve(subRef2, src(l))?.({ a: '2a' })).toBe(
'/my-grandparent/my-y/my-parent/my-x/my-route/foo/2a',
);
expect(() => r.resolve(subRef2, '/')?.({ a: '2a' })).toThrow(
expect(() => r.resolve(subRef2, src('/'))?.({ a: '2a' })).toThrow(
/^Cannot route.*with parent.*as it has parameters$/,
);
expect(r.resolve(subRef3, l)?.({ x: '3x' })).toBe(
expect(r.resolve(subRef3, src(l))?.({ x: '3x' })).toBe(
'/my-grandparent/my-y/my-parent/3x/bar',
);
expect(r.resolve(subRef3, '/my-grandparent/my-y')?.({ x: '3x' })).toBe(
expect(r.resolve(subRef3, src('/my-grandparent/my-y'))?.({ x: '3x' })).toBe(
'/my-grandparent/my-y/my-parent/3x/bar',
);
expect(r.resolve(subRef4, l)?.({ x: '4x', a: '4a' })).toBe(
expect(r.resolve(subRef4, src(l))?.({ x: '4x', a: '4a' })).toBe(
'/my-grandparent/my-y/my-parent/4x/bar/4a',
);
expect(
r.resolve(subRef4, '/my-grandparent/my-y')?.({ x: '4x', a: '4a' }),
r.resolve(subRef4, src('/my-grandparent/my-y'))?.({ x: '4x', a: '4a' }),
).toBe('/my-grandparent/my-y/my-parent/4x/bar/4a');
expect(r.resolve(externalRef1, l)?.()).toBe(
expect(r.resolve(externalRef1, src(l))?.()).toBe(
'/my-grandparent/my-y/my-parent/my-x/my-route',
);
expect(() => r.resolve(externalRef1, '/')?.()).toThrow(
expect(() => r.resolve(externalRef1, src('/'))?.()).toThrow(
/^Cannot route.*with parent.*as it has parameters$/,
);
expect(r.resolve(externalRef2, l)?.()).toBe(undefined);
expect(r.resolve(externalRef3, l)?.({ x: '5x' })).toBe(
expect(r.resolve(externalRef2, src(l))?.()).toBe(undefined);
expect(r.resolve(externalRef3, src(l))?.({ x: '5x' })).toBe(
'/my-grandparent/my-y/my-parent/5x',
);
expect(() => r.resolve(externalRef3, '/')?.({ x: '5x' })).toThrow(
expect(() => r.resolve(externalRef3, src('/'))?.({ x: '5x' })).toThrow(
/^Cannot route.*with parent.*as it has parameters$/,
);
expect(r.resolve(externalRef4, l)?.({ x: '6x' })).toBe(
expect(r.resolve(externalRef4, src(l))?.({ x: '6x' })).toBe(
'/my-grandparent/my-y/my-parent/6x/bar',
);
expect(() => r.resolve(externalRef4, '/')?.({ x: '6x' })).toThrow(
expect(() => r.resolve(externalRef4, src('/'))?.({ x: '6x' })).toThrow(
/^Cannot route.*with parent.*as it has parameters$/,
);
});
@@ -358,7 +376,7 @@ describe('RouteResolver', () => {
'/base',
);
expect(r.resolve(ref2, '/')?.({ x: 'a/#&?b' })).toBe(
expect(r.resolve(ref2, src('/'))?.({ x: 'a/#&?b' })).toBe(
'/base/my-parent/a%2F%23%26%3Fb',
);
});
@@ -21,6 +21,8 @@ import {
SubRouteRef,
AnyRouteRefParams,
RouteFunc,
RouteResolutionApiResolveOptions,
RouteResolutionApi,
} from '@backstage/frontend-plugin-api';
import mapValues from 'lodash/mapValues';
import { AnyRouteRef, BackstageRouteObject } from './types';
@@ -177,7 +179,7 @@ function resolveBasePath(
return `${joinPaths(parentPath, ...diffPaths)}/`;
}
export class RouteResolver {
export class RouteResolver implements RouteResolutionApi {
constructor(
private readonly routePaths: Map<RouteRef, string>,
private readonly routeParents: Map<RouteRef, RouteRef | undefined>,
@@ -189,13 +191,13 @@ export class RouteResolver {
private readonly appBasePath: string, // base path without a trailing slash
) {}
resolve<Params extends AnyRouteRefParams>(
resolve<TParams extends AnyRouteRefParams>(
anyRouteRef:
| RouteRef<Params>
| SubRouteRef<Params>
| ExternalRouteRef<Params, any>,
sourceLocation: Parameters<typeof matchRoutes>[1],
): RouteFunc<Params> | undefined {
| RouteRef<TParams>
| SubRouteRef<TParams>
| ExternalRouteRef<TParams, any>,
options?: RouteResolutionApiResolveOptions,
): RouteFunc<TParams> | undefined {
// First figure out what our target absolute ref is, as well as our target path.
const [targetRef, targetPath] = resolveTargetRef(
anyRouteRef,
@@ -208,17 +210,7 @@ export class RouteResolver {
// The location that we get passed in uses the full path, so start by trimming off
// the app base path prefix in case we're running the app on a sub-path.
let relativeSourceLocation: Parameters<typeof matchRoutes>[1];
if (typeof sourceLocation === 'string') {
relativeSourceLocation = this.trimPath(sourceLocation);
} else if (sourceLocation.pathname) {
relativeSourceLocation = {
...sourceLocation,
pathname: this.trimPath(sourceLocation.pathname),
};
} else {
relativeSourceLocation = sourceLocation;
}
const relativeSourceLocation = this.trimPath(options?.sourcePath ?? '');
// Next we figure out the base path, which is the combination of the common parent path
// between our current location and our target location, as well as the additional path
@@ -233,7 +225,7 @@ export class RouteResolver {
this.routeObjects,
);
const routeFunc: RouteFunc<Params> = (...[params]) => {
const routeFunc: RouteFunc<TParams> = (...[params]) => {
// We selectively encode some some known-dangerous characters in the
// params. The reason that we don't perform a blanket `encodeURIComponent`
// here is that this encoding was added defensively long after the initial
@@ -1,66 +0,0 @@
/*
* Copyright 2020 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 React, { ReactNode } from 'react';
import {
ExternalRouteRef,
RouteRef,
SubRouteRef,
} from '@backstage/frontend-plugin-api';
import {
createVersionedValueMap,
createVersionedContext,
} from '@backstage/version-bridge';
import { RouteResolver } from './RouteResolver';
import { BackstageRouteObject } from './types';
const RoutingContext = createVersionedContext<{ 1: RouteResolver }>(
'routing-context',
);
type ProviderProps = {
routePaths: Map<RouteRef, string>;
routeParents: Map<RouteRef, RouteRef | undefined>;
routeObjects: BackstageRouteObject[];
routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>;
basePath?: string;
children: ReactNode;
};
// TODO(Rugvip): Migrate to a routing API instead
export const RoutingProvider = ({
routePaths,
routeParents,
routeObjects,
routeBindings,
basePath = '',
children,
}: ProviderProps) => {
const resolver = new RouteResolver(
routePaths,
routeParents,
routeObjects,
routeBindings,
basePath,
);
const versionedValue = createVersionedValueMap({ 1: resolver });
return (
<RoutingContext.Provider value={versionedValue}>
{children}
</RoutingContext.Provider>
);
};
@@ -0,0 +1,27 @@
/*
* Copyright 2024 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 { ConfigApi } from '@backstage/frontend-plugin-api';
/** @internal */
export function getBasePath(configApi: ConfigApi) {
let { pathname } = new URL(
configApi.getOptionalString('app.baseUrl') ?? '/',
'http://sample.dev', // baseUrl can be specified as just a path
);
pathname = pathname.replace(/\/*$/, '');
return pathname;
}
@@ -31,6 +31,8 @@ import {
FrontendFeature,
iconsApiRef,
RouteRef,
RouteResolutionApi,
routeResolutionApiRef,
useRouteRef,
} from '@backstage/frontend-plugin-api';
import { App } from '../extensions/App';
@@ -89,7 +91,7 @@ import {
translationApiRef,
} from '@backstage/core-plugin-api/alpha';
import { CreateAppRouteBinder } from '../routing';
import { RoutingProvider } from '../routing/RoutingProvider';
import { RouteResolver } from '../routing/RouteResolver';
import { resolveRouteBindings } from '../routing/resolveRouteBindings';
import { collectRouteIds } from '../routing/collectRouteIds';
import { createAppTree } from '../tree';
@@ -110,6 +112,7 @@ import { DefaultIconsApi } from '../apis/implementations/IconsApi';
import { stringifyError } from '@backstage/errors';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { icons as defaultIcons } from '../../../app-defaults/src/defaults';
import { getBasePath } from '../routing/getBasePath';
const DefaultApis = defaultApis.map(factory => createApiExtension({ factory }));
@@ -354,11 +357,25 @@ export function createSpecializedApp(options?: {
config,
});
const routeInfo = extractRouteInfoFromAppNode(tree.root);
const routeBindings = resolveRouteBindings(
options?.bindRoutes,
config,
collectRouteIds(features),
);
const appIdentityProxy = new AppIdentityProxy();
const apiHolder = createApiHolder(
tree,
config,
appIdentityProxy,
new RouteResolver(
routeInfo.routePaths,
routeInfo.routeParents,
routeInfo.routeObjects,
routeBindings,
getBasePath(config),
),
options?.icons,
);
@@ -381,24 +398,16 @@ export function createSpecializedApp(options?: {
}
}
const routeInfo = extractRouteInfoFromAppNode(tree.root);
const routeBindings = resolveRouteBindings(
options?.bindRoutes,
config,
collectRouteIds(features),
);
const rootEl = tree.root.instance!.getData(coreExtensionData.reactElement);
const AppComponent = () => (
<ApiProvider apis={apiHolder}>
<AppThemeProvider>
<RoutingProvider {...routeInfo} routeBindings={routeBindings}>
<InternalAppContext.Provider
value={{ appIdentityProxy, routeObjects: routeInfo.routeObjects }}
>
{rootEl}
</InternalAppContext.Provider>
</RoutingProvider>
<InternalAppContext.Provider
value={{ appIdentityProxy, routeObjects: routeInfo.routeObjects }}
>
{rootEl}
</InternalAppContext.Provider>
</AppThemeProvider>
</ApiProvider>
);
@@ -414,6 +423,7 @@ function createApiHolder(
tree: AppTree,
configApi: ConfigApi,
appIdentityProxy: AppIdentityProxy,
routeResolutionApi: RouteResolutionApi,
icons?: { [key in string]: IconComponent },
): ApiHolder {
const factoryRegistry = new ApiFactoryRegistry();
@@ -465,6 +475,12 @@ function createApiHolder(
}),
});
factoryRegistry.register('static', {
api: routeResolutionApiRef,
deps: {},
factory: () => routeResolutionApi,
});
const componentsExtensions =
tree.root.edges.attachments
.get('components')