From 62c3628b27ee5c0bc8f51661eaf9f190cf0936f7 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 15 Aug 2025 14:35:46 +0200 Subject: [PATCH 1/2] refactor: rename convert legacy app helper Signed-off-by: Camila Belo --- .changeset/gold-hands-decide.md | 5 +++ .../building-apps/08-migrating.md | 32 +++++++++---------- packages/app-next/src/App.tsx | 4 +-- packages/core-compat-api/report.api.md | 22 ++++++------- .../src/convertLegacyApp.test.tsx | 8 ++--- .../core-compat-api/src/convertLegacyApp.ts | 6 ++-- packages/core-compat-api/src/index.ts | 4 +-- 7 files changed, 43 insertions(+), 38 deletions(-) create mode 100644 .changeset/gold-hands-decide.md diff --git a/.changeset/gold-hands-decide.md b/.changeset/gold-hands-decide.md new file mode 100644 index 0000000000..f65cc16ed7 --- /dev/null +++ b/.changeset/gold-hands-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': minor +--- + +**BREAKING**: Rename `createLegacyApp` to `createLegacyAppRoot` as it better refers to the purpose of the function. diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index e796f64728..86ac85cee8 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -32,13 +32,13 @@ Let's start by addressing the change to `app.createRoot(...)`, which no longer a Given that the app element tree is most of what builds up the app, it's likely also going to be the majority of the migration effort. In order to make the migration as smooth as possible we have provided a helper that lets you convert an existing app element tree into plugins that you can install in a new app. This in turn allows for a gradual migration of individual plugins, rather than needing to migrate the entire app structure at once. -The helper is called `convertLegacyApp` and is exported from the `@backstage/core-compat-api` package. We will also be using the `convertLegacyAppOptions` helper that lets us re-use the existing app options, also exported from the same package. You will need to add it as a dependency to your app package: +The helper is called `convertLegacyAppRoot` and is exported from the `@backstage/core-compat-api` package. We will also be using the `convertLegacyAppOptions` helper that lets us re-use the existing app options, also exported from the same package. You will need to add it as a dependency to your app package: ```bash yarn --cwd packages/app add @backstage/core-compat-api ``` -Once installed, import `convertLegacyApp`. If your app currently looks like this: +Once installed, import `convertLegacyAppRoot`. If your app currently looks like this: ```tsx title="in packages/app/src/App.tsx" const app = createApp({ @@ -60,11 +60,11 @@ Migrate it to the following: ```tsx title="in packages/app/src/App.tsx" import { - convertLegacyApp, + convertLegacyAppRoot, convertLegacyAppOptions, } from '@backstage/core-compat-api'; -const legacyFeatures = convertLegacyApp( +const legacyFeatures = convertLegacyAppRoot( <> @@ -85,7 +85,7 @@ const app = createApp({ export default app.createRoot(); ``` -We've taken all the elements that were previously passed to `app.createRoot(...)`, and instead passed them to `convertLegacyApp(...)`. We then pass the features returned by `convertLegacyApp` and forward them to the `features` option of the new `createApp`. +We've taken all the elements that were previously passed to `app.createRoot(...)`, and instead passed them to `convertLegacyAppRoot(...)`. We then pass the features returned by `convertLegacyAppRoot` and forward them to the `features` option of the new `createApp`. There is one more detail that we need to deal with before moving on. The `app.createRoot()` function now returns a React element rather than a component, so we need to update our app `index.tsx` as follows: @@ -429,13 +429,13 @@ const app = createApp({ Route bindings can still be done using this option, but you now also have the ability to bind routes using static configuration instead. See the section on [binding routes](../architecture/36-routes.md#binding-external-route-references) for more information. -Note that if you are binding routes from a legacy plugin that was converted using `convertLegacyApp`, you will need to use the `convertLegacyRouteRefs` and/or `convertLegacyRouteRef` to convert the routes to be compatible with the new system. +Note that if you are binding routes from a legacy plugin that was converted using `convertLegacyAppRoot`, you will need to use the `convertLegacyRouteRefs` and/or `convertLegacyRouteRef` to convert the routes to be compatible with the new system. For example, if both the `catalogPlugin` and `scaffolderPlugin` are legacy plugins, you can bind their routes like this: ```ts const app = createApp({ - features: convertLegacyApp(...), + features: convertLegacyAppRoot(...), bindRoutes({ bind }) { bind(convertLegacyRouteRefs(catalogPlugin.externalRoutes), { createComponent: convertLegacyRouteRef(scaffolderPlugin.routes.root), @@ -490,14 +490,14 @@ You would then add `catalogTranslations` as an `extension` like you did with `li ## Gradual Migration -After updating all `createApp` options as well as using `convertLegacyApp` to use your existing app structure, you should be able to start up the app and see that it still works. If that is not the case, make sure you read any error messages that you may see in the app as they can provide hints on what you need to fix. If you are still stuck, you can check if anyone else ran into the same issue in our [GitHub issues](https://github.com/backstage/backstage/issues), or ask for help in our [community Discord](https://discord.gg/backstage-687207715902193673). +After updating all `createApp` options as well as using `convertLegacyAppRoot` to use your existing app structure, you should be able to start up the app and see that it still works. If that is not the case, make sure you read any error messages that you may see in the app as they can provide hints on what you need to fix. If you are still stuck, you can check if anyone else ran into the same issue in our [GitHub issues](https://github.com/backstage/backstage/issues), or ask for help in our [community Discord](https://discord.gg/backstage-687207715902193673). Assuming your app is now working, let's continue by migrating the rest of the app element tree to use the new system. First off we'll want to trim away any top-level elements in the app so that only the `routes` are left. For example, continuing where we left off with the following elements: ```tsx title="in packages/app/src/App.tsx" -const legacyFeatures = convertLegacyApp( +const legacyFeatures = convertLegacyAppRoot( <> @@ -511,14 +511,14 @@ const legacyFeatures = convertLegacyApp( You can remove all surrounding elements and just keep the `routes`: ```tsx title="in packages/app/src/App.tsx" -const legacyFeatures = convertLegacyApp(routes); +const legacyFeatures = convertLegacyAppRoot(routes); ``` -This will remove many extension overrides that `convertLegacyApp` put in place, and switch over the shell of the app to the new system. This includes the root layout of the app along with the elements, router, and sidebar. The app will likely not look the same as before, and you'll need to refer to the [sidebar](#sidebar), [app root elements](#app-root-elements) and [app root wrappers](#app-root-wrappers) sections below for information on how to migrate those. +This will remove many extension overrides that `convertLegacyAppRoot` put in place, and switch over the shell of the app to the new system. This includes the root layout of the app along with the elements, router, and sidebar. The app will likely not look the same as before, and you'll need to refer to the [sidebar](#sidebar), [app root elements](#app-root-elements) and [app root wrappers](#app-root-wrappers) sections below for information on how to migrate those. Once that step is complete the work that remains is to migrate all of the [routes](#top-level-routes) and [entity pages](#entity-pages) in the app, including any plugins that do not yet support the new system. For information on how to migrate your own internal plugins, refer to the [plugin migration guide](../building-plugins/05-migrating.md). For external plugins you will need to check the migration status of each plugin and potentially contribute to the effort. -Once these migrations are complete you should be left with an empty `convertLegacyApp(...)` call that you can now remove, and your app should be fully migrated to the new system! 🎉 +Once these migrations are complete you should be left with an empty `convertLegacyAppRoot(...)` call that you can now remove, and your app should be fully migrated to the new system! 🎉 ### Top-level Routes @@ -573,15 +573,15 @@ Continue this process for each of your legacy routes until you have migrated all ### Entity Pages -The entity pages are typically defined in `packages/app/src/components/catalog` and rendered as a child of the `/catalog/:namespace/:kind/:name` route. The entity pages are typically quite large and bringing in content from quite a lot of different plugins. To help gradually migrate entity pages we provide the `entityPage` option in the `convertLegacyApp` helper. This option lets you pass in an entity page app element tree that will be converted to extensions that are added to the features returned from `convertLegacyApp`. +The entity pages are typically defined in `packages/app/src/components/catalog` and rendered as a child of the `/catalog/:namespace/:kind/:name` route. The entity pages are typically quite large and bringing in content from quite a lot of different plugins. To help gradually migrate entity pages we provide the `entityPage` option in the `convertLegacyAppRoot` helper. This option lets you pass in an entity page app element tree that will be converted to extensions that are added to the features returned from `convertLegacyAppRoot`. -To start the gradual migration of entity pages, add your `entityPages` to the `convertLegacyApp` call: +To start the gradual migration of entity pages, add your `entityPages` to the `convertLegacyAppRoot` call: ```tsx title="in packages/app/src/App.tsx" /* highlight-remove-next-line */ -const legacyFeatures = convertLegacyApp(routes); +const legacyFeatures = convertLegacyAppRoot(routes); /* highlight-add-next-line */ -const legacyFeatures = convertLegacyApp(routes, { entityPage }); +const legacyFeatures = convertLegacyAppRoot(routes, { entityPage }); ``` Next, you will need to fully migrate the catalog plugin itself. This is because only a single version of a plugin can be installed in the app at a time, so in order to start using the new version of the catalog plugin you need to remove all usage of the old one. This includes both the routes and entity pages. You will need to keep the structural helpers for the entity pages, such as `EntityLayout` and `EntitySwitch`, but remove any extensions like the `` and entity cards and content like `` and ``. diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index ac15c895df..6c567f3fe1 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -35,7 +35,7 @@ import { } from '@backstage/plugin-techdocs'; import appVisualizerPlugin from '@backstage/plugin-app-visualizer'; import { homePage } from './HomePage'; -import { convertLegacyApp } from '@backstage/core-compat-api'; +import { convertLegacyAppRoot } from '@backstage/core-compat-api'; import { FlatRoutes } from '@backstage/core-app-api'; import { Route } from 'react-router'; import { CatalogImportPage } from '@backstage/plugin-catalog-import'; @@ -116,7 +116,7 @@ const notFoundErrorPageModule = createFrontendModule({ extensions: [notFoundErrorPage], }); -const collectedLegacyPlugins = convertLegacyApp( +const collectedLegacyPlugins = convertLegacyAppRoot( } /> , diff --git a/packages/core-compat-api/report.api.md b/packages/core-compat-api/report.api.md index 3e74caaa61..11d48bd830 100644 --- a/packages/core-compat-api/report.api.md +++ b/packages/core-compat-api/report.api.md @@ -31,17 +31,6 @@ import { SubRouteRef as SubRouteRef_2 } from '@backstage/frontend-plugin-api'; // @public export function compatWrapper(element: ReactNode): JSX_3.Element; -// @public (undocumented) -export function convertLegacyApp( - rootElement: JSX_2.Element, - options?: ConvertLegacyAppOptions, -): (FrontendPlugin | FrontendModule)[]; - -// @public (undocumented) -export interface ConvertLegacyAppOptions { - entityPage?: JSX_2.Element; -} - // @public (undocumented) export function convertLegacyAppOptions(options?: { apis?: Iterable; @@ -54,6 +43,17 @@ export function convertLegacyAppOptions(options?: { featureFlags?: (FeatureFlag & Omit)[]; }): FrontendModule; +// @public (undocumented) +export function convertLegacyAppRoot( + rootElement: JSX_2.Element, + options?: ConvertLegacyAppRootOptions, +): (FrontendPlugin | FrontendModule)[]; + +// @public (undocumented) +export interface ConvertLegacyAppRootOptions { + entityPage?: JSX_2.Element; +} + // @public (undocumented) export function convertLegacyPageExtension( LegacyExtension: ComponentType<{}>, diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx index e80812d2da..7bbe13867c 100644 --- a/packages/core-compat-api/src/convertLegacyApp.test.tsx +++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx @@ -17,7 +17,7 @@ import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; import { ReactNode } from 'react'; import { Route } from 'react-router-dom'; -import { convertLegacyApp } from './convertLegacyApp'; +import { convertLegacyAppRoot } from './convertLegacyApp'; import { createApiFactory, createApiRef, @@ -62,7 +62,7 @@ const ExamplePage2 = examplePlugin2.provide( describe('convertLegacyApp', () => { it('should find and extract root and routes', () => { - const collected = convertLegacyApp( + const collected = convertLegacyAppRoot( <>
@@ -142,7 +142,7 @@ describe('convertLegacyApp', () => { }); it('should find and extract just routes', () => { - const collected = convertLegacyApp( + const collected = convertLegacyAppRoot( } /> } /> @@ -222,7 +222,7 @@ describe('convertLegacyApp', () => { ); - const converted = convertLegacyApp( + const converted = convertLegacyAppRoot( test
} /> , diff --git a/packages/core-compat-api/src/convertLegacyApp.ts b/packages/core-compat-api/src/convertLegacyApp.ts index 0c3a8c0060..06b282ae7a 100644 --- a/packages/core-compat-api/src/convertLegacyApp.ts +++ b/packages/core-compat-api/src/convertLegacyApp.ts @@ -62,7 +62,7 @@ function selectChildren( } /** @public */ -export interface ConvertLegacyAppOptions { +export interface ConvertLegacyAppRootOptions { /** * By providing an entity page element here it will be split up and converted * into individual extensions for the catalog plugin in the new frontend @@ -84,9 +84,9 @@ export interface ConvertLegacyAppOptions { } /** @public */ -export function convertLegacyApp( +export function convertLegacyAppRoot( rootElement: JSX.Element, - options: ConvertLegacyAppOptions = {}, + options: ConvertLegacyAppRootOptions = {}, ): (FrontendPlugin | FrontendModule)[] { if (getComponentData(rootElement, 'core.type') === 'FlatRoutes') { return collectLegacyRoutes(rootElement, options?.entityPage); diff --git a/packages/core-compat-api/src/index.ts b/packages/core-compat-api/src/index.ts index b1168184f9..0f5cae3023 100644 --- a/packages/core-compat-api/src/index.ts +++ b/packages/core-compat-api/src/index.ts @@ -18,8 +18,8 @@ export * from './compatWrapper'; export * from './apis'; export { - convertLegacyApp, - type ConvertLegacyAppOptions, + convertLegacyAppRoot, + type ConvertLegacyAppRootOptions, } from './convertLegacyApp'; export { convertLegacyAppOptions } from './convertLegacyAppOptions'; export { convertLegacyPlugin } from './convertLegacyPlugin'; From d22cfa9f1a61ac9f6469a57ff5e964bf5f82aacd Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 18 Aug 2025 10:20:58 +0200 Subject: [PATCH 2/2] refactor: apply review suggestions Signed-off-by: Camila Belo --- .changeset/gold-hands-decide.md | 2 +- packages/core-compat-api/report.api.md | 6 ++++++ packages/core-compat-api/src/convertLegacyApp.ts | 14 ++++++++++++++ packages/core-compat-api/src/index.ts | 2 ++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.changeset/gold-hands-decide.md b/.changeset/gold-hands-decide.md index f65cc16ed7..b22bf3efb5 100644 --- a/.changeset/gold-hands-decide.md +++ b/.changeset/gold-hands-decide.md @@ -2,4 +2,4 @@ '@backstage/core-compat-api': minor --- -**BREAKING**: Rename `createLegacyApp` to `createLegacyAppRoot` as it better refers to the purpose of the function. +Rename `createLegacyApp` to `createLegacyAppRoot` as it better refers to the purpose of the function. diff --git a/packages/core-compat-api/report.api.md b/packages/core-compat-api/report.api.md index 11d48bd830..ae202c96f7 100644 --- a/packages/core-compat-api/report.api.md +++ b/packages/core-compat-api/report.api.md @@ -31,6 +31,12 @@ import { SubRouteRef as SubRouteRef_2 } from '@backstage/frontend-plugin-api'; // @public export function compatWrapper(element: ReactNode): JSX_3.Element; +// @public @deprecated (undocumented) +export const convertLegacyApp: typeof convertLegacyAppRoot; + +// @public @deprecated (undocumented) +export type ConvertLegacyAppOptions = ConvertLegacyAppRootOptions; + // @public (undocumented) export function convertLegacyAppOptions(options?: { apis?: Iterable; diff --git a/packages/core-compat-api/src/convertLegacyApp.ts b/packages/core-compat-api/src/convertLegacyApp.ts index 06b282ae7a..f3981a774b 100644 --- a/packages/core-compat-api/src/convertLegacyApp.ts +++ b/packages/core-compat-api/src/convertLegacyApp.ts @@ -171,3 +171,17 @@ export function convertLegacyAppRoot( }), ]; } + +/** + * @public + * @deprecated + * Use `convertLegacyAppRoot` instead. + */ +export const convertLegacyApp = convertLegacyAppRoot; + +/** + * @public + * @deprecated + * Use `ConvertLegacyAppRootOptions` instead. + */ +export type ConvertLegacyAppOptions = ConvertLegacyAppRootOptions; diff --git a/packages/core-compat-api/src/index.ts b/packages/core-compat-api/src/index.ts index 0f5cae3023..6d8041ef44 100644 --- a/packages/core-compat-api/src/index.ts +++ b/packages/core-compat-api/src/index.ts @@ -18,6 +18,8 @@ export * from './compatWrapper'; export * from './apis'; export { + convertLegacyApp, + type ConvertLegacyAppOptions, convertLegacyAppRoot, type ConvertLegacyAppRootOptions, } from './convertLegacyApp';