diff --git a/.changeset/hip-ladybugs-behave.md b/.changeset/hip-ladybugs-behave.md new file mode 100644 index 0000000000..44cf690505 --- /dev/null +++ b/.changeset/hip-ladybugs-behave.md @@ -0,0 +1,12 @@ +--- +'@backstage/frontend-app-api': minor +--- + +**BREAKING**: The returned object from `createSpecializedApp` no longer contains a `createRoot()` method, and it instead now contains `apis` and `tree`. + +You can replace existing usage of `app.createRoot()` with the following: + +```ts +const root = tree.root.instance?.getData(coreExtensionData.reactEleme +nt); +``` diff --git a/.changeset/many-insects-add.md b/.changeset/many-insects-add.md new file mode 100644 index 0000000000..b52ef69818 --- /dev/null +++ b/.changeset/many-insects-add.md @@ -0,0 +1,6 @@ +--- +'@backstage/frontend-defaults': patch +'@backstage/frontend-test-utils': patch +--- + +Internal refactor to match updated `createSpecializedApp`. diff --git a/packages/frontend-app-api/report.api.md b/packages/frontend-app-api/report.api.md index 53ecb56585..74cf2bfb8a 100644 --- a/packages/frontend-app-api/report.api.md +++ b/packages/frontend-app-api/report.api.md @@ -10,7 +10,6 @@ import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FrontendModule } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; -import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SubRouteRef } from '@backstage/frontend-plugin-api'; @@ -36,7 +35,6 @@ export function createSpecializedApp(options?: { extensionFactoryMiddleware?: ExtensionFactoryMiddleware; }): { apis: ApiHolder; - createRoot(): JSX_2.Element; tree: AppTree; }; diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index e269961d52..7fed91add8 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -56,7 +56,7 @@ describe('createSpecializedApp', () => { ], }); - render(app.createRoot()); + render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); expect(screen.getByText('Test')).toBeInTheDocument(); }); @@ -91,7 +91,7 @@ describe('createSpecializedApp', () => { ], }); - render(app.createRoot()); + render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); expect(screen.getByText('Test 2')).toBeInTheDocument(); }); @@ -117,7 +117,7 @@ describe('createSpecializedApp', () => { ], }); - render(app.createRoot()); + render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); expect(screen.getByText('Test foo')).toBeInTheDocument(); }); @@ -163,7 +163,7 @@ describe('createSpecializedApp', () => { ], }); - render(app.createRoot()); + render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); expect(screen.getByText('flags:test=a,test=b')).toBeInTheDocument(); @@ -307,7 +307,7 @@ describe('createSpecializedApp', () => { ], }); - render(app.createRoot()); + render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); expect(mockAnalyticsApi).toHaveBeenCalled(); }); @@ -339,7 +339,7 @@ describe('createSpecializedApp', () => { ], }); - render(app.createRoot()); + render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); expect(screen.getByText('providedApis:config')).toBeInTheDocument(); @@ -518,7 +518,7 @@ describe('createSpecializedApp', () => { bindRoutes({ bind }) { bind(pluginA.externalRoutes, { ext: pluginB.routes.root }); }, - }).createRoot(), + }).tree.root.instance!.getData(coreExtensionData.reactElement), ); expect(screen.getByText('link: /test')).toBeInTheDocument(); diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index 785f12045e..3379edcc08 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -14,14 +14,12 @@ * limitations under the License. */ -import React, { JSX } from 'react'; import { ConfigReader } from '@backstage/config'; import { ApiBlueprint, AppTree, AppTreeApi, appTreeApiRef, - coreExtensionData, RouteRef, ExternalRouteRef, SubRouteRef, @@ -191,6 +189,7 @@ class RouteResolutionApiProxy implements RouteResolutionApi { return this.#routeObjects; } } + /** * Creates an empty app without any default features. This is a low-level API is * intended for use in tests or specialized setups. Typically wou want to use @@ -204,7 +203,7 @@ export function createSpecializedApp(options?: { bindRoutes?(context: { bind: CreateAppRouteBinder }): void; apis?: ApiHolder; extensionFactoryMiddleware?: ExtensionFactoryMiddleware; -}): { apis: ApiHolder; createRoot(): JSX.Element; tree: AppTree } { +}): { apis: ApiHolder; tree: AppTree } { const config = options?.config ?? new ConfigReader({}, 'empty-config'); const features = deduplicateFeatures(options?.features ?? []); @@ -233,7 +232,7 @@ export function createSpecializedApp(options?: { ); const appIdentityProxy = new AppIdentityProxy(); - const apiHolder = + const apis = options?.apis ?? createApiHolder({ factories, @@ -245,7 +244,7 @@ export function createSpecializedApp(options?: { ], }); - const featureFlagApi = apiHolder.get(featureFlagsApiRef); + const featureFlagApi = apis.get(featureFlagsApiRef); if (featureFlagApi) { for (const feature of features) { if (OpaqueFrontendPlugin.isType(feature)) { @@ -268,28 +267,14 @@ export function createSpecializedApp(options?: { } // Now instantiate the entire tree, which will skip anything that's already been instantiated - instantiateAppNodeTree( - tree.root, - apiHolder, - options?.extensionFactoryMiddleware, - ); + instantiateAppNodeTree(tree.root, apis, options?.extensionFactoryMiddleware); const routeInfo = extractRouteInfoFromAppNode(tree.root); routeResolutionApi.initialize(routeInfo); appTreeApi.initialize(routeInfo); - const rootEl = tree.root.instance!.getData(coreExtensionData.reactElement); - - const AppComponent = () => rootEl; - - return { - apis: apiHolder, - tree, - createRoot() { - return ; - }, - }; + return { apis, tree }; } function createApiFactories(options: { tree: AppTree }): AnyApiFactory[] { diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx index 7ff59ca262..a8b40a5f63 100644 --- a/packages/frontend-defaults/src/createApp.tsx +++ b/packages/frontend-defaults/src/createApp.tsx @@ -15,7 +15,7 @@ */ import React, { JSX, ReactNode } from 'react'; -import { ConfigApi } from '@backstage/frontend-plugin-api'; +import { ConfigApi, coreExtensionData } from '@backstage/frontend-plugin-api'; import { stringifyError } from '@backstage/errors'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { defaultConfigLoaderSync } from '../../core-app-api/src/app/defaultConfigLoader'; @@ -114,9 +114,13 @@ export function createApp(options?: CreateAppOptions): { features: [appPlugin, ...discoveredFeatures, ...providedFeatures], bindRoutes: options?.bindRoutes, extensionFactoryMiddleware: options?.extensionFactoryMiddleware, - }).createRoot(); + }); - return { default: () => app }; + const rootEl = app.tree.root.instance!.getData( + coreExtensionData.reactElement, + ); + + return { default: () => rootEl }; } return { diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index 7f03921077..cd9f4439b3 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -201,5 +201,7 @@ export function renderInTestApp( ]), }); - return render(app.createRoot()); + return render( + app.tree.root.instance!.getData(coreExtensionData.reactElement), + ); }