Merge pull request #22471 from backstage/camilaibs/building-new-frontend-apps

[DI] Add building apps and built-in extensions docs
This commit is contained in:
Patrik Oldsberg
2024-02-08 09:54:34 +01:00
committed by GitHub
3 changed files with 346 additions and 0 deletions
@@ -0,0 +1,148 @@
---
id: index
title: Building Frontend Apps
sidebar_label: Overview
# prettier-ignore
description: Building frontend apps using the new frontend system
---
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
To get set up quickly with your own Backstage project you can create a Backstage App.
A Backstage App is a monorepo setup that includes everything you need to run Backstage in your own environment.
## Creating a new app
To create a new Backstage app we recommend using the `@backstage/create-app` command line, and the easiest way to run this package is with `npx`:
:::note
The create-app CLI requires Node.js Active LTS Release.
:::
```sh
# The command bellow creates a Backstage App inside the current folder.
# The name of the app-folder is the name that was provided when prompted.
npx @backstage/create-app@latest
```
The created-app is currently templated for legacy frontend system applications, so the app wiring code it creates needs to be migrated, see [the app instance](#the-app-instance) section for an example.
## The app instance
The starting point of a frontend app is the `createApp` function, which accepts a single options object as its only parameter. It is imported from `@backstage/frontend-app-api`, which is where you will find most of the common APIs for building apps.
This is how to create a minimal app:
```tsx title="in src/index.ts"
import ReactDOM from 'react-dom/client';
import { createApp } from '@backstage/frontend-app-api';
import catalogPlugin from '@backstage/plugin-catalog/alpha';
// Create your app instance
const app = createApp({
// Features such as plugins can be installed explicitly, but we will explore other options later on
features: [catalogPlugin],
});
// This creates a React element that renders the entire app
const root = app.createRoot();
// Just like any other React we need a root element. No server side rendering is used.
const rootEl = document.getElementById('root')!;
ReactDOM.createRoot(rootEl).render(root);
```
Note that `createRoot` returns the root element that is rendered by React. The above example is installing a catalog plugin and using default settings for the app, as no options other than the `features` array are passed to the `createApp` function.
Visit the [built-in extensions](#customize-or-override-built-in-extensions) section to see what is installed by default in a Backstage application.
## Configure your app
### Bind external routes
Linking routes from different plugins requires this configuration. You can do this either through a configuration file or by coding, visit [this](https://backstage.io/docs/frontend-system/architecture/routes#binding-external-route-references) page for instructions.
### Enable feature discovery
Use this setting to enable experimental feature discovery when building your app with `@backstage/cli`. With this configuration your application tries to discover and install package extensions automatically, check [here](../architecture/02-app.md#feature-discovery) for more details.
:::warning
Remember that package extensions that are not auto-discovered must be manually added to the application when creating an app. See [features](#install-features-manually) for more details.
:::
### Configure extensions individually
It is possible to enable, disable and configure extensions individually in the `app-config.yaml` config file. To get familiar with what is available for app extensions personalization, go to the [built-in extensions](./02-built-in-extensions.md) documentation. For plugin customizations, we recommend that you read the instructions in each plugin's README file.
### Customize or override built-in extensions
Previously you would customize the application route, components, apis, sidebar, etc. through the code in `App.tsx`. Now we want you to write less code and install more extensions to customize your Backstage instance. See [here](../building-plugins/03-extension-types.md) which types of extensions are available for you to customize your application.
## Use code to customize the app at a more granular level
### Install features manually
A manual installation is required if your packages are not discovered automatically, either because you are not using `@backstage/cli` to build your application or because the features are defined in local modules in the app package. In order to manually install a feature, you must import it and pass it to the `createApp` function:
```tsx title="packages/app/src/App.tsx"
import { createApp } from '@backstage/frontend-app-api';
// This plugin was create as a local module in the app
import { somePlugin } from './plugins';
const app = createApp({
features: [somePlugin],
});
export default app.createRoot();
```
:::info
You can also pass overrides to the features array, for more details, please read the [extension overrides](../architecture/05-extension-overrides.md) documentation.
:::
### Using an async features loader
In case you need to perform asynchronous operations before passing features to the `createApp` function, define a [feature loader](https://backstage.io/docs/reference/frontend-app-api.createappfeatureloader/) object and pass it to the `features` option:
```tsx title="packages/app/src/App.tsx"
import { createApp } from '@backstage/frontend-app-api';
const app = createApp({
features: {
getLoaderName: () => '<your-custom-features-loader-name>',
// there is a reference to the config api in the options param
load: async _options => {
// returning a lazy loaded plugins and overrides array
// could be util for module federation
return import('./features').then(m => m.default);
},
},
});
export default app.createRoot();
```
### Lazy load your configuration file
In some cases we want to load our configuration from a backend server and to do so, you can pass an callback to the `configLoader` option when calling the `createApp` function, the callback should return a promise of an object with the config object:
```tsx title="packages/app/src/App.tsx"
import { createApp } from '@backstage/frontend-app-api';
import { getConfigFromServer } from './utils';
// Example lazy loading the app configuration
const app = createApp({
// Returns Promise<{ config: ConfigApi }>
configLoader: async () => {
// Calls an async utility method that fetches the config object from the server
const config = await getConfigFromServer();
// Feel free to manipulate the config object before returning it
// A common example is conditionally modify the config based on the running enviroment
return { config };
},
});
export default app.createRoot();
```
@@ -0,0 +1,190 @@
---
id: built-in-extensions
title: App Built-in Extensions
sidebar_label: Built-in extensions
# prettier-ignore
description: Configuring or overriding built-in extensions
---
Built-in extensions are default app extensions that are always installed when you create a Backstage app.
## Disable built-in extensions
All built-in extensions can be disabled in the same way as you disable any other extension:
```yaml title="app-config.yaml"
extensions:
# Disabling the built-in app root alert element
- app-root-element:app/alert-display: false
```
:::warning
Be careful when disabling built-in extensions, as there may be other extensions depending on their existence. For example, the built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/docs/reference/core-plugin-api.alertapi) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
:::
## Override built-in extensions
You can override any built-in extension whenever their customizations, whether through configuration or input, do not meet a use case for your Backstage instance. Check out [this](../architecture/05-extension-overrides.md) documentation on how to override application extensions.
:::warning
Be aware there could be some implementation requirements to properly override an built-in extension, such as using same apis and do not remove inputs or configurations otherwise you can cause a side effect in other parts of the system that expects same minimal behavior.
:::
## Default built-in extensions
### App
This extension is the first extension attached to the extension tree. It is responsible for receiving the application's root element and other Frontend framework inputs.
#### Inputs
| Name | Description | Type | Optional | Default | Extension creator |
| ------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| root | The app root element. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | The [`App/Root`](#app-root) extension output. | No creator available, configure or override the [`App/Root`](#app-root) extension. |
| apis | The app apis factories. | [createApiExtension.factoryDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createapiextension.factorydataref) | false | See [default apis](#default-apis-extensions). | [createApiExtension](https://backstage.io/docs/reference/frontend-plugin-api.createapiextension) |
| themes | The app themes list. | [createThemeExtension.themeDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension.themedataref) | false | See [default themes](#default-theme-extensions). | [createThemeExtension](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension) |
| components | The app components list. | [createComponentExtension.componentDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension.componentdataref) | false | See [default components](#default-components-extensions). | [createComponentExtension](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension) |
| translations | The app translations list. | [createTranslationExtension.translationDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension.translationdataref) | false | - | [createTranslationExtension](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension) |
#### Default theme extensions
Extensions that provides default theme inputs for the `App` extension.
| kind | namespace | name | id |
| :---: | :-------: | :---: | :---------------: |
| theme | app | light | `theme:app/light` |
| theme | app | dark | `theme:app/dark` |
#### Default components extensions
Extensions that provides default components inputs for the `App` extension.
| kind | namespace | name | id |
| :--------: | :-------: | :-----------------------------------: | :----------------------------------------------------: |
| components | app | core.components.progress | `components:app/core.components.progress` |
| components | app | core.components.notFoundErrorPage | `components:app/core.components.notFoundErrorPage` |
| components | app | core.components.errorBoundaryFallback | `components:app/core.components.errorBoundaryFallback` |
#### Default apis extensions
Extensions that provides default apis inputs for the `App` extension.
| kind | namespace | name | id |
| :--: | :------------------------: | :--: | :------------------------------: |
| api | core.discovery | - | `api:core.discovery` |
| api | core.alert | - | `api:core.alert` |
| api | core.analytics | - | `api:core.analytics` |
| api | core.error | - | `api:core.error` |
| api | core.storage | - | `api:core.storage` |
| api | core.fetch | - | `api:core.fetch` |
| api | core.oauthrequest | - | `api:core.oauthrequest` |
| api | core.auth.google | - | `api:core.auth.google` |
| api | core.auth.microsoft | - | `api:core.auth.microsoft` |
| api | core.auth.github | - | `api:core.auth.github` |
| api | core.auth.okta | - | `api:core.auth.okta` |
| api | core.auth.gitlab | - | `api:core.auth.gitlab` |
| api | core.auth.onelogin | - | `api:core.auth.onelogin` |
| api | core.auth.bitbucket | - | `api:core.auth.bitbucket` |
| api | core.auth.bitbucket-server | - | `api:core.auth.bitbucket-server` |
| api | core.auth.atlassian | - | `api:core.auth.atlassian` |
| api | plugin.permission.api | - | `api:plugin.permission.api` |
### App root
This is the extension that creates the app root element, so it renders root level components such as app router and layout.
#### Inputs
| Name | Description | Requirements | Optional | Default | Extension creator |
| ---------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| router | A React component that should manager the app routes context. | It must be one [router](https://reactrouter.com/en/main/routers/picking-a-router#web-projects) component or a custom component compatible with the 'react-router' library. | true | [BrowserRouter](https://reactrouter.com/en/main/router-components/browser-router) | [createRouterExtension](https://backstage.io/docs/reference/frontend-plugin-api.createrouterextension) |
| signInPage | A React component that should render the app sign-in page. | Should call the `onSignInSuccess` prop when the user has been successfully authorized, otherwise the user will not be correctly redirected to the application home page. | true | The default `AppRoot` extension does not use a default component for this input, it bypasses the user authentication check and always renders all routes when a login page is not installed. | [createSignInPageExtension](https://backstage.io/docs/reference/frontend-plugin-api.createsigninpageextension/) |
| children | A React component that renders the app sidebar and main content in a particular layout. | - | false | The [`App/Layout`](#app-layout) extension output. | No creator available, configure or override the [`App/Layout`](#app-layout) extension. |
| elements | React elements to be rendered outside of the app layout, such as shared popups. | - | false | See [default elements](#default-app-root-elements-extensions). | [createAppRootElementExtension](https://backstage.io/docs/reference/frontend-plugin-api.createapprootelementextension/) |
| wrappers | React components that should wrap the root element. | - | true | - | [createAppRootWrapperExtension](https://backstage.io/docs/reference/frontend-plugin-api.createapprootwrapperextension/) |
#### Default app root elements extensions
##### Alert Display
An app root element extension that displays messages posted via the [`AlertApi`](https://backstage.io/docs/reference/core-plugin-api.alertapi).
| kind | namespace | name | id |
| :--------------: | :-------: | :-----------: | :----------------------------------: |
| app-root-element | app | alert-display | `app-root-element:app/alert-display` |
###### Configurations
| Key | Type | Default value | Description |
| -------------------- | -------------------------------------------------------------------------- | ----------------------------------------- | ----------------------------------------------------------------- |
| `transientTimeoutMs` | number | 5000 | Time in milliseconds to wait before displaying messages. |
| `anchorOrigin` | { vertical: 'top' \| 'bottom', horizontal: 'left' \| 'center' \| 'right' } | { vertical: 'top', horizontal: 'center' } | Position on the screen where the message alert will be displayed. |
###### Override or disable the extension
If you do not want to display alerts, disable this extension or if the available settings do not meet your needs, override this extension.
:::warning
The built-in "alert display" extension displays messages retrieved via [AlertApi](https://backstage.io/docs/reference/core-plugin-api.alertapi) and disabling this extension will cause the application to no longer display these messages unless you install another extension that displays messages from `AlertApi`.
:::
##### OAuth Request Dialog
An app root element extension that renders the oauth request dialog, it is based on the [oauthRequestApi](https://backstage.io/docs/reference/core-plugin-api.oauthrequestapi/).
| kind | namespace | name | id |
| :--------------: | :-------: | :------------------: | :-----------------------------------------: |
| app-root-element | app | oauth-request-dialog | `app-root-element:app/oauth-request-dialog` |
### App layout
Renders the app's sidebar and content in a specific layout.
| kind | namespace | name | id |
| :--: | :-------: | :----: | :----------: |
| - | app | layout | `app/layout` |
#### Inputs
| Name | Description | Type | Optional | Default | Extension creator |
| ------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- | ------- | ------------------------------------ |
| nav | A React element that renders the app sidebar. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | - | Override the `App/Nav` extension. |
| content | A React element that renders the app content. | [coreExtensionData.reactElement](https://backstage.io/docs/reference/frontend-plugin-api.coreextensiondata) | false | - | Override the `App/Routes` extension. |
### App nav
Extension responsible for rendering the logo and items in the app's sidebar.
| kind | namespace | name | id |
| :--: | :-------: | :--: | :-------: |
| - | app | nav | `app/nav` |
#### Inputs
| Name | Description | Type | Optional | Default | Extension creator |
| ----- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------------------------------------------------------------------------------------------------------- |
| logos | A nav logos object. | [createNavLogoExtension.logoElementsDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavlogoextension.logoelementsdataref) | true | - | [createNavLogoExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavlogoextension) |
| items | Nav items target objects. | [createNavItemExtension.targetDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension.targetdataref) | true | - | [createNavItemExtension](https://backstage.io/docs/reference/frontend-plugin-api.createnavitemextension) |
### App routes
Renders a route element for each route received as input and a `NotFoundErrorPage` component.
| kind | namespace | name | id |
| :--: | :-------: | :----: | :----------: |
| - | app | routes | `app/routes` |
#### Caveats
Be careful when overriding this extension, as to do so correctly you must consider these implementation requirements:
- The routing system is managed by more than one extension, and they all use `react-router` behind the scenes. There are also some utilities that are based on the same `routing` library like `useRouteRefParams`. Therefore, you cannot use a different library without causing side effects in these other extensions and helper utilities;
- Don't remove configs or inputs, just extend these things yourself with optional new options, otherwise it will cause breaking changes for extensions like `createPageExtension` that depend on this type of inputs;
- Remember to user the route refs for getting paths dynamically, otherwise if an adopter modifies a path through configuration, the route is not going to point to the configured path;
- Adopters expect to be able to customize the `NotFoundErrorPage` component via Components API, you should render this component for routes not configured.
#### Inputs
| Name | Description | Type | Optional | Default | Extension creator |
| ------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------- |
| routes | The route objects list. | `{ path: coreExtensionData.routePath, ref: coreExtensionData.routeRef.optional(), element: coreExtensionData.reactElement }` | false | - | [createPageExtension](https://backstage.io/docs/reference/frontend-plugin-api.createpageextension) |