Add @backstage/frontend-dev-utils package

Adds a new `@backstage/frontend-dev-utils` package for the new frontend
system. It provides a minimal `createDevApp` helper for wiring up a
development app from a `dev/` entry point.

The app-visualizer plugin is updated to use this new package as the
initial testing ground.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
This commit is contained in:
Patrik Oldsberg
2026-03-16 11:31:49 +01:00
parent 3775ef5b51
commit c25532a7a1
14 changed files with 315 additions and 9 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@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.
@@ -0,0 +1,5 @@
---
'@backstage/plugin-app-visualizer': patch
---
Switched dev entry point to use `createDevApp` from `@backstage/frontend-dev-utils`.
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+19
View File
@@ -0,0 +1,19 @@
# @backstage/frontend-dev-utils
Utilities for developing Backstage frontend plugins using the new frontend system.
This package provides a minimal helper for wiring up a development app from a `dev/` entry point, making it easy to run and test frontend plugins in isolation.
## Installation
Install the package via Yarn:
```sh
cd plugins/<plugin> # if within a monorepo
yarn add -D @backstage/frontend-dev-utils
```
## Documentation
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
- [Backstage Documentation](https://backstage.io/docs)
@@ -0,0 +1,10 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-frontend-dev-utils
title: '@backstage/frontend-dev-utils'
description: Utilities for developing Backstage frontend plugins using the new frontend system.
spec:
lifecycle: experimental
type: backstage-web-library
owner: framework-maintainers
@@ -0,0 +1,2 @@
# Knip report
+64
View File
@@ -0,0 +1,64 @@
{
"name": "@backstage/frontend-dev-utils",
"version": "0.0.0",
"description": "Utilities for developing Backstage frontend plugins using the new frontend system.",
"backstage": {
"role": "web-library"
},
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"keywords": [
"backstage"
],
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/frontend-dev-utils"
},
"license": "Apache-2.0",
"sideEffects": false,
"main": "src/index.ts",
"types": "src/index.ts",
"files": [
"dist"
],
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"start": "backstage-cli package start",
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/frontend-defaults": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@backstage/plugin-app": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^16.0.0",
"@types/react": "^18.0.0",
"react": "^18.0.2",
"react-dom": "^18.0.2",
"react-router-dom": "^6.30.2"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0",
"react-router-dom": "^6.30.2"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
}
+18
View File
@@ -0,0 +1,18 @@
## API Report File for "@backstage/frontend-dev-utils"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { CreateAppOptions } from '@backstage/frontend-defaults';
import { FrontendFeature } from '@backstage/frontend-plugin-api';
import { FrontendFeatureLoader } from '@backstage/frontend-plugin-api';
// @public
export function createDevApp(options: CreateDevAppOptions): void;
// @public
export interface CreateDevAppOptions {
createAppOptions?: Omit<CreateAppOptions, 'features'>;
features: (FrontendFeature | FrontendFeatureLoader)[];
}
```
@@ -0,0 +1,74 @@
/*
* Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
PageBlueprint,
createFrontendPlugin,
} from '@backstage/frontend-plugin-api';
import { within, waitFor } from '@testing-library/react';
import { mockApis } from '@backstage/test-utils';
import { createDevApp } from './createDevApp';
import { default as appPlugin } from '@backstage/plugin-app';
describe('createDevApp', () => {
afterEach(() => {
document.getElementById('root')?.remove();
});
it('should render a dev app with a plugin', async () => {
const root = document.createElement('div');
root.id = 'root';
document.body.appendChild(root);
const testPlugin = createFrontendPlugin({
pluginId: 'test',
extensions: [
PageBlueprint.make({
params: {
path: '/',
loader: async () => <div>Test Plugin Page</div>,
},
}),
],
});
createDevApp({
features: [
appPlugin.withOverrides({
extensions: [
appPlugin
.getExtension('sign-in-page:app')
.override({ disabled: true }),
],
}),
testPlugin,
],
createAppOptions: {
advanced: {
configLoader: async () => ({ config: mockApis.config() }),
},
},
});
const body = within(document.body);
await waitFor(
() => {
expect(body.getByText('Test Plugin Page')).toBeDefined();
},
{ timeout: 10000 },
);
}, 15000);
});
@@ -0,0 +1,64 @@
/*
* Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
FrontendFeature,
FrontendFeatureLoader,
} from '@backstage/frontend-plugin-api';
import { createApp, CreateAppOptions } from '@backstage/frontend-defaults';
import ReactDOM from 'react-dom/client';
/**
* Options for {@link createDevApp}.
*
* @public
*/
export interface CreateDevAppOptions {
/**
* The list of features to load in the dev app.
*/
features: (FrontendFeature | FrontendFeatureLoader)[];
/**
* Additional options to pass through to `createApp`.
*/
createAppOptions?: Omit<CreateAppOptions, 'features'>;
}
/**
* Creates and renders a minimal development app for the new frontend system.
*
* @example
* ```tsx
* // dev/index.ts
* import { createDevApp } from '@backstage/frontend-dev-utils';
* import myPlugin from '../src';
*
* createDevApp({ features: [myPlugin] });
* ```
*
* @public
*/
export function createDevApp(options: CreateDevAppOptions): void {
const app = createApp({
...options.createAppOptions,
features: options.features,
});
ReactDOM.createRoot(document.getElementById('root')!).render(
app.createRoot(),
);
}
+23
View File
@@ -0,0 +1,23 @@
/*
* Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Utilities for developing Backstage frontend plugins using the new frontend system.
*
* @packageDocumentation
*/
export { createDevApp, type CreateDevAppOptions } from './createDevApp';
+2 -7
View File
@@ -14,12 +14,7 @@
* limitations under the License.
*/
import ReactDOM from 'react-dom/client';
import { createApp } from '@backstage/frontend-defaults';
import { createDevApp } from '@backstage/frontend-dev-utils';
import { default as plugin } from '../src';
const app = createApp({
features: [plugin],
});
ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot());
createDevApp({ features: [plugin] });
+1 -1
View File
@@ -43,7 +43,7 @@
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@backstage/frontend-defaults": "workspace:^",
"@backstage/frontend-dev-utils": "workspace:^",
"@types/react": "^18.0.0",
"react": "^18.0.2",
"react-dom": "^18.0.2",
+27 -1
View File
@@ -3671,6 +3671,32 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/frontend-dev-utils@workspace:^, @backstage/frontend-dev-utils@workspace:packages/frontend-dev-utils":
version: 0.0.0-use.local
resolution: "@backstage/frontend-dev-utils@workspace:packages/frontend-dev-utils"
dependencies:
"@backstage/cli": "workspace:^"
"@backstage/frontend-defaults": "workspace:^"
"@backstage/frontend-plugin-api": "workspace:^"
"@backstage/plugin-app": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@testing-library/jest-dom": "npm:^6.0.0"
"@testing-library/react": "npm:^16.0.0"
"@types/react": "npm:^18.0.0"
react: "npm:^18.0.2"
react-dom: "npm:^18.0.2"
react-router-dom: "npm:^6.30.2"
peerDependencies:
"@types/react": ^17.0.0 || ^18.0.0
react: ^17.0.0 || ^18.0.0
react-dom: ^17.0.0 || ^18.0.0
react-router-dom: ^6.30.2
peerDependenciesMeta:
"@types/react":
optional: true
languageName: unknown
linkType: soft
"@backstage/frontend-dynamic-feature-loader@workspace:packages/frontend-dynamic-feature-loader":
version: 0.0.0-use.local
resolution: "@backstage/frontend-dynamic-feature-loader@workspace:packages/frontend-dynamic-feature-loader"
@@ -4026,7 +4052,7 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/core-components": "workspace:^"
"@backstage/core-plugin-api": "workspace:^"
"@backstage/frontend-defaults": "workspace:^"
"@backstage/frontend-dev-utils": "workspace:^"
"@backstage/frontend-plugin-api": "workspace:^"
"@backstage/ui": "workspace:^"
"@remixicon/react": "npm:^4.6.0"