Merge pull request #4251 from backstage/rugvip/extmpl
cli: update frontend plugin template to use new extension API
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Update `create-plugin` template to use the new composability API, by switching to exporting a single routable extension component.
|
||||
@@ -0,0 +1 @@
|
||||
<mxfile host="Chrome" modified="2021-01-03T17:00:54.430Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" version="13.9.5" etag="DwPjZwigIikbKX1hFX51" type="device"><diagram id="WdRoEH4gdfKcJUWp3sm5" name="Page-1">7VpLc6M4EP41PiaFBLbh6LxmLlubSqZ2Z+amBQVUBYgScmzPrx9hJIMQGJzEhtTOxUaNnp++bnU3mtm3yfYLQ1n0Fw1wPINWsJ3ZdzMIAfQ88VdIdqXEs2EpCBkJZKVK8Ex+YSm0pHRNApxrFTmlMSeZLvRpmmKfazLEGN3o1V5orI+aoRAbgmcfxab0XxLwqJS6cFnJv2ISRmpksJALTpCqLFeSRyigm5rIvp/Zt4xSXj4l21scF+ApXMp2Dx1vDxNjOOVDGvwk7Nv6n4erTfLNgzcr9/HvH5sru+zlFcVrueBVlsn58p0CId+QJEapKN3I+phxvO2cCDgsT/AC0wRzthNVZANXAiIZARayvKnwPdSJathCRwqR3NPw0HW1bPEgV34CCs4gFBhdpwEu+rEEEJuIcPycIb94uxHkF7KIJ2LcOzAUp+4t6QQP6uA5JnZt0J0NOduEzsAtFMBlnXhIxUX/qerWqXxyFv2EsuFFCQVNvbrfcpzmhKb5aMRyjsPo6CgCZ1xqKSbVMHyia46f8MtEEQSuDqEzNoLgsyPYRkLQpsrnw9CA8IHRVKhyIKSP8Tok6buPy7cgBVtsnHVRG2eSqwOOC1ELfqZz05ucZh6HDyygjh8cFz8wPdPWB+C0COga+I3voPRosM5AAMamoNUN4UQRbPjJYGQOgoWBoAGcOGhXRRgvSn6M8pz4OlYByqM9rhVwODBC+iGwiWHpmvl4wJZzxEJ8rMOOgKUG9LwFaCVjOEacvOqLaENfjvBIiVjeYZu9xjZbjf0rlykbwVrCQPWjGtqNhuWyjYb7vT+s8h10WL6fDmKf2e67VLN94UdRuIZzVb7b1t/e7VRpS/h31Yl4rjcTxapVUVCNzs63oXQblW9LnW/eqXRTNs7R+3EvzD7zRJy6R9F/IF42VgKmV1uciCxF8dSh7D8ZLxx2ms7F6baw1aQdt2iFLXuWQ1DGIxpSsXv3lbTTwvYZ2MnYSm9MW3lg1k63ne81lmA+zFoKwqBdrVpWVMiPzLcxzsI6bVq2Vl88lBP4UMsNB+Sga2px+A5j1d3HooBiEqaFNglGYiYEhaUhPopX8kVCgmCvBKKPrOg52YbFZ6/r8ksTLP+LbhXr9x/CgH0t8L1JKfcjKX+JSfb1zFbtqunvt+SMnBaqw7MZtXnr+TDpoLORj3THTumaIdPkIWy4Ke7IcTs0w4xVRibrmzSV2DWV+LLwfb7MEVhMTYvbHeVpYzgxNbZND1mo8QPyOZW9TxDDiemybWbRDeSGRhkqlBgSZczOHAsobvQGA+VZMFqizr2eL4ED3fJ3sdTY8eY8SiMfA5r0OXMiRV3w+rCs7ttj0jdm/ZafI+tnW7MRydtkWTNqGErXnuzhR8WxDTdq2RPHNr0u7wJxrG1eGjIU508cW23GaHEs+N9uVNellJ1uWj5+Y2bFtRt1U7ZUuOq+sX3/Gw==</diagram></mxfile>
|
||||
@@ -23,35 +23,60 @@ browser APIs or by depending on external modules to do the work.
|
||||
|
||||
### Routing
|
||||
|
||||
Each plugin is responsible for registering its components to corresponding
|
||||
routes in the app.
|
||||
Each plugin can export routable extensions, which are then imported into the app
|
||||
and mounted at a path.
|
||||
|
||||
The app will call the `createPlugin` method on each plugin, passing in a
|
||||
`router` object with a set of methods on it.
|
||||
First you will need a `RouteRef` instance to serve as the mount point of your
|
||||
extensions. This can be used within your own plugin to create a link to the
|
||||
extension page using `useRouteRef`, as well as for other plugins to link to your
|
||||
extension.
|
||||
|
||||
```jsx
|
||||
It is best to place these in a separate top-level `src/routes.ts` file, in order
|
||||
to avoid import cycles, for example like this:
|
||||
|
||||
```tsx
|
||||
/* src/routes.ts */
|
||||
import { createRouteRef } from '@backstage/core';
|
||||
|
||||
// Note: This route ref is for internal use only, don't export it from the plugin
|
||||
export const rootRouteRef = createRouteRef({
|
||||
title: 'Example Page',
|
||||
});
|
||||
```
|
||||
|
||||
Now that we have a `RouteRef`, we import it into `src/plugin.ts`, create our
|
||||
plugin instance with `createPlugin`, as well as create and wrap our routable
|
||||
extension using `createRoutableExtension` from `@backstage/core`:
|
||||
|
||||
```tsx
|
||||
/* src/plugin.ts */
|
||||
import { createPlugin, createRouteRef } from '@backstage/core';
|
||||
import ExampleComponent from './components/ExampleComponent';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
path: '/new-plugin',
|
||||
title: 'New plugin',
|
||||
});
|
||||
|
||||
export const plugin = createPlugin({
|
||||
id: 'new-plugin',
|
||||
register({ router }) {
|
||||
router.addRoute(rootRouteRef, ExampleComponent);
|
||||
// Create a plugin instance and export this from your plugin package
|
||||
export const examplePlugin = createPlugin({
|
||||
id: 'example',
|
||||
routes: {
|
||||
root: rootRouteRef, // This is where the route ref should be exported for usage in the app
|
||||
},
|
||||
});
|
||||
|
||||
// This creates a routable extension, which are typically full pages of content.
|
||||
// Each extension should also be exported from your plugin package.
|
||||
export const ExamplePage = examplePlugin.provide(
|
||||
createRoutableExtension({
|
||||
// The component needs to be lazy-loaded. It's what will actually be rendered in the end.
|
||||
component: () =>
|
||||
import('./components/ExampleComponent').then(m => m.ExampleComponent),
|
||||
// This binds the extension to this route ref, which allows for routing within and across plugin extensions
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
#### `router` API
|
||||
This extension can then be imported and used in the app as follow, typically
|
||||
placed within the top-level `<FlatRoutes>`:
|
||||
|
||||
```typescript
|
||||
addRoute(
|
||||
target: RouteRef,
|
||||
Component: ComponentType<any>,
|
||||
options?: RouteOptions,
|
||||
): void;
|
||||
```tsx
|
||||
<Route route="/any-path" element={<ExamplePage />} />
|
||||
```
|
||||
|
||||
@@ -28,6 +28,7 @@ new-plugin/
|
||||
index.ts
|
||||
plugin.test.ts
|
||||
plugin.ts
|
||||
routes.ts
|
||||
jest.config.js
|
||||
jest.setup.ts
|
||||
package.json
|
||||
@@ -56,26 +57,30 @@ package.json to declare the plugin dependencies, metadata and scripts.
|
||||
In the `src` folder we get to the interesting bits. Check out the `plugin.ts`:
|
||||
|
||||
```jsx
|
||||
import { createPlugin, createRouteRef } from '@backstage/core';
|
||||
import ExampleComponent from './components/ExampleComponent';
|
||||
import { createPlugin, createRoutableExtension } from '@backstage/core';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
path: '/new-plugin',
|
||||
title: 'New plugin',
|
||||
});
|
||||
import { rootRouteRef } from './routes';
|
||||
|
||||
export const plugin = createPlugin({
|
||||
id: 'new-plugin',
|
||||
register({ router }) {
|
||||
router.addRoute(rootRouteRef, ExampleComponent);
|
||||
export const examplePlugin = createPlugin({
|
||||
id: 'example',
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const ExamplePage = examplePlugin.provide(
|
||||
createRoutableExtension({
|
||||
component: () =>
|
||||
import('./components/ExampleComponent').then(m => m.ExampleComponent),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
This is where the plugin is created and where it hooks into the app by declaring
|
||||
what component should be shown on what URL. See reference docs for
|
||||
[createPlugin](../reference/createPlugin.md) or
|
||||
[router](../reference/createPlugin-router.md).
|
||||
This is where the plugin is created and where it creates and exports extensions
|
||||
that can be imported and used the app. See reference docs for
|
||||
[createPlugin](../reference/createPlugin.md) or introduction to the new
|
||||
[Composability System](./composability.md).
|
||||
|
||||
## Components
|
||||
|
||||
@@ -91,12 +96,15 @@ You may tweak these components, rename them and/or replace them completely.
|
||||
|
||||
## Connecting the plugin to the Backstage app
|
||||
|
||||
There are two things needed for a Backstage app to start making use of a plugin.
|
||||
There are three things needed for a Backstage app to start making use of a
|
||||
plugin.
|
||||
|
||||
1. Add plugin as dependency in `app/package.json`
|
||||
2. `import` plugin in `app/src/plugins.ts`
|
||||
3. Import and use one or more plugin extensions, for example in
|
||||
`app/src/App.tsx`.
|
||||
|
||||
Luckily these two steps happen automatically when you create a plugin with the
|
||||
Luckily these three steps happen automatically when you create a plugin with the
|
||||
Backstage CLI.
|
||||
|
||||
## Talking to the outside world
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
id: createPlugin-router
|
||||
title: createPlugin - router
|
||||
description: Documentation on createPlugin - router
|
||||
---
|
||||
|
||||
The router that is passed to the `register` function makes it possible for
|
||||
plugins to hook into routing of the Backstage app and provide the end users with
|
||||
new views to navigate to. This is done by utilising the following methods on the
|
||||
`router`:
|
||||
|
||||
```typescript
|
||||
addRoute(
|
||||
target: RouteRef,
|
||||
Component: ComponentType<any>,
|
||||
options?: RouteOptions,
|
||||
): void;
|
||||
```
|
||||
|
||||
## RouteRef
|
||||
|
||||
`addRoute` method is using mutable RouteRefs, which can be created as following:
|
||||
|
||||
```ts
|
||||
import { createRouteRef } from '@backstage/core';
|
||||
|
||||
const myPluginRouteRef = createRouteRef({
|
||||
path: '/my-plugin',
|
||||
title: 'My Plugin',
|
||||
});
|
||||
```
|
||||
@@ -17,32 +17,24 @@ type PluginConfig = {
|
||||
};
|
||||
|
||||
type PluginHooks = {
|
||||
router: RouterHooks;
|
||||
featureFlags: FeatureFlagsHooks;
|
||||
};
|
||||
```
|
||||
|
||||
- [Read more about the router here](createPlugin-router.md)
|
||||
- [Read more about feature flags here](createPlugin-feature-flags.md)
|
||||
|
||||
## Example Uses
|
||||
|
||||
### Creating a basic plugin
|
||||
|
||||
Showcasing adding a route and a feature flag.
|
||||
Showcasing adding a feature flag.
|
||||
|
||||
```jsx
|
||||
import { createPlugin, createRouteRef } from '@backstage/core';
|
||||
import ExampleComponent from './components/ExampleComponent';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
path: '/new-plugin',
|
||||
title: 'New Plugin',
|
||||
});
|
||||
import { createPlugin } from '@backstage/core';
|
||||
|
||||
export default createPlugin({
|
||||
id: 'new-plugin',
|
||||
register({ router, featureFlags }) {
|
||||
router.addRoute(rootRouteRef, ExampleComponent);
|
||||
featureFlags.register('enable-example-component');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,6 +20,8 @@ import chalk from 'chalk';
|
||||
import inquirer, { Answers, Question } from 'inquirer';
|
||||
import { exec as execCb } from 'child_process';
|
||||
import { resolve as resolvePath, join as joinPath } from 'path';
|
||||
import camelCase from 'lodash/camelCase';
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import os from 'os';
|
||||
import { Command } from 'commander';
|
||||
import {
|
||||
@@ -104,16 +106,12 @@ export async function addPluginDependencyToApp(
|
||||
});
|
||||
}
|
||||
|
||||
export async function addPluginToApp(
|
||||
export async function addPluginImportToApp(
|
||||
rootDir: string,
|
||||
pluginName: string,
|
||||
pluginVar: string,
|
||||
pluginPackage: string,
|
||||
) {
|
||||
const pluginNameCapitalized = pluginName
|
||||
.split('-')
|
||||
.map(name => capitalize(name))
|
||||
.join('');
|
||||
const pluginExport = `export { plugin as ${pluginNameCapitalized} } from '${pluginPackage}';`;
|
||||
const pluginExport = `export { ${pluginVar} } from '${pluginPackage}';`;
|
||||
const pluginsFilePath = 'packages/app/src/plugins.ts';
|
||||
const pluginsFile = resolvePath(rootDir, pluginsFilePath);
|
||||
|
||||
@@ -126,6 +124,46 @@ export async function addPluginToApp(
|
||||
});
|
||||
}
|
||||
|
||||
export async function addPluginExtensionToApp(
|
||||
pluginId: string,
|
||||
extensionName: string,
|
||||
pluginPackage: string,
|
||||
) {
|
||||
const pluginsFilePath = paths.resolveTargetRoot('packages/app/src/App.tsx');
|
||||
if (!(await fs.pathExists(pluginsFilePath))) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.forItem('processing', pluginsFilePath, async () => {
|
||||
const content = await fs.readFile(pluginsFilePath, 'utf8');
|
||||
const revLines = content.split('\n').reverse();
|
||||
|
||||
const lastImportIndex = revLines.findIndex(line =>
|
||||
line.match(/ from ("|').*("|')/),
|
||||
);
|
||||
const lastRouteIndex = revLines.findIndex(line =>
|
||||
line.match(/<\/FlatRoutes/),
|
||||
);
|
||||
|
||||
if (lastImportIndex !== -1 && lastRouteIndex !== -1) {
|
||||
revLines.splice(
|
||||
lastImportIndex,
|
||||
0,
|
||||
`import { ${extensionName} } from '${pluginPackage}';`,
|
||||
);
|
||||
const [indentation] = revLines[lastRouteIndex + 1].match(/^\s*/) ?? [];
|
||||
revLines.splice(
|
||||
lastRouteIndex + 1,
|
||||
0,
|
||||
`${indentation}<Route path="/${pluginId}" element={<${extensionName} />}/>`,
|
||||
);
|
||||
|
||||
const newContent = revLines.reverse().join('\n');
|
||||
await fs.writeFile(pluginsFilePath, newContent, 'utf8');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function cleanUp(tempDir: string) {
|
||||
await Task.forItem('remove', 'temporary directory', async () => {
|
||||
await fs.remove(tempDir);
|
||||
@@ -223,6 +261,8 @@ export default async (cmd: Command) => {
|
||||
const name = cmd.scope
|
||||
? `@${cmd.scope.replace(/^@/, '')}/plugin-${pluginId}`
|
||||
: `plugin-${pluginId}`;
|
||||
const pluginVar = `${camelCase(answers.id)}Plugin`;
|
||||
const extensionName = `${upperFirst(camelCase(answers.id))}Page`;
|
||||
const npmRegistry = cmd.npmRegistry && cmd.scope ? cmd.npmRegistry : '';
|
||||
const privatePackage = cmd.private === false ? false : true;
|
||||
const isMonoRepo = await fs.pathExists(paths.resolveTargetRoot('lerna.json'));
|
||||
@@ -259,7 +299,9 @@ export default async (cmd: Command) => {
|
||||
tempDir,
|
||||
{
|
||||
...answers,
|
||||
pluginVar,
|
||||
pluginVersion,
|
||||
extensionName,
|
||||
name,
|
||||
privatePackage,
|
||||
npmRegistry,
|
||||
@@ -278,7 +320,8 @@ export default async (cmd: Command) => {
|
||||
await addPluginDependencyToApp(paths.targetRoot, name, pluginVersion);
|
||||
|
||||
Task.section('Import plugin in app');
|
||||
await addPluginToApp(paths.targetRoot, pluginId, name);
|
||||
await addPluginImportToApp(paths.targetRoot, pluginVar, name);
|
||||
await addPluginExtensionToApp(pluginId, extensionName, name);
|
||||
}
|
||||
|
||||
if (ownerIds && ownerIds.length) {
|
||||
|
||||
@@ -39,6 +39,11 @@ const fileHandlers = [
|
||||
patterns: ['package.json'],
|
||||
handler: handlers.packageJson,
|
||||
},
|
||||
{
|
||||
// Not all plugins have routes
|
||||
patterns: ['src/routes.ts'],
|
||||
handler: handlers.skip,
|
||||
},
|
||||
{
|
||||
// make sure files in 1st level of src/ and dev/ exist
|
||||
patterns: ['.eslintrc.js', /^(src|dev)\/[^/]+$/],
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { plugin } from '../src/plugin';
|
||||
|
||||
createDevApp().registerPlugin(plugin).render();
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { {{ pluginVar }}, {{ extensionName }} } from '../src/plugin';
|
||||
|
||||
createDevApp()
|
||||
.registerPlugin({{ pluginVar }})
|
||||
.addPage({
|
||||
element: <{{ extensionName }} />,
|
||||
title: 'Root Page',
|
||||
})
|
||||
.render();
|
||||
+7
-6
@@ -1,13 +1,12 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import ExampleComponent from './ExampleComponent';
|
||||
import { ExampleComponent } from './ExampleComponent';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
|
||||
|
||||
describe('ExampleComponent', () => {
|
||||
const server = setupServer();
|
||||
// Enable sane handlers for network requests
|
||||
@@ -15,15 +14,17 @@ describe('ExampleComponent', () => {
|
||||
|
||||
// setup mock response
|
||||
beforeEach(() => {
|
||||
server.use(rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))))
|
||||
})
|
||||
server.use(
|
||||
rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))),
|
||||
);
|
||||
});
|
||||
|
||||
it('should render', () => {
|
||||
const rendered = render(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ExampleComponent />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
expect(rendered.getByText('Welcome to {{ id }}!')).toBeInTheDocument();
|
||||
);
|
||||
expect(rendered.getByText('Welcome to {{ id }}!')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+2
-4
@@ -9,9 +9,9 @@ import {
|
||||
HeaderLabel,
|
||||
SupportButton,
|
||||
} from '@backstage/core';
|
||||
import ExampleFetchComponent from '../ExampleFetchComponent';
|
||||
import { ExampleFetchComponent } from '../ExampleFetchComponent';
|
||||
|
||||
const ExampleComponent = () => (
|
||||
export const ExampleComponent = () => (
|
||||
<Page themeId="tool">
|
||||
<Header title="Welcome to {{ id }}!" subtitle="Optional subtitle">
|
||||
<HeaderLabel label="Owner" value="Team X" />
|
||||
@@ -36,5 +36,3 @@ const ExampleComponent = () => (
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
|
||||
export default ExampleComponent;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default } from './ExampleComponent';
|
||||
export { ExampleComponent } from './ExampleComponent';
|
||||
|
||||
+8
-4
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import ExampleFetchComponent from './ExampleFetchComponent';
|
||||
import { ExampleFetchComponent } from './ExampleFetchComponent';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
@@ -9,11 +9,15 @@ describe('ExampleFetchComponent', () => {
|
||||
const server = setupServer();
|
||||
// Enable sane handlers for network requests
|
||||
msw.setupDefaultHandlers(server);
|
||||
|
||||
|
||||
// setup mock response
|
||||
beforeEach(() => {
|
||||
server.use(rest.get('https://randomuser.me/*', (_, res, ctx) => res(ctx.status(200), ctx.delay(2000), ctx.json({}))))
|
||||
})
|
||||
server.use(
|
||||
rest.get('https://randomuser.me/*', (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.delay(2000), ctx.json({})),
|
||||
),
|
||||
);
|
||||
});
|
||||
it('should render', async () => {
|
||||
const rendered = render(<ExampleFetchComponent />);
|
||||
expect(await rendered.findByTestId('progress')).toBeInTheDocument();
|
||||
|
||||
+2
-4
@@ -48,7 +48,7 @@ export const DenseTable = ({ users }: DenseTableProps) => {
|
||||
{ title: 'Nationality', field: 'nationality' },
|
||||
];
|
||||
|
||||
const data = users.map((user) => {
|
||||
const data = users.map(user => {
|
||||
return {
|
||||
avatar: (
|
||||
<img
|
||||
@@ -73,7 +73,7 @@ export const DenseTable = ({ users }: DenseTableProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ExampleFetchComponent = () => {
|
||||
export const ExampleFetchComponent = () => {
|
||||
const { value, loading, error } = useAsync(async (): Promise<User[]> => {
|
||||
const response = await fetch('https://randomuser.me/api/?results=20');
|
||||
const data = await response.json();
|
||||
@@ -88,5 +88,3 @@ const ExampleFetchComponent = () => {
|
||||
|
||||
return <DenseTable users={value || []} />;
|
||||
};
|
||||
|
||||
export default ExampleFetchComponent;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default } from './ExampleFetchComponent';
|
||||
export { ExampleFetchComponent } from './ExampleFetchComponent';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { plugin } from './plugin';
|
||||
@@ -0,0 +1 @@
|
||||
export { {{ pluginVar }}, {{ extensionName }} } from './plugin';
|
||||
@@ -1,7 +1,7 @@
|
||||
import { plugin } from './plugin';
|
||||
import { {{ pluginVar }} } from './plugin';
|
||||
|
||||
describe('{{ id }}', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(plugin).toBeDefined();
|
||||
expect({{ pluginVar }}).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { createPlugin, createRouteRef } from '@backstage/core';
|
||||
import ExampleComponent from './components/ExampleComponent';
|
||||
import { createPlugin, createRoutableExtension } from '@backstage/core';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
path: '/{{ id }}',
|
||||
title: '{{ id }}',
|
||||
});
|
||||
import { rootRouteRef } from './routes';
|
||||
|
||||
export const plugin = createPlugin({
|
||||
export const {{ pluginVar }} = createPlugin({
|
||||
id: '{{ id }}',
|
||||
register({ router }) {
|
||||
router.addRoute(rootRouteRef, ExampleComponent);
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const {{ extensionName }} = {{ pluginVar }}.provide(
|
||||
createRoutableExtension({
|
||||
component: () =>
|
||||
import('./components/ExampleComponent').then(m => m.ExampleComponent),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { createRouteRef } from '@backstage/core';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
title: '{{ id }}',
|
||||
});
|
||||
Reference in New Issue
Block a user