diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 9ab19b5588..11635368fb 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -50,14 +50,13 @@ import { } from '../extensions/traversal'; import { IconComponent, IconComponentMap, IconKey } from '../icons'; import { BackstagePlugin } from '../plugin'; -import { RouteRef } from '../routing'; +import { RouteRef, ExternalRouteRef } from '../routing'; import { routeObjectCollector, routeParentCollector, routePathCollector, } from '../routing/collectors'; import { RoutingProvider, validateRoutes } from '../routing/hooks'; -import { ExternalRouteRef } from '../routing/RouteRef'; import { AppContextProvider } from './AppContext'; import { AppIdentity } from './AppIdentity'; import { AppThemeProvider } from './AppThemeProvider'; diff --git a/packages/core-api/src/plugin/types.ts b/packages/core-api/src/plugin/types.ts index 711246f1f4..c3b64d8cb6 100644 --- a/packages/core-api/src/plugin/types.ts +++ b/packages/core-api/src/plugin/types.ts @@ -15,9 +15,8 @@ */ import { ComponentType } from 'react'; -import { RouteRef } from '../routing'; +import { RouteRef, ExternalRouteRef } from '../routing'; import { AnyApiFactory } from '../apis/system'; -import { ExternalRouteRef } from '../routing/RouteRef'; export type RouteOptions = { // Whether the route path must match exactly, defaults to true. diff --git a/packages/core-api/src/routing/RouteRef.ts b/packages/core-api/src/routing/RouteRef.ts index 67ff9bc1fb..879a9c99f8 100644 --- a/packages/core-api/src/routing/RouteRef.ts +++ b/packages/core-api/src/routing/RouteRef.ts @@ -14,19 +14,31 @@ * limitations under the License. */ -import { RouteRef } from './types'; +import { RouteRef, ExternalRouteRef, routeRefType } from './types'; import { IconComponent } from '../icons'; +// TODO(Rugvip): Remove this once we get rid of the deprecated fields, it's not exported export type RouteRefConfig = { params?: Array; - /** @deprecated Route refs no longer decide their own path */ path?: string; icon?: IconComponent; title: string; }; -export class AbsoluteRouteRef { - constructor(private readonly config: RouteRefConfig) {} +class RouteRefBaseBase { + constructor(type: string, id: string) { + this.toString = () => `routeRef{type=${type},id=${id}}`; + } +} + +export class RouteRefImpl< + Params extends { [param in string]: string } +> extends RouteRefBaseBase { + readonly [routeRefType] = 'absolute'; + + constructor(private readonly config: RouteRefConfig) { + super('absolute', config.title); + } get icon() { return this.config.icon; @@ -40,10 +52,6 @@ export class AbsoluteRouteRef { get title() { return this.config.title; } - - toString() { - return `routeRef{title=${this.title}}`; - } } export function createRouteRef< @@ -58,22 +66,27 @@ export function createRouteRef< params?: ParamKey[]; /** @deprecated Route refs no longer decide their own path */ path?: string; + /** @deprecated Route refs no longer decide their own icon */ icon?: IconComponent; + /** @deprecated Route refs no longer decide their own title */ title: string; }): RouteRef { - return new AbsoluteRouteRef(config); + return new RouteRefImpl(config); } -export class ExternalRouteRef { - readonly optional: boolean; +export class ExternalRouteRefImpl< + Optional extends boolean +> extends RouteRefBaseBase { + readonly [routeRefType] = 'external'; - private constructor({ id, optional }: ExternalRouteRefOptions) { - this.toString = () => `externalRouteRef{${id}}`; - this.optional = Boolean(optional); + constructor(id: string, readonly optional: Optional) { + super('external', id); } } -export type ExternalRouteRefOptions = { +export function createExternalRouteRef< + Optional extends boolean = false +>(options: { /** * An identifier for this route, used to identify it in error messages */ @@ -86,14 +99,9 @@ export type ExternalRouteRefOptions = { * if they aren't, `useRouteRef` will return `undefined`. */ optional?: Optional; -}; - -export function createExternalRouteRef( - options: ExternalRouteRefOptions, -): ExternalRouteRef { - return new ((ExternalRouteRef as unknown) as { - new (options: ExternalRouteRefOptions): ExternalRouteRef< - Optional - >; - })(options); +}): ExternalRouteRef { + return new ExternalRouteRefImpl( + options.id, + Boolean(options.optional) as Optional, + ); } diff --git a/packages/core-api/src/routing/hooks.test.tsx b/packages/core-api/src/routing/hooks.test.tsx index 94cd3a704d..cfb7a5c68a 100644 --- a/packages/core-api/src/routing/hooks.test.tsx +++ b/packages/core-api/src/routing/hooks.test.tsx @@ -38,10 +38,9 @@ import { import { createRouteRef, createExternalRouteRef, - ExternalRouteRef, RouteRefConfig, } from './RouteRef'; -import { AnyRouteRef, RouteRef } from './types'; +import { AnyRouteRef, RouteRef, ExternalRouteRef } from './types'; const mockConfig = (extra?: Partial>) => ({ path: '/unused', diff --git a/packages/core-api/src/routing/hooks.tsx b/packages/core-api/src/routing/hooks.tsx index 54490fb4f2..d9bbef438e 100644 --- a/packages/core-api/src/routing/hooks.tsx +++ b/packages/core-api/src/routing/hooks.tsx @@ -15,9 +15,13 @@ */ import React, { createContext, ReactNode, useContext, useMemo } from 'react'; -import { AnyRouteRef, BackstageRouteObject, RouteRef } from './types'; +import { + AnyRouteRef, + BackstageRouteObject, + RouteRef, + ExternalRouteRef, +} from './types'; import { generatePath, matchRoutes, useLocation } from 'react-router-dom'; -import { ExternalRouteRef } from './RouteRef'; // The extra TS magic here is to require a single params argument if the RouteRef // had at least one param defined, but require 0 arguments if there are no params defined. diff --git a/packages/core-api/src/routing/index.ts b/packages/core-api/src/routing/index.ts index 100158c3c5..2a0685f583 100644 --- a/packages/core-api/src/routing/index.ts +++ b/packages/core-api/src/routing/index.ts @@ -19,12 +19,9 @@ export type { AbsoluteRouteRef, ConcreteRoute, MutableRouteRef, + ExternalRouteRef, } from './types'; export { FlatRoutes } from './FlatRoutes'; -export { - createRouteRef, - createExternalRouteRef, - ExternalRouteRef, -} from './RouteRef'; +export { createRouteRef, createExternalRouteRef } from './RouteRef'; export type { RouteRefConfig } from './RouteRef'; export { useRouteRef } from './hooks'; diff --git a/packages/core-api/src/routing/types.ts b/packages/core-api/src/routing/types.ts index b21fa38052..1b2cea3465 100644 --- a/packages/core-api/src/routing/types.ts +++ b/packages/core-api/src/routing/types.ts @@ -15,35 +15,41 @@ */ import { IconComponent } from '../icons'; -import { ExternalRouteRef } from './RouteRef'; +import { getGlobalSingleton } from '../lib/globalObject'; -// @ts-ignore, we're just embedding the Params type for usage in other places -export type RouteRef = { - // TODO(Rugvip): Remove path, look up via registry instead +export const routeRefType: unique symbol = getGlobalSingleton( + 'route-ref-type', + () => Symbol('route-ref-type'), +); + +export type RouteRef = { + [routeRefType]: 'absolute'; + + params?: keyof Params; + + // TODO(Rugvip): Remove all of these once plugins don't rely on the path /** @deprecated paths are no longer accessed directly from RouteRefs, use useRouteRef instead */ path: string; + /** @deprecated icons are no longer accessed via RouteRefs */ icon?: IconComponent; - title: string; + /** @deprecated titles are no longer accessed via RouteRefs */ + title?: string; +}; + +export type ExternalRouteRef = { + [routeRefType]: 'external'; + + optional?: Optional; }; export type AnyRouteRef = RouteRef | ExternalRouteRef; -/** - * This type should not be used - * @deprecated - */ +// TODO(Rugvip): None of these should be found in the wild anymore, remove in next minor release +/** @deprecated */ export type ConcreteRoute = {}; - -/** - * This type should not be used, use RouteRef instead - * @deprecated - */ +/** @deprecated */ export type AbsoluteRouteRef = RouteRef<{}>; - -/** - * This type should not be used, use RouteRef instead - * @deprecated - */ +/** @deprecated */ export type MutableRouteRef = RouteRef<{}>; // A duplicate of the react-router RouteObject, but with routeRef added