cli: update frontend plugin template to use new extension API

This commit is contained in:
Patrik Oldsberg
2020-12-16 22:41:41 +01:00
parent 8eb862c58e
commit 398e1f83ee
15 changed files with 64 additions and 43 deletions
+5
View File
@@ -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.
@@ -20,6 +20,7 @@ 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 os from 'os';
import { Command } from 'commander';
import {
@@ -106,14 +107,10 @@ export async function addPluginDependencyToApp(
export async function addPluginToApp(
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);
@@ -223,6 +220,7 @@ export default async (cmd: Command) => {
const name = cmd.scope
? `@${cmd.scope.replace(/^@/, '')}/plugin-${pluginId}`
: `plugin-${pluginId}`;
const pluginVar = `${camelCase(answers.id)}Plugin`;
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,6 +257,7 @@ export default async (cmd: Command) => {
tempDir,
{
...answers,
pluginVar,
pluginVersion,
name,
privatePackage,
@@ -278,7 +277,7 @@ export default async (cmd: Command) => {
await addPluginDependencyToApp(paths.targetRoot, name, pluginVersion);
Task.section('Import plugin in app');
await addPluginToApp(paths.targetRoot, pluginId, name);
await addPluginToApp(paths.targetRoot, pluginVar, name);
}
if (ownerIds && ownerIds.length) {
@@ -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 }}, ExamplePage } from '../src/plugin';
createDevApp()
.registerPlugin({{ pluginVar }})
.addPage({
element: <ExamplePage />,
title: 'Root Page',
})
.render();
@@ -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();
});
});
@@ -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';
@@ -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();
@@ -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 }} } 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 ExamplePage = {{ 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 }}',
});