From e0d9c9559a1abf70a871984d9d94805ba6153fc4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 7 Dec 2022 22:51:37 +0100 Subject: [PATCH 1/6] core-app-api: added AppRouter as replacement for app.getRouter() Signed-off-by: Patrik Oldsberg --- .changeset/fuzzy-rivers-search.md | 5 + packages/core-app-api/api-report.md | 3 + packages/core-app-api/src/app/AppManager.tsx | 148 +------------- packages/core-app-api/src/app/AppRouter.tsx | 189 ++++++++++++++++++ .../src/app/InternalAppContext.ts | 27 +++ packages/core-app-api/src/app/index.ts | 1 + packages/core-app-api/src/app/types.ts | 2 + 7 files changed, 233 insertions(+), 142 deletions(-) create mode 100644 .changeset/fuzzy-rivers-search.md create mode 100644 packages/core-app-api/src/app/AppRouter.tsx create mode 100644 packages/core-app-api/src/app/InternalAppContext.ts diff --git a/.changeset/fuzzy-rivers-search.md b/.changeset/fuzzy-rivers-search.md new file mode 100644 index 0000000000..06e2dda6d7 --- /dev/null +++ b/.changeset/fuzzy-rivers-search.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': minor +--- + +Added a new `AppRouter` component that replaces the same component currently created through `app.getRouter()`. diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 70312a54a5..137d6d72ca 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -227,6 +227,9 @@ export type AppRouteBinder = < >, ) => void; +// @public +export function AppRouter({ children }: { children?: ReactNode }): JSX.Element; + // @public export class AppThemeSelector implements AppThemeApi { constructor(themes: AppTheme[]); diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index f4fa2d8442..a5c981a54e 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -17,15 +17,10 @@ import { AppConfig, Config } from '@backstage/config'; import React, { ComponentType, - createContext, PropsWithChildren, - ReactElement, - useContext, useMemo, useRef, - useState, } from 'react'; -import { Route, Routes } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; import { ApiProvider, @@ -34,7 +29,6 @@ import { LocalStorageFeatureFlags, } from '../apis'; import { - useApi, AnyApiFactory, ApiHolder, IconComponent, @@ -44,7 +38,6 @@ import { AppThemeApi, ConfigApi, featureFlagsApiRef, - IdentityApi, identityApiRef, BackstagePlugin, } from '@backstage/core-plugin-api'; @@ -61,7 +54,6 @@ import { routingV2Collector, } from '../routing/collectors'; import { RoutingProvider } from '../routing/RoutingProvider'; -import { RouteTracker } from '../routing/RouteTracker'; import { validateRouteParameters, validateRouteBindings, @@ -74,14 +66,14 @@ import { AppContext, AppOptions, BackstageApp, - SignInPageProps, } from './types'; import { AppThemeProvider } from './AppThemeProvider'; import { defaultConfigLoader } from './defaultConfigLoader'; import { ApiRegistry } from '../apis/system/ApiRegistry'; import { resolveRouteBindings } from './resolveRouteBindings'; -import { BackstageRouteObject } from '../routing/types'; import { isReactRouterBeta } from './isReactRouterBeta'; +import { InternalAppContext } from './InternalAppContext'; +import { AppRouter, getBasePath } from './AppRouter'; type CompatiblePlugin = | BackstagePlugin @@ -89,39 +81,6 @@ type CompatiblePlugin = output(): Array<{ type: 'feature-flag'; name: string }>; }); -const InternalAppContext = createContext<{ - routeObjects: BackstageRouteObject[]; -}>({ routeObjects: [] }); - -/** - * Get the app base path from the configured app baseUrl. - * - * The returned path does not have a trailing slash. - */ -function getBasePath(configApi: Config) { - if (!isReactRouterBeta()) { - // When using rr v6 stable the base path is handled through the - // basename prop on the router component instead. - return ''; - } - - return readBasePath(configApi); -} - -/** - * Read the configured base path. - * - * The returned path does not have a trailing slash. - */ -function readBasePath(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; -} - function useConfigLoader( configLoader: AppConfigLoader | undefined, components: AppComponents, @@ -413,7 +372,10 @@ export class AppManager implements BackstageApp { basePath={getBasePath(loadedConfig.api)} > {children} @@ -427,104 +389,6 @@ export class AppManager implements BackstageApp { } getRouter(): ComponentType<{}> { - const { Router: RouterComponent, SignInPage: SignInPageComponent } = - this.components; - - // This wraps the sign-in page and waits for sign-in to be completed before rendering the app - const SignInPageWrapper = ({ - component: Component, - children, - }: { - component: ComponentType; - children: ReactElement; - }) => { - const [identityApi, setIdentityApi] = useState(); - const configApi = useApi(configApiRef); - const basePath = getBasePath(configApi); - - if (!identityApi) { - return ; - } - - this.appIdentityProxy.setTarget(identityApi, { - signOutTargetUrl: basePath || '/', - }); - return children; - }; - - const AppRouter = ({ children }: PropsWithChildren<{}>) => { - const configApi = useApi(configApiRef); - const basePath = readBasePath(configApi); - const mountPath = `${basePath}/*`; - const { routeObjects } = useContext(InternalAppContext); - - // If the app hasn't configured a sign-in page, we just continue as guest. - if (!SignInPageComponent) { - this.appIdentityProxy.setTarget( - { - getUserId: () => 'guest', - getIdToken: async () => undefined, - getProfile: () => ({ - email: 'guest@example.com', - displayName: 'Guest', - }), - getProfileInfo: async () => ({ - email: 'guest@example.com', - displayName: 'Guest', - }), - getBackstageIdentity: async () => ({ - type: 'user', - userEntityRef: 'user:default/guest', - ownershipEntityRefs: ['user:default/guest'], - }), - getCredentials: async () => ({}), - signOut: async () => {}, - }, - { signOutTargetUrl: basePath || '/' }, - ); - - if (isReactRouterBeta()) { - return ( - - - - {children}} /> - - - ); - } - - return ( - - - {children} - - ); - } - - if (isReactRouterBeta()) { - return ( - - - - - {children}} /> - - - - ); - } - - return ( - - - - <>{children} - - - ); - }; - return AppRouter; } diff --git a/packages/core-app-api/src/app/AppRouter.tsx b/packages/core-app-api/src/app/AppRouter.tsx new file mode 100644 index 0000000000..c95bdaa7c5 --- /dev/null +++ b/packages/core-app-api/src/app/AppRouter.tsx @@ -0,0 +1,189 @@ +/* + * Copyright 2022 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, { useContext, ReactNode, ComponentType, useState } from 'react'; +import { + ConfigApi, + configApiRef, + IdentityApi, + SignInPageProps, + useApi, + useApp, +} from '@backstage/core-plugin-api'; +import { InternalAppContext } from './InternalAppContext'; +import { isReactRouterBeta } from './isReactRouterBeta'; +import { RouteTracker } from '../routing/RouteTracker'; +import { Route, Routes } from 'react-router-dom'; +import { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy'; + +/** + * Get the app base path from the configured app baseUrl. + * + * The returned path does not have a trailing slash. + */ +export function getBasePath(configApi: ConfigApi) { + if (!isReactRouterBeta()) { + // When using rr v6 stable the base path is handled through the + // basename prop on the router component instead. + return ''; + } + + return readBasePath(configApi); +} + +/** + * Read the configured base path. + * + * The returned path does not have a trailing slash. + */ +function readBasePath(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, + appIdentityProxy, + children, +}: { + component: ComponentType; + appIdentityProxy: AppIdentityProxy; + children: ReactNode; +}) { + const [identityApi, setIdentityApi] = useState(); + const configApi = useApi(configApiRef); + const basePath = getBasePath(configApi); + + if (!identityApi) { + return ; + } + + appIdentityProxy.setTarget(identityApi, { + signOutTargetUrl: basePath || '/', + }); + return <>{children}; +} + +/** + * Props for the {@link AppRouter} component. + * @public + */ +export interface AppRouterProps { + children?: ReactNode; +} + +/** + * App router and sign-in page wrapper. + * + * @public + * @remarks + * + * The AppRouter provides the routing context and renders the sign-in page. + * Until the user has successfully signed in, this component will render + * the sign-in page. Once the user has signed-in, it will instead render + * the app, while providing routing and route tracking for the app. + * + */ +export function AppRouter({ children }: { children?: ReactNode }) { + const { Router: RouterComponent, SignInPage: SignInPageComponent } = + useApp().getComponents(); + + const configApi = useApi(configApiRef); + const basePath = readBasePath(configApi); + const mountPath = `${basePath}/*`; + const internalAppContext = useContext(InternalAppContext); + if (!internalAppContext) { + throw new Error('AppRouter must be rendered within the AppProvider'); + } + const { routeObjects, appIdentityProxy } = internalAppContext; + + // If the app hasn't configured a sign-in page, we just continue as guest. + if (!SignInPageComponent) { + appIdentityProxy.setTarget( + { + getUserId: () => 'guest', + getIdToken: async () => undefined, + getProfile: () => ({ + email: 'guest@example.com', + displayName: 'Guest', + }), + getProfileInfo: async () => ({ + email: 'guest@example.com', + displayName: 'Guest', + }), + getBackstageIdentity: async () => ({ + type: 'user', + userEntityRef: 'user:default/guest', + ownershipEntityRefs: ['user:default/guest'], + }), + getCredentials: async () => ({}), + signOut: async () => {}, + }, + { signOutTargetUrl: basePath || '/' }, + ); + + if (isReactRouterBeta()) { + return ( + + + + {children}} /> + + + ); + } + + return ( + + + {children} + + ); + } + + if (isReactRouterBeta()) { + return ( + + + + + {children}} /> + + + + ); + } + + return ( + + + + {children} + + + ); +} diff --git a/packages/core-app-api/src/app/InternalAppContext.ts b/packages/core-app-api/src/app/InternalAppContext.ts new file mode 100644 index 0000000000..81382acf62 --- /dev/null +++ b/packages/core-app-api/src/app/InternalAppContext.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2022 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 { createContext } from 'react'; +import { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy'; +import { BackstageRouteObject } from '../routing/types'; + +export const InternalAppContext = createContext< + | undefined + | { + routeObjects: BackstageRouteObject[]; + appIdentityProxy: AppIdentityProxy; + } +>(undefined); diff --git a/packages/core-app-api/src/app/index.ts b/packages/core-app-api/src/app/index.ts index 7843b36339..f6289a5830 100644 --- a/packages/core-app-api/src/app/index.ts +++ b/packages/core-app-api/src/app/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +export { AppRouter } from './AppRouter'; export { createSpecializedApp } from './createSpecializedApp'; export { defaultConfigLoader } from './defaultConfigLoader'; export * from './types'; diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 70022534a1..861766f8d3 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -307,6 +307,8 @@ export type BackstageApp = { /** * Router component that should wrap the App Routes create with getRoutes() * and any other components that should only be available while signed in. + * + * @deprecated Import and use the {@link AppRouter} component from `@backstage/core-app-api` instead */ getRouter(): ComponentType<{}>; }; From d9b3753f877b5dfb15f8533224dacbbbaa96b545 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 7 Dec 2022 22:56:07 +0100 Subject: [PATCH 2/6] app,create-app: update to use AppRouter Signed-off-by: Patrik Oldsberg --- .changeset/shy-birds-hammer.md | 17 +++++++++++++++++ packages/app/src/App.tsx | 3 +-- .../default-app/packages/app/src/App.tsx | 3 +-- 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 .changeset/shy-birds-hammer.md diff --git a/.changeset/shy-birds-hammer.md b/.changeset/shy-birds-hammer.md new file mode 100644 index 0000000000..132b137407 --- /dev/null +++ b/.changeset/shy-birds-hammer.md @@ -0,0 +1,17 @@ +--- +'@backstage/create-app': patch +--- + +Updated the app template to use the new `AppRouter` component instead of `app.getRouter()`. + +To apply this change to an existing app, make the following change to `packages/app/src/App.tsx`: + +```diff +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + + ... + + const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +``` diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 05ef61c11a..f21872a742 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -27,7 +27,7 @@ import { RELATION_PROVIDES_API, } from '@backstage/catalog-model'; import { createApp } from '@backstage/app-defaults'; -import { FlatRoutes } from '@backstage/core-app-api'; +import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; import { AlertDisplay, OAuthRequestDialog, @@ -146,7 +146,6 @@ const app = createApp({ }); const AppProvider = app.getProvider(); -const AppRouter = app.getRouter(); const routes = ( diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 46cb786399..368ed4d679 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -29,7 +29,7 @@ import { Root } from './components/Root'; import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; import { createApp } from '@backstage/app-defaults'; -import { FlatRoutes } from '@backstage/core-app-api'; +import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; import { RequirePermission } from '@backstage/plugin-permission-react'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; @@ -54,7 +54,6 @@ const app = createApp({ }); const AppProvider = app.getProvider(); -const AppRouter = app.getRouter(); const routes = ( From 06d65c51b25829565c2d0899c4dac92139fcba71 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 7 Dec 2022 23:31:34 +0100 Subject: [PATCH 3/6] core-app-api: add AppRouter tests Signed-off-by: Patrik Oldsberg --- .../core-app-api/src/app/AppRouter.test.tsx | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 packages/core-app-api/src/app/AppRouter.test.tsx diff --git a/packages/core-app-api/src/app/AppRouter.test.tsx b/packages/core-app-api/src/app/AppRouter.test.tsx new file mode 100644 index 0000000000..75f28a6bfe --- /dev/null +++ b/packages/core-app-api/src/app/AppRouter.test.tsx @@ -0,0 +1,120 @@ +/* + * Copyright 2022 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 from 'react'; +import { + AppComponents, + configApiRef, + IdentityApi, + identityApiRef, + SignInPageProps, + useApi, +} from '@backstage/core-plugin-api'; +import { InternalAppContext } from './InternalAppContext'; +import { MemoryRouter } from 'react-router-dom'; +import { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy'; +import { render, screen } from '@testing-library/react'; +import { AppRouter } from './AppRouter'; +import useAsync from 'react-use/lib/useAsync'; +import { AppContextProvider } from './AppContext'; +import { TestApiProvider } from '@backstage/test-utils'; +import { ConfigReader } from '@backstage/config'; + +function UserRefDisplay() { + const identityApi = useApi(identityApiRef); + const { value } = useAsync(() => identityApi.getBackstageIdentity()); + return
ref: {value?.userEntityRef}
; +} + +describe('AppRouter', () => { + const mockComponents = { + Router: MemoryRouter, + } as AppComponents; + + it('should fall back to guest if there is no sign-in page', async () => { + const appIdentityProxy = new AppIdentityProxy(); + + render( + + + mockComponents } as any} + > + + + + + + , + , + ); + + await expect( + screen.findByText('ref: user:default/guest'), + ).resolves.toBeInTheDocument(); + }); + + it('should use the result from the sign-in page', async () => { + const appIdentityProxy = new AppIdentityProxy(); + + const SignInPage = (props: SignInPageProps) => { + props.onSignInSuccess({ + getBackstageIdentity: async () => ({ + type: 'user', + userEntityRef: 'user:default/test', + ownershipEntityRefs: ['user:default/test'], + }), + } as IdentityApi); + return null; + }; + + render( + + + ({ ...mockComponents, SignInPage }), + } as any + } + > + + + + + + , + ); + + await expect( + screen.findByText('ref: user:default/test'), + ).resolves.toBeInTheDocument(); + }); +}); From 62e58de887170999728e028d700bb6ca0cf9e255 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 8 Dec 2022 00:04:15 +0100 Subject: [PATCH 4/6] core-app-api: added app.createRoot() Signed-off-by: Patrik Oldsberg --- .changeset/fuzzy-rivers-search.md | 47 +++++++++++++++++++- packages/core-app-api/api-report.md | 1 + packages/core-app-api/src/app/AppManager.tsx | 16 +++++++ packages/core-app-api/src/app/types.ts | 27 +++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/.changeset/fuzzy-rivers-search.md b/.changeset/fuzzy-rivers-search.md index 06e2dda6d7..4a82d1884c 100644 --- a/.changeset/fuzzy-rivers-search.md +++ b/.changeset/fuzzy-rivers-search.md @@ -2,4 +2,49 @@ '@backstage/core-app-api': minor --- -Added a new `AppRouter` component that replaces the same component currently created through `app.getRouter()`. +Added a new `AppRouter` component and `app.createRoot()` method that replaces `app.getRouter()` and `app.getProvider()`, which are now deprecated. The new `AppRouter` component is a drop-in replacement for the old router component, while the new `app.createRoot()` method is used instead of the old provider component. + +An old app setup might look like this: + +```tsx +const app = createApp(/* ... */); + +const AppProvider = app.getProvider(); +const AppRouter = app.getRouter(); + +const routes = ...; + +const App = () => ( + + + + + {routes} + + +); + +export default App; +``` + +With these new APIs, the setup now looks like this: + +```tsx +import { AppRouter } from '@backstage/core-app-api'; + +const app = createApp(/* ... */); + +const routes = ...; + +export default app.createRoot( + <> + + + + {routes} + + , +); +``` + +Note that `app.createRoot()` accepts a React element, rather than a component. diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 137d6d72ca..c07ea55b67 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -262,6 +262,7 @@ export type AuthApiCreateOptions = { export type BackstageApp = { getPlugins(): BackstagePlugin[]; getSystemIcon(key: string): IconComponent | undefined; + createRoot(element: JSX.Element): ComponentType<{}>; getProvider(): ComponentType<{}>; getRouter(): ComponentType<{}>; }; diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index a5c981a54e..5a752ad5df 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -248,7 +248,23 @@ export class AppManager implements BackstageApp { return this.components; } + createRoot(element: JSX.Element): ComponentType<{}> { + const AppProvider = this.getProvider(); + const AppRoot = () => { + return {element}; + }; + return AppRoot; + } + + #getProviderCalled = false; getProvider(): ComponentType<{}> { + if (this.#getProviderCalled) { + throw new Error( + 'app.getProvider() or app.createRoot() has already been called, and can only be called once', + ); + } + this.#getProviderCalled = true; + const appContext = new AppContextImpl(this); // We only validate routes once diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 861766f8d3..82c05ce268 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -298,9 +298,36 @@ export type BackstageApp = { */ getSystemIcon(key: string): IconComponent | undefined; + /** + * Creates the root component that renders the entire app. + * + * @remarks + * + * This method must only be called once, and you have to provide it the entire + * app element tree. The element tree will be analyzed to discover plugins, + * routes, and other app features. The returned component will render all + * of the app elements wrapped within the app context provider. + * + * @example + * ```tsx + * export default app.createRoot( + * <> + * + * + * + * {routes} + * + * , + * ); + * ``` + */ + createRoot(element: JSX.Element): ComponentType<{}>; + /** * Provider component that should wrap the Router created with getRouter() * and any other components that need to be within the app context. + * + * @deprecated Use {@link BackstageApp.createRoot} instead. */ getProvider(): ComponentType<{}>; From 0e91c1196d1751540f65a0e744f5d3c8b86db235 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 8 Dec 2022 11:43:23 +0100 Subject: [PATCH 5/6] app,create-app: update to use app.createRoot() Signed-off-by: Patrik Oldsberg --- .changeset/shy-birds-hammer.md | 35 +++++++++++++++++-- packages/app/src/App.tsx | 10 ++---- .../default-app/packages/app/src/App.tsx | 10 ++---- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/.changeset/shy-birds-hammer.md b/.changeset/shy-birds-hammer.md index 132b137407..116652f740 100644 --- a/.changeset/shy-birds-hammer.md +++ b/.changeset/shy-birds-hammer.md @@ -2,7 +2,7 @@ '@backstage/create-app': patch --- -Updated the app template to use the new `AppRouter` component instead of `app.getRouter()`. +Updated the app template to use the new `AppRouter` component instead of `app.getRouter()`, as well as `app.createRoot()` instead of `app.getProvider()`. To apply this change to an existing app, make the following change to `packages/app/src/App.tsx`: @@ -12,6 +12,37 @@ To apply this change to an existing app, make the following change to `packages/ ... - const AppProvider = app.getProvider(); +-const AppProvider = app.getProvider(); -const AppRouter = app.getRouter(); + + ... + +-const App = () => ( ++export default app.createRoot( +- ++ <> + + + + {routes} + +- ++ , + ); ``` + +The final export step should end up looking something like this: + +```tsx +export default app.createRoot( + <> + + + + {routes} + + , +); +``` + +Note that `app.createRoot()` accepts a React element, rather than a component. diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index f21872a742..f4a50a0902 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -145,8 +145,6 @@ const app = createApp({ }, }); -const AppProvider = app.getProvider(); - const routes = ( } /> @@ -277,14 +275,12 @@ const routes = ( ); -const App = () => ( - +export default app.createRoot( + <> {routes} - + , ); - -export default App; diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 368ed4d679..95fc94703e 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -53,8 +53,6 @@ const app = createApp({ }, }); -const AppProvider = app.getProvider(); - const routes = ( } /> @@ -96,14 +94,12 @@ const routes = ( ); -const App = () => ( - +export default app.createRoot( + <> {routes} - + , ); - -export default App; From 2e7a08394d33ff877a84bf1ed3f4b3ea6ed70e8e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 8 Dec 2022 17:18:25 +0100 Subject: [PATCH 6/6] core-app-api: actually use AppRouterProps Signed-off-by: Patrik Oldsberg --- packages/core-app-api/api-report.md | 8 +++++++- packages/core-app-api/src/app/AppRouter.tsx | 11 +++++------ packages/core-app-api/src/app/index.ts | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index c07ea55b67..4d867f0b37 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -228,7 +228,13 @@ export type AppRouteBinder = < ) => void; // @public -export function AppRouter({ children }: { children?: ReactNode }): JSX.Element; +export function AppRouter(props: AppRouterProps): JSX.Element; + +// @public +export interface AppRouterProps { + // (undocumented) + children?: ReactNode; +} // @public export class AppThemeSelector implements AppThemeApi { diff --git a/packages/core-app-api/src/app/AppRouter.tsx b/packages/core-app-api/src/app/AppRouter.tsx index c95bdaa7c5..b799983a3b 100644 --- a/packages/core-app-api/src/app/AppRouter.tsx +++ b/packages/core-app-api/src/app/AppRouter.tsx @@ -100,9 +100,8 @@ export interface AppRouterProps { * Until the user has successfully signed in, this component will render * the sign-in page. Once the user has signed-in, it will instead render * the app, while providing routing and route tracking for the app. - * */ -export function AppRouter({ children }: { children?: ReactNode }) { +export function AppRouter(props: AppRouterProps) { const { Router: RouterComponent, SignInPage: SignInPageComponent } = useApp().getComponents(); @@ -145,7 +144,7 @@ export function AppRouter({ children }: { children?: ReactNode }) { - {children}} /> + {props.children}} /> ); @@ -154,7 +153,7 @@ export function AppRouter({ children }: { children?: ReactNode }) { return ( - {children} + {props.children} ); } @@ -168,7 +167,7 @@ export function AppRouter({ children }: { children?: ReactNode }) { appIdentityProxy={appIdentityProxy} > - {children}} /> + {props.children}} /> @@ -182,7 +181,7 @@ export function AppRouter({ children }: { children?: ReactNode }) { component={SignInPageComponent} appIdentityProxy={appIdentityProxy} > - {children} + {props.children} ); diff --git a/packages/core-app-api/src/app/index.ts b/packages/core-app-api/src/app/index.ts index f6289a5830..156c59d0c7 100644 --- a/packages/core-app-api/src/app/index.ts +++ b/packages/core-app-api/src/app/index.ts @@ -15,6 +15,7 @@ */ export { AppRouter } from './AppRouter'; +export type { AppRouterProps } from './AppRouter'; export { createSpecializedApp } from './createSpecializedApp'; export { defaultConfigLoader } from './defaultConfigLoader'; export * from './types';