frontend-app-api: split out and export createSpecializedApp

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-11-15 13:03:59 +01:00
parent 37f4f98ba2
commit b8cb7804c8
4 changed files with 84 additions and 40 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': patch
---
Added `createSpecializedApp`, which is a synchronous version of `createApp` where config and features already need to be loaded.
+9
View File
@@ -41,6 +41,15 @@ export function createApp(options?: {
// @public (undocumented)
export function createExtensionTree(options: { config: Config }): ExtensionTree;
// @public
export function createSpecializedApp(options?: {
features?: (BackstagePlugin | ExtensionOverrides)[];
config?: ConfigApi;
bindRoutes?(context: { bind: AppRouteBinder }): void;
}): {
createRoot(): JSX_2.Element;
};
// @public (undocumented)
export interface ExtensionTree {
// (undocumented)
@@ -244,49 +244,18 @@ export function createApp(options?: {
const discoveredFeatures = getAvailableFeatures(config);
const loadedFeatures = (await options?.featureLoader?.({ config })) ?? [];
const allFeatures = deduplicateFeatures([
...discoveredFeatures,
...loadedFeatures,
...(options?.features ?? []),
]);
const tree = createAppTree({
features: allFeatures,
builtinExtensions,
const app = createSpecializedApp({
config,
});
features: [
...discoveredFeatures,
...loadedFeatures,
...(options?.features ?? []),
],
bindRoutes: options?.bindRoutes,
}).createRoot();
const appContext = createLegacyAppContext(
allFeatures.filter(
(f): f is BackstagePlugin => f.$$type === '@backstage/BackstagePlugin',
),
);
const routeIds = collectRouteIds(allFeatures);
const App = () => (
<ApiProvider apis={createApiHolder(tree, config)}>
<AppContextProvider appContext={appContext}>
<AppThemeProvider>
<RoutingProvider
{...extractRouteInfoFromAppNode(tree.root)}
routeBindings={resolveRouteBindings(
options?.bindRoutes,
config,
routeIds,
)}
>
{/* TODO: set base path using the logic from AppRouter */}
<BrowserRouter>
{tree.root.instance!.getData(coreExtensionData.reactElement)}
</BrowserRouter>
</RoutingProvider>
</AppThemeProvider>
</AppContextProvider>
</ApiProvider>
);
return { default: App };
return { default: () => app };
}
return {
@@ -301,6 +270,66 @@ export function createApp(options?: {
};
}
/**
* Synchronous version of {@link createApp}, expecting all features and
* config to have been loaded already.
* @public
*/
export function createSpecializedApp(options?: {
features?: (BackstagePlugin | ExtensionOverrides)[];
config?: ConfigApi;
bindRoutes?(context: { bind: AppRouteBinder }): void;
}): { createRoot(): JSX.Element } {
const {
features: duplicatedFeatures = [],
config = new ConfigReader({}, 'empty-config'),
} = options ?? {};
const features = deduplicateFeatures(duplicatedFeatures);
const tree = createAppTree({
features,
builtinExtensions,
config,
});
const appContext = createLegacyAppContext(
features.filter(
(f): f is BackstagePlugin => f.$$type === '@backstage/BackstagePlugin',
),
);
const routeIds = collectRouteIds(features);
const App = () => (
<ApiProvider apis={createApiHolder(tree, config)}>
<AppContextProvider appContext={appContext}>
<AppThemeProvider>
<RoutingProvider
{...extractRouteInfoFromAppNode(tree.root)}
routeBindings={resolveRouteBindings(
options?.bindRoutes,
config,
routeIds,
)}
>
{/* TODO: set base path using the logic from AppRouter */}
<BrowserRouter>
{tree.root.instance!.getData(coreExtensionData.reactElement)}
</BrowserRouter>
</RoutingProvider>
</AppThemeProvider>
</AppContextProvider>
</ApiProvider>
);
return {
createRoot() {
return <App />;
},
};
}
// Make sure that we only convert each new plugin instance to its legacy equivalent once
const legacyPluginStore = getOrCreateGlobalSingleton(
'legacy-plugin-compatibility-store',
@@ -16,6 +16,7 @@
export {
createApp,
createSpecializedApp,
createExtensionTree,
type ExtensionTreeNode,
type ExtensionTree,