diff --git a/.changeset/cyan-kiwis-suffer.md b/.changeset/cyan-kiwis-suffer.md
new file mode 100644
index 0000000000..b59b0e9a32
--- /dev/null
+++ b/.changeset/cyan-kiwis-suffer.md
@@ -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.
diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts
index a2005c5580..c9f1664411 100644
--- a/packages/cli/src/commands/create-plugin/createPlugin.ts
+++ b/packages/cli/src/commands/create-plugin/createPlugin.ts
@@ -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) {
diff --git a/packages/cli/templates/default-plugin/dev/index.tsx b/packages/cli/templates/default-plugin/dev/index.tsx
deleted file mode 100644
index 6fce113093..0000000000
--- a/packages/cli/templates/default-plugin/dev/index.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-import { createDevApp } from '@backstage/dev-utils';
-import { plugin } from '../src/plugin';
-
-createDevApp().registerPlugin(plugin).render();
diff --git a/packages/cli/templates/default-plugin/dev/index.tsx.hbs b/packages/cli/templates/default-plugin/dev/index.tsx.hbs
new file mode 100644
index 0000000000..13705ecd58
--- /dev/null
+++ b/packages/cli/templates/default-plugin/dev/index.tsx.hbs
@@ -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: ,
+ title: 'Root Page',
+ })
+ .render();
diff --git a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs
index e805900f36..e6eab17493 100644
--- a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs
+++ b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs
@@ -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(
,
- );
- expect(rendered.getByText('Welcome to {{ id }}!')).toBeInTheDocument();
+ );
+ expect(rendered.getByText('Welcome to {{ id }}!')).toBeInTheDocument();
});
});
diff --git a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs
index 5f90f2de1e..dcecebdf46 100644
--- a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs
+++ b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs
@@ -9,9 +9,9 @@ import {
HeaderLabel,
SupportButton,
} from '@backstage/core';
-import ExampleFetchComponent from '../ExampleFetchComponent';
+import { ExampleFetchComponent } from '../ExampleFetchComponent';
-const ExampleComponent = () => (
+export const ExampleComponent = () => (
@@ -36,5 +36,3 @@ const ExampleComponent = () => (
);
-
-export default ExampleComponent;
diff --git a/packages/cli/templates/default-plugin/src/components/ExampleComponent/index.ts b/packages/cli/templates/default-plugin/src/components/ExampleComponent/index.ts
index 520a3bf553..8b8437521b 100644
--- a/packages/cli/templates/default-plugin/src/components/ExampleComponent/index.ts
+++ b/packages/cli/templates/default-plugin/src/components/ExampleComponent/index.ts
@@ -1 +1 @@
-export { default } from './ExampleComponent';
+export { ExampleComponent } from './ExampleComponent';
diff --git a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs
index 81e1b4be09..fa1e289d8e 100644
--- a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs
+++ b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs
@@ -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();
expect(await rendered.findByTestId('progress')).toBeInTheDocument();
diff --git a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs
index 8cc5ed2ab7..20dd6d1f57 100644
--- a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs
+++ b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs
@@ -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: (
{
);
};
-const ExampleFetchComponent = () => {
+export const ExampleFetchComponent = () => {
const { value, loading, error } = useAsync(async (): Promise => {
const response = await fetch('https://randomuser.me/api/?results=20');
const data = await response.json();
@@ -88,5 +88,3 @@ const ExampleFetchComponent = () => {
return ;
};
-
-export default ExampleFetchComponent;
diff --git a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/index.ts b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/index.ts
index 3e53453948..41a43e84f1 100644
--- a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/index.ts
+++ b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/index.ts
@@ -1 +1 @@
-export { default } from './ExampleFetchComponent';
+export { ExampleFetchComponent } from './ExampleFetchComponent';
diff --git a/packages/cli/templates/default-plugin/src/index.ts b/packages/cli/templates/default-plugin/src/index.ts
deleted file mode 100644
index 99edba26c3..0000000000
--- a/packages/cli/templates/default-plugin/src/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { plugin } from './plugin';
diff --git a/packages/cli/templates/default-plugin/src/index.ts.hbs b/packages/cli/templates/default-plugin/src/index.ts.hbs
new file mode 100644
index 0000000000..165c8419e1
--- /dev/null
+++ b/packages/cli/templates/default-plugin/src/index.ts.hbs
@@ -0,0 +1 @@
+export { {{ pluginVar }} } from './plugin';
diff --git a/packages/cli/templates/default-plugin/src/plugin.test.ts.hbs b/packages/cli/templates/default-plugin/src/plugin.test.ts.hbs
index c2be0a5301..9d44a9c497 100644
--- a/packages/cli/templates/default-plugin/src/plugin.test.ts.hbs
+++ b/packages/cli/templates/default-plugin/src/plugin.test.ts.hbs
@@ -1,7 +1,7 @@
-import { plugin } from './plugin';
+import { {{ pluginVar }} } from './plugin';
describe('{{ id }}', () => {
it('should export plugin', () => {
- expect(plugin).toBeDefined();
+ expect({{ pluginVar }}).toBeDefined();
});
});
diff --git a/packages/cli/templates/default-plugin/src/plugin.ts.hbs b/packages/cli/templates/default-plugin/src/plugin.ts.hbs
index 1bf4d07cdb..2a836592f7 100644
--- a/packages/cli/templates/default-plugin/src/plugin.ts.hbs
+++ b/packages/cli/templates/default-plugin/src/plugin.ts.hbs
@@ -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,
+ }),
+);
diff --git a/packages/cli/templates/default-plugin/src/routes.ts.hbs b/packages/cli/templates/default-plugin/src/routes.ts.hbs
new file mode 100644
index 0000000000..b2afba074d
--- /dev/null
+++ b/packages/cli/templates/default-plugin/src/routes.ts.hbs
@@ -0,0 +1,5 @@
+import { createRouteRef } from '@backstage/core';
+
+export const rootRouteRef = createRouteRef({
+ title: '{{ id }}',
+});