From 375dd30fa33f4be26e1c7c49a4d4eb1ee9e19657 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 8 Jan 2024 13:43:36 +0100 Subject: [PATCH 1/7] docs/frontend-system: add missing sorting prefix Signed-off-by: Patrik Oldsberg --- .../building-plugins/{testing.md => 02-testing.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/frontend-system/building-plugins/{testing.md => 02-testing.md} (100%) diff --git a/docs/frontend-system/building-plugins/testing.md b/docs/frontend-system/building-plugins/02-testing.md similarity index 100% rename from docs/frontend-system/building-plugins/testing.md rename to docs/frontend-system/building-plugins/02-testing.md From 39a1dbd9d837e0479b24438ec265f35b38b3d8ea Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 9 Jan 2024 21:02:42 +0100 Subject: [PATCH 2/7] docs/frontend-system: initial plugin index docs Signed-off-by: Patrik Oldsberg --- .../building-plugins/01-index.md | 201 ++++++++++++++++++ microsite/sidebars.json | 5 +- 2 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 docs/frontend-system/building-plugins/01-index.md diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md new file mode 100644 index 0000000000..730dd58156 --- /dev/null +++ b/docs/frontend-system/building-plugins/01-index.md @@ -0,0 +1,201 @@ +--- +id: index +title: Building Frontend Plugins +sidebar_label: Overview +# prettier-ignore +description: Building frontend plugins using the new frontend system +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +This section covers how to build your own frontend [plugins](../architecture/04-plugins.md) and +[overrides](../architecture/05-extension-overrides.md). They are sometimes collectively referred to as +frontend _features_, and what you install to build up a Backstage frontend [app](../architecture/02-app.md). + +## Creating a new plugin + +This guide assumes that you already have a Backstage project set up. Even if you only want to develop a single plugin for publishing, we still recommend that you do so in a standard Backstage monorepo project, as you often end up needing multiple packages. For instructions on how to set up a new project, see our [getting started](../../getting-started/index.md#prerequisites) documentation. + +To create a frontend plugin, run `yarn new`, select `plugin`, and fill out the rest of the prompts. This will create a new package at `plugins/`, which will be the main entrypoint for your plugin. + +> **NOT: The created plugin will currently be templated for use in the legacy frontend system, and you will need to replace the existing plugin wiring code.** + +## The plugin instance + +The starting point of a frontend plugin is the `createPlugin` function, which accepts a single options object as its only parameter. It is imported from `@backstage/frontend-plugin-api`, which is where you will find most of the common APIs for building plugins. + +This is how to create a minimal plugin: + +```tsx title="in src/plugin.ts" +import { createPlugin } from '@backstage/frontend-plugin-api'; + +export const examplePlugin = createPlugin({ + id: 'example', + extensions: [], +}); +``` + +```tsx title="in src/index.ts" +export { examplePlugin as default } from './plugin'; +``` + +Note that we export the plugin as the default export of our package from `src/index.ts`. This is important, as it is how users of our plugin are able to seamlessly install the plugin package in a Backstage app without having to reference the plugin instance through code. + +The plugin ID should be a lowercase dash-separated string, while the plugin instance variable should be the camelCase version of the ID with a `Plugin` suffix. For more details on naming patterns within the frontend system, see [the article on naming patterns](../architecture/08-naming-patterns.md). By sticking to these naming patterns you ensure that users of your plugin more easily recognize the exports and features provided by your plugin. + +## Adding extensions + +The plugin that we created above is empty, and does't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/03-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page. + +To create a new extension you typically use pre-defined [extension creators](../architecture/03-extensions.md#extension-creators), provided either by the framework itself or by other plugins. In this case we'll need to use `createPageExtension` and `createNavItemExtension`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/07-routes.md#creating-a-route-reference) to use as a reference for out page, allowing us to dynamically create URLs that link to our page. + +```tsx title="in src/routes.ts" +import { createRouteRef } from '@backstage/frontend-plugin-api'; + +// Typically all routes are defined in src/routes.ts, in order to avoid circular imports. + +// This will be the route reference for our example page. If you want to link +// to the page from somewhere else, you can use this reference to generate the target path. +export const rootRouteRef = createRouteRef(); +``` + +```tsx title="in src/plugin.ts" +import { + createPlugin, + createPageExtension, + createNavItemExtension, +} from '@backstage/frontend-plugin-api'; +import { rootRouteRef } from './routes'; + +// Note that these extensions aren't exported, only the plugin itself it. +// You can export it locally for testing purposes, but don't export it from the plugin package. +const examplePage = createPageExtension({ + routeRef: rootRouteRef, + + // This is the default path of this page, but integrators are free to override it + defaultPath: '/example', + + // Page extensions are always dynamically loaded using React.lazy(). + // [1] All of the functionality of this page is implemented in the + // ExamplePage component, which is a regular React component. + loader: () => import('./components/ExamplePage').then(m => m.ExamplePage), +}); + +// This nav item is provided to the app.nav extension, and will by default be rendered as a sidebar item +const exampleNavItem = createNavItemExtension({ + routeRef: rootRouteRef, + title: 'Example', + icon: ExampleIcon, // Custom SvgIcon, or one from the Material UI icon library +}); + +// The same plugin as above, now with the extensions added +export const examplePlugin = createPlugin({ + id: 'example', + extensions: [examplePage, exampleNavItem], + // [2] We can also make routes available to other plugins. + routes: { + root: rootRouteRef, + }, +}); +``` + +What we've built here is a very common type of plugin. It's a top-level tool that provides a single page, along with a method for navigating to that page. The implementation of the page component at `[1]` can be arbitrarily complex, anything from a single simple information page, to a full-blown application with multiple sub-pages. + +We have also made the route reference available to other plugins at `[2]`. This makes it possible for app integrators to bind an external link from a different plugin to our plugin page. You can read more about how this works in the [External Route References](../architecture/07-routes.md#external-route-references) section. + +## Utility APIs + +Another type of extensions that is commonly used are [Utility APIs](../utility-apis/01-index.md). They can encapsulate shared pieces of functionality of your plugin, for example an API client for a backend service. You can optionally export your Utility API for other plugins to use, or allow integrators to replace the implementation of your Utility API with their own. For details on how to define and provide your own Utility API in your plugin, see the section on [creating Utility APIs](../utility-apis/02-creating.md). + +What we'll show here is a complete example of a simple Utility API used only within the plugin itself: + +```tsx title="src/api.ts - Defining an interface, API reference, and default implementation" +import { createApiRef } from '@backstage/frontend-plugin-api'; + +export interface ExampleApi { + getExample(): { example: string }; +} + +export const exampleApiRef = createApiRef({ + id: 'plugin.example', +}); + +export class DefaultExampleApi implements ExampleApi { + getExample(): { example: string } { + return { example: 'Hello World!' }; + } +} +``` + +```tsx title="src/components/ExamplePage.tsx - Using the API in our page component" +import { useApi } from '@backstage/frontend-plugin-api'; +import { exampleApiRef } from '../api'; + +export function ExamplePage() { + // highlight-next-line + const exampleApi = useApi(exampleApiRef); + + return ( +
+

Example Page

+

Example: {exampleApi.getExample()}

+
+ ); +} +``` + +```tsx title="in src/plugin.ts - Registering a factory for our API" +import { + createApiFactory, + createApiExtension, +} from '@backstage/frontend-plugin-api'; +import { exampleApiRef, DefaultExampleApi } from './api'; + +// highlight-add-start +const exampleApi = createApiExtension({ + factory: createApiFactory({ + api: exampleApiRef, + deps: {}, + factory: () => new DefaultExampleApi(), + }), +}); +// highlight-add-end + +/* Omitted definitions for examplePage, exampleNavItem, and rootRouteRef. */ + +export const examplePlugin = createPlugin({ + id: 'example', + extensions: [ + // highlight-add-next-line + exampleApi, + examplePage, + exampleNavItem, + ], + routes: { + root: rootRouteRef, + }, +}); +``` + +## Plugin specific extensions + +There are many different plugins that you can extend with additional functionality through extensions. One such plugin is the catalog plugin, one of the core features of Backstage. It lets you catalog the software in your organization, where each item in the catalog has its own page that can be populated with tools and information relating to that catalog entity. In this example we will explore how our plugin can provide such a tool to display on an entity page. + +```tsx title="in src/plugin.ts - An example entity content extension" +import { createEntityContentExtension } from '@backstage/plugin-catalog-react'; + +// Entity content extension are similar to page extensions in that they are rendered at a route, +// although they also have a title to support in-line navigation between the different content. +// Just like a page extensions the content is lazy loaded, and you can also provide a +// route reference if you want to be able to generate a URL that links to the content. +const exampleEntityContent = createEntityContentExtension({ + defaultPath: 'example', + defaultTitle: 'Example', + loader: () => + import('./components/ExampleEntityContent').then(m => ( + + )), +}); +``` + +The `ExampleEntityContent` itself is again a regular React component where you can implement any functionality you want. To access the entity that the content is being rendered for, you can use the `useEntity` hook from `@backstage/plugin-catalog-react`. You can see a full list of API provided by the catalog React library in [the API reference](../../reference/plugin-catalog-react.md). diff --git a/microsite/sidebars.json b/microsite/sidebars.json index c54e59f45c..c34598376f 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -411,7 +411,10 @@ { "type": "category", "label": "Building Plugins", - "items": ["frontend-system/building-plugins/testing"] + "items": [ + "frontend-system/building-plugins/index", + "frontend-system/building-plugins/testing" + ] }, { "type": "category", From 07b40ecb87909aff23e1c67e917ad7d9b15ea4fb Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 9 Jan 2024 22:51:49 +0100 Subject: [PATCH 3/7] docs/frontend-system: add extenison types doc Signed-off-by: Patrik Oldsberg --- .../building-plugins/03-extension-types.md | 43 +++++++++++++++++++ microsite/sidebars.json | 3 +- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 docs/frontend-system/building-plugins/03-extension-types.md diff --git a/docs/frontend-system/building-plugins/03-extension-types.md b/docs/frontend-system/building-plugins/03-extension-types.md new file mode 100644 index 0000000000..36eda55112 --- /dev/null +++ b/docs/frontend-system/building-plugins/03-extension-types.md @@ -0,0 +1,43 @@ +--- +id: index +title: Frontend System Extension Types +sidebar_label: Extension Types +# prettier-ignore +description: Extension types provided by the frontend system and core features +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +This section covers many of the [extension types](../architecture/03-extensions.md#extension-creators) available at your disposal when building Backstage frontend plugins. + +## Built-in extension types + +These are the extension types provided by the Backstage frontend framework itself. + +### Api - [Reference](../../reference/frontend-plugin-api.createapiextension.md) + +An API extension is used to add or override [Utility API factories](../utility-apis/01-index.md) in the app. They are commonly used by plugins for both internal and shared APIs. There are also many built-in Api extensions provided by the framework that you are able to override. + +### Component - [Reference](../../reference/frontend-plugin-api.createcomponentextension.md) + +Components extensions are used to override the component associated with a component reference throughout the app. + +### NavItem - [Reference](../../reference/frontend-plugin-api.createnavitemextension.md) + +Navigation item extensions are used to provide menu items that link to different parts of the app. By default nav items are attached to the app nav extension, which by default is rendered as the left sidebar in the app. + +### Page - [Reference](../../reference/frontend-plugin-api.createpageextension.md) + +Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes. + +### SignInPage - [Reference](../../reference/frontend-plugin-api.createsigninpageextension.md) + +Sign-in page extension have a single purpose - to implement a custom sign-in page. They are always attached to the app root extension and are rendered before the rest of the app until the user is signed in. + +### Theme - [Reference](../../reference/frontend-plugin-api.createthemeextension.md) + +Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use. + +### Translation - [Reference](../../reference/frontend-plugin-api.createtranslationextension.md) + +Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages. diff --git a/microsite/sidebars.json b/microsite/sidebars.json index c34598376f..234afd78d6 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -413,7 +413,8 @@ "label": "Building Plugins", "items": [ "frontend-system/building-plugins/index", - "frontend-system/building-plugins/testing" + "frontend-system/building-plugins/testing", + "frontend-system/building-plugins/extension-types" ] }, { From 894497677b81d1dc9cf65743189c315df3ea4486 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 9 Jan 2024 23:15:53 +0100 Subject: [PATCH 4/7] docs/frontend-system: add core feature extension types Signed-off-by: Patrik Oldsberg --- .../frontend-system/building-plugins/01-index.md | 2 ++ .../building-plugins/03-extension-types.md | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 730dd58156..80182126be 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -199,3 +199,5 @@ const exampleEntityContent = createEntityContentExtension({ ``` The `ExampleEntityContent` itself is again a regular React component where you can implement any functionality you want. To access the entity that the content is being rendered for, you can use the `useEntity` hook from `@backstage/plugin-catalog-react`. You can see a full list of API provided by the catalog React library in [the API reference](../../reference/plugin-catalog-react.md). + +For a more complete list of the different types of extensions that you can create for your plugin, see the [extension types](./03-extension-types.md) section. diff --git a/docs/frontend-system/building-plugins/03-extension-types.md b/docs/frontend-system/building-plugins/03-extension-types.md index 36eda55112..1d59c60545 100644 --- a/docs/frontend-system/building-plugins/03-extension-types.md +++ b/docs/frontend-system/building-plugins/03-extension-types.md @@ -41,3 +41,19 @@ Theme extensions provide custom themes for the app. They are always attached to ### Translation - [Reference](../../reference/frontend-plugin-api.createtranslationextension.md) Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages. + +## Core feature extension types + +These are the extension types provided by the Backstage core feature plugins. + +### EntityCard - [Reference](../../reference/plugin-catalog-react.createentitycardextension.md) + +Creates entity cards to be displayed on the entity pages of the catalog plugin. + +### EntityContent - [Reference](../../reference/plugin-catalog-react.createentitycontentextension.md) + +Creates entity content to be displayed on the entity pages of the catalog plugin. + +### SearchResultListItem - [Reference](../../reference/plugin-search-react.createsearchresultlistitemextension.md) + +Creates search result list items for different types of search results, to be displayed in search result lists. From 22940cb56165167d63f06b5dd98a8ab0a53de9d1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Jan 2024 10:30:18 +0100 Subject: [PATCH 5/7] docs/frontend-system: update extension type links to use alpha API report Signed-off-by: Patrik Oldsberg --- docs/frontend-system/building-plugins/03-extension-types.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/frontend-system/building-plugins/03-extension-types.md b/docs/frontend-system/building-plugins/03-extension-types.md index 1d59c60545..1db99a78b9 100644 --- a/docs/frontend-system/building-plugins/03-extension-types.md +++ b/docs/frontend-system/building-plugins/03-extension-types.md @@ -46,14 +46,14 @@ Translation extension provide custom translation messages for the app. They can These are the extension types provided by the Backstage core feature plugins. -### EntityCard - [Reference](../../reference/plugin-catalog-react.createentitycardextension.md) +### EntityCard - [Reference](https://github.com/backstage/backstage/blob/master/plugins/catalog-react/api-report-alpha.md) Creates entity cards to be displayed on the entity pages of the catalog plugin. -### EntityContent - [Reference](../../reference/plugin-catalog-react.createentitycontentextension.md) +### EntityContent - [Reference](https://github.com/backstage/backstage/blob/master/plugins/catalog-react/api-report-alpha.md) Creates entity content to be displayed on the entity pages of the catalog plugin. -### SearchResultListItem - [Reference](../../reference/plugin-search-react.createsearchresultlistitemextension.md) +### SearchResultListItem - [Reference](https://github.com/backstage/backstage/blob/master/plugins/search-react/api-report-alpha.md) Creates search result list items for different types of search results, to be displayed in search result lists. From f26024be00a7d34c03973f97007d79e11c56a6f0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Jan 2024 14:17:38 +0100 Subject: [PATCH 6/7] docs/frontend-system: plugin index vale fixes Signed-off-by: Patrik Oldsberg --- docs/frontend-system/building-plugins/01-index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 80182126be..afdc9546dd 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -41,11 +41,11 @@ export { examplePlugin as default } from './plugin'; Note that we export the plugin as the default export of our package from `src/index.ts`. This is important, as it is how users of our plugin are able to seamlessly install the plugin package in a Backstage app without having to reference the plugin instance through code. -The plugin ID should be a lowercase dash-separated string, while the plugin instance variable should be the camelCase version of the ID with a `Plugin` suffix. For more details on naming patterns within the frontend system, see [the article on naming patterns](../architecture/08-naming-patterns.md). By sticking to these naming patterns you ensure that users of your plugin more easily recognize the exports and features provided by your plugin. +The plugin ID should be a lowercase dash-separated string, while the plugin instance variable should be the camel case version of the ID with a `Plugin` suffix. For more details on naming patterns within the frontend system, see [the article on naming patterns](../architecture/08-naming-patterns.md). By sticking to these naming patterns you ensure that users of your plugin more easily recognize the exports and features provided by your plugin. ## Adding extensions -The plugin that we created above is empty, and does't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/03-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page. +The plugin that we created above is empty, and doesn't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/03-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page. To create a new extension you typically use pre-defined [extension creators](../architecture/03-extensions.md#extension-creators), provided either by the framework itself or by other plugins. In this case we'll need to use `createPageExtension` and `createNavItemExtension`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/07-routes.md#creating-a-route-reference) to use as a reference for out page, allowing us to dynamically create URLs that link to our page. From 28248998b0af56699a00bbb4c43e7ab97a56307a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 17 Jan 2024 17:28:25 +0100 Subject: [PATCH 7/7] docs/frontend-system: plugin building review fixes Signed-off-by: Patrik Oldsberg --- docs/frontend-system/building-plugins/01-index.md | 13 ++++++++----- .../building-plugins/03-extension-types.md | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index afdc9546dd..cb29b3518a 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -76,9 +76,10 @@ const examplePage = createPageExtension({ defaultPath: '/example', // Page extensions are always dynamically loaded using React.lazy(). - // [1] All of the functionality of this page is implemented in the + // All of the functionality of this page is implemented in the // ExamplePage component, which is a regular React component. - loader: () => import('./components/ExamplePage').then(m => m.ExamplePage), + // highlight-next-line + loader: () => import('./components/ExamplePage').then(m => ), }); // This nav item is provided to the app.nav extension, and will by default be rendered as a sidebar item @@ -92,16 +93,18 @@ const exampleNavItem = createNavItemExtension({ export const examplePlugin = createPlugin({ id: 'example', extensions: [examplePage, exampleNavItem], - // [2] We can also make routes available to other plugins. + // We can also make routes available to other plugins. + // highlight-start routes: { root: rootRouteRef, }, + // highlight-end }); ``` -What we've built here is a very common type of plugin. It's a top-level tool that provides a single page, along with a method for navigating to that page. The implementation of the page component at `[1]` can be arbitrarily complex, anything from a single simple information page, to a full-blown application with multiple sub-pages. +What we've built here is a very common type of plugin. It's a top-level tool that provides a single page, along with a method for navigating to that page. The implementation of the page component, in this case the highlighted `ExamplePage`, can be arbitrarily complex. It can be anything from a single simple information page, to a full-blown application with multiple sub-pages. -We have also made the route reference available to other plugins at `[2]`. This makes it possible for app integrators to bind an external link from a different plugin to our plugin page. You can read more about how this works in the [External Route References](../architecture/07-routes.md#external-route-references) section. +We have also provided external access to our route reference by passing it to the plugin `routes` option. This makes it possible for app integrators to bind an external link from a different plugin to our plugin page. You can read more about how this works in the [External Route References](../architecture/07-routes.md#external-router-references) section. ## Utility APIs diff --git a/docs/frontend-system/building-plugins/03-extension-types.md b/docs/frontend-system/building-plugins/03-extension-types.md index 1db99a78b9..2bea073ed6 100644 --- a/docs/frontend-system/building-plugins/03-extension-types.md +++ b/docs/frontend-system/building-plugins/03-extension-types.md @@ -1,5 +1,5 @@ --- -id: index +id: extension-types title: Frontend System Extension Types sidebar_label: Extension Types # prettier-ignore