From 1594749d8eddd8f19641a1bf31ca8a7386a4dc13 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 16 Mar 2026 15:06:05 +0100 Subject: [PATCH] Simplify createDevApp options Accept createApp options at the top level in createDevApp and update the tests, docs, API report, and changeset to match the new shape. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- .changeset/add-frontend-dev-utils.md | 2 +- .../building-plugins/01-index.md | 2 +- packages/frontend-dev-utils/report.api.md | 4 +- .../src/createDevApp.test.tsx | 15 ++---- .../frontend-dev-utils/src/createDevApp.tsx | 46 +++++++++++-------- 5 files changed, 35 insertions(+), 34 deletions(-) diff --git a/.changeset/add-frontend-dev-utils.md b/.changeset/add-frontend-dev-utils.md index 310ab204a0..902dc8019a 100644 --- a/.changeset/add-frontend-dev-utils.md +++ b/.changeset/add-frontend-dev-utils.md @@ -2,4 +2,4 @@ '@backstage/frontend-dev-utils': minor --- -Added `@backstage/frontend-dev-utils`, a new package that provides a minimal helper for wiring up a development app for frontend plugins using the new frontend system. It exports a `createDevApp` function that handles creating and rendering a development app from a `dev/` entry point. The dev app automatically bypasses the sign-in page and loads the `@backstage/ui` CSS. +Added `@backstage/frontend-dev-utils`, a new package that provides a minimal helper for wiring up a development app for frontend plugins using the new frontend system. It exports a `createDevApp` function that handles creating and rendering a development app from a `dev/` entry point. The dev app automatically bypasses the sign-in page and loads the `@backstage/ui` CSS. The options interface extends all `createApp` options from `@backstage/frontend-defaults` (except `features`), such as `bindRoutes` and `advanced`. diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 12558d5413..b8e1346bee 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -121,7 +121,7 @@ import myPlugin from '../src'; createDevApp({ features: [myPlugin] }); ``` -This will create and render a Backstage app with only your plugin installed. If you need to include additional features that your plugin depends on, pass them along in the `features` array. You can also forward additional options to `createApp` from `@backstage/frontend-defaults` using the `createAppOptions` option. +This will create and render a Backstage app with only your plugin installed. If you need to include additional features that your plugin depends on, pass them along in the `features` array. The options also accept all other `createApp` options from `@backstage/frontend-defaults`, such as `bindRoutes` and `advanced`. The dev setup is started by running `yarn start` in the plugin directory, which uses the `backstage-cli package start` command. It sets up a local development server with hot reloading, just like a full app. diff --git a/packages/frontend-dev-utils/report.api.md b/packages/frontend-dev-utils/report.api.md index 2dd3b38467..42261680ab 100644 --- a/packages/frontend-dev-utils/report.api.md +++ b/packages/frontend-dev-utils/report.api.md @@ -11,8 +11,8 @@ import { FrontendFeatureLoader } from '@backstage/frontend-plugin-api'; export function createDevApp(options: CreateDevAppOptions): void; // @public -export interface CreateDevAppOptions { - createAppOptions?: Omit; +export interface CreateDevAppOptions + extends Omit { features: (FrontendFeature | FrontendFeatureLoader)[]; } ``` diff --git a/packages/frontend-dev-utils/src/createDevApp.test.tsx b/packages/frontend-dev-utils/src/createDevApp.test.tsx index 7f94c5543e..ea377e2e2f 100644 --- a/packages/frontend-dev-utils/src/createDevApp.test.tsx +++ b/packages/frontend-dev-utils/src/createDevApp.test.tsx @@ -18,7 +18,7 @@ import { PageBlueprint, createFrontendPlugin, } from '@backstage/frontend-plugin-api'; -import { within, waitFor } from '@testing-library/react'; +import { within } from '@testing-library/react'; import { mockApis } from '@backstage/test-utils'; import { createDevApp } from './createDevApp'; @@ -46,19 +46,12 @@ describe('createDevApp', () => { createDevApp({ features: [testPlugin], - createAppOptions: { - advanced: { - configLoader: async () => ({ config: mockApis.config() }), - }, + advanced: { + configLoader: async () => ({ config: mockApis.config() }), }, }); const body = within(document.body); - await waitFor( - () => { - expect(body.getByText('Test Plugin Page')).toBeDefined(); - }, - { timeout: 10000 }, - ); + await body.findByText('Test Plugin Page', {}, { timeout: 10000 }); }, 15000); }); diff --git a/packages/frontend-dev-utils/src/createDevApp.tsx b/packages/frontend-dev-utils/src/createDevApp.tsx index 54eea25d06..294b360040 100644 --- a/packages/frontend-dev-utils/src/createDevApp.tsx +++ b/packages/frontend-dev-utils/src/createDevApp.tsx @@ -25,21 +25,32 @@ import { createApp, CreateAppOptions } from '@backstage/frontend-defaults'; import appPlugin from '@backstage/plugin-app'; import ReactDOM from 'react-dom/client'; +type AppPluginWithSimpleOverrides = { + withOverrides(options: { extensions: unknown[] }): FrontendFeature; +}; + +// Collapse the deeply nested override types to avoid excessive instantiation. +const appPluginOverride = ( + appPlugin as unknown as AppPluginWithSimpleOverrides +).withOverrides({ + extensions: [ + appPlugin.getExtension('sign-in-page:app').override({ + disabled: true, + }), + ], +}); + /** * Options for {@link createDevApp}. * * @public */ -export interface CreateDevAppOptions { +export interface CreateDevAppOptions + extends Omit { /** * The list of features to load in the dev app. */ features: (FrontendFeature | FrontendFeatureLoader)[]; - - /** - * Additional options to pass through to `createApp`. - */ - createAppOptions?: Omit; } /** @@ -57,19 +68,16 @@ export interface CreateDevAppOptions { * @public */ export function createDevApp(options: CreateDevAppOptions): void { - const app = createApp({ - ...options.createAppOptions, - features: [ - appPlugin.withOverrides({ - extensions: [ - appPlugin - .getExtension('sign-in-page:app') - .override({ disabled: true }), - ], - }), - ...options.features, - ], - }); + const { features, ...createAppOptions } = options; + const devFeatures: CreateAppOptions['features'] = [ + appPluginOverride, + ...features, + ]; + const appOptions: CreateAppOptions = { + ...createAppOptions, + features: devFeatures, + }; + const app = createApp(appOptions); ReactDOM.createRoot(document.getElementById('root')!).render( app.createRoot(),