From f972f8d9f97af8d874958114d4f542c19018b7f3 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sun, 4 Feb 2024 14:13:14 +0100 Subject: [PATCH] refactor: apply more review suggestions Signed-off-by: Camila Belo --- .../frontend-system/building-apps/01-index.md | 78 ++++++++++++------- .../building-apps/02-built-in-extensions.md | 42 +++++----- 2 files changed, 72 insertions(+), 48 deletions(-) diff --git a/docs/frontend-system/building-apps/01-index.md b/docs/frontend-system/building-apps/01-index.md index cf860c01d9..e23f628fb6 100644 --- a/docs/frontend-system/building-apps/01-index.md +++ b/docs/frontend-system/building-apps/01-index.md @@ -14,19 +14,19 @@ A Backstage App is a monorepo setup that includes everything you need to run Bac ## Creating a new app -To create a Backstage app, you will need to have Node.js Active LTS Release installed. +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`: -The easiest way to run the create app 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 ``` -This will create a new Backstage App inside the current folder. The name of the app-folder is the name that was provided when prompted. - -:::note -The created app will currently be templated for use in the legacy frontend system, and you will need to replace the existing app wiring code. -::: +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 @@ -37,6 +37,7 @@ 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({ @@ -49,31 +50,41 @@ 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 the `createRoot` exports an element instead of a component and it no longer accepts a custom root element as parameter, so this is the reason you can pass the returned element directly to React `createRoot`. See [customizing your root app element](#app-root) for more details on how to do it with extensions. +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 -### Enable features discovering +### Bind external routes -This is how you enable the experimental feature discovering when building your app with the `@backstage/cli`, check out [here](https://backstage.io/docs/frontend-system/architecture/app#feature-discovery) for more details. +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. ::: -### Bind external routes +### Configure extensions individually -This is the configuration you do when you want to associate an external plugin route ref with a regular one. There are two ways of doing it: via configuration file or through code. Access [this](https://backstage.io/docs/frontend-system/architecture/routes#binding-external-route-references) page for learning both ways. +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 individual extensions +### Customize or override built-in extensions -It possible to enable, disable, order and configure extensions individually in the `app-config.yaml` config file. To get familiar with what is available for app extensions, go to the [built-in extensions](./02-built-in-extensions.md) documentation. For plugins customizations, we recommend you checking their read files instructions. +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. -## Install features manually +## Use code to customize the app at a more granular level -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. For manually install a feature you have to import your it and pass it to the `createApp` function: +### 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'; @@ -91,34 +102,47 @@ export default app.createRoot(); You can also pass overrides to the features array, for more details, please read the [extension overrides](../architecture/05-extension-overrides.md) documentation. ::: -If for some reason you need to perform asynchronous operations before passing features to the `createApp` function, you can use an async function as `features` option and this should return a features array promise: +### 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({ - // Lazy loading the plugin feature - features: import('./plugins/MyPlugin').then(m => [m.default]), + features: { + getLoaderName: () => '', + // 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 +### Lazy load your configuration file -In some cases we want to load our configuration from a backend server or we just want to override the default app configuration loader. If you want to do it, you can pass an async callback to the `createApp` function and it should return a promise of the config object: +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({ - // Lazy loading the plugin feature - features: import('./plugins/MyPlugin').then(m => [m.default]), + // 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(); ``` - -## 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. diff --git a/docs/frontend-system/building-apps/02-built-in-extensions.md b/docs/frontend-system/building-apps/02-built-in-extensions.md index be89e23c9e..4f8417f74f 100644 --- a/docs/frontend-system/building-apps/02-built-in-extensions.md +++ b/docs/frontend-system/building-apps/02-built-in-extensions.md @@ -38,13 +38,13 @@ This extension is the first extension attached to the extension tree. It is resp #### 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](#defaults-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) | +| 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 @@ -59,11 +59,11 @@ Extensions that provides default theme inputs for the `App` extension. Extensions that provides default components inputs for the `App` extension. -| kind | namespace | name | id | -| :--------: | :-------: | :-----------------------------------: | :--------------------: | -| components | app | core.components.progress | `components:app/light` | -| components | app | core.components.notFoundErrorPage | `components:app/dark` | -| components | app | core.components.errorBoundaryFallback | `components:app/dark` | +| 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 @@ -91,7 +91,7 @@ Extensions that provides default apis inputs for the `App` extension. ### App root -This is the extension that creates the app root element, so it renders root level components such as app router, layout. +This is the extension that creates the app root element, so it renders root level components such as app router and layout. #### Inputs @@ -115,10 +115,10 @@ An app root element extension that displays messages posted via the [`AlertApi`] ###### 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 | +| 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 @@ -178,10 +178,10 @@ Renders a route element for each route received as input and a `NotFoundErrorPag 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 and there are also some utilities that are based on the same 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 entry, for example; -- 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. -- Adopter expects to be able to customize the `NotFoundErrorPage` component via Components API, you should render this component for routes not configured. +- 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