frontend-app-api: remove .createRoot

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2025-03-09 14:31:31 +01:00
parent d5afcd49fa
commit abcdf444e5
7 changed files with 41 additions and 34 deletions
+12
View File
@@ -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);
```
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/frontend-defaults': patch
'@backstage/frontend-test-utils': patch
---
Internal refactor to match updated `createSpecializedApp`.
-2
View File
@@ -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;
};
@@ -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();
@@ -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 <AppComponent />;
},
};
return { apis, tree };
}
function createApiFactories(options: { tree: AppTree }): AnyApiFactory[] {
+7 -3
View File
@@ -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 {
@@ -201,5 +201,7 @@ export function renderInTestApp(
]),
});
return render(app.createRoot());
return render(
app.tree.root.instance!.getData(coreExtensionData.reactElement),
);
}