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
+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';