diff --git a/packages/core-api/src/app/App.test.tsx b/packages/core-api/src/app/App.test.tsx
index 4050343bbf..8dd40d52b0 100644
--- a/packages/core-api/src/app/App.test.tsx
+++ b/packages/core-api/src/app/App.test.tsx
@@ -66,23 +66,24 @@ describe('Integration Test', () => {
const HiddenComponent = plugin2.provide(
createRoutableExtension({
- component: (_: { path?: string }) =>
,
+ component: () => Promise.resolve((_: { path?: string }) => ),
mountPoint: plugin2RouteRef,
}),
);
const ExposedComponent = plugin1.provide(
createRoutableExtension({
- component: (_: PropsWithChildren<{ path?: string }>) => {
- // eslint-disable-next-line react-hooks/rules-of-hooks
- const routeRefFunction = useRouteRef(externalRouteRef);
- return Our Route Is: {routeRefFunction({})}
;
- },
+ component: () =>
+ Promise.resolve((_: PropsWithChildren<{ path?: string }>) => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const routeRefFunction = useRouteRef(externalRouteRef);
+ return Our Route Is: {routeRefFunction({})}
;
+ }),
mountPoint: plugin1RouteRef,
}),
);
- it('runs happy path', () => {
+ it('runs happy path', async () => {
const components = {
NotFoundErrorPage: () => null,
BootErrorPage: () => null,
@@ -112,7 +113,7 @@ describe('Integration Test', () => {
const Provider = app.getProvider();
const Router = app.getRouter();
- renderWithEffects(
+ await renderWithEffects(
diff --git a/packages/core-api/src/extensions/extensions.test.tsx b/packages/core-api/src/extensions/extensions.test.tsx
index 861045fb3d..26755b3bcf 100644
--- a/packages/core-api/src/extensions/extensions.test.tsx
+++ b/packages/core-api/src/extensions/extensions.test.tsx
@@ -33,7 +33,9 @@ describe('extensions', () => {
const Component = () => ;
const extension = createReactExtension({
- component: Component,
+ component: {
+ sync: Component,
+ },
data: {
myData: { foo: 'bar' },
},
@@ -51,11 +53,13 @@ describe('extensions', () => {
const routeRef = createRouteRef({ path: '/foo', title: 'Foo' });
const extension1 = createComponentExtension({
- component: Component,
+ component: {
+ sync: Component,
+ },
});
const extension2 = createRoutableExtension({
- component: Component,
+ component: () => Promise.resolve(Component),
mountPoint: routeRef,
});
diff --git a/packages/core-api/src/extensions/extensions.tsx b/packages/core-api/src/extensions/extensions.tsx
index aca29e87dc..adec690f20 100644
--- a/packages/core-api/src/extensions/extensions.tsx
+++ b/packages/core-api/src/extensions/extensions.tsx
@@ -14,17 +14,30 @@
* limitations under the License.
*/
-import React from 'react';
+import React, { lazy, Suspense } from 'react';
import { RouteRef } from '../routing';
import { attachComponentData } from './componentData';
import { Extension, BackstagePlugin } from '../plugin/types';
+type ComponentLoader =
+ | {
+ lazy: () => Promise;
+ }
+ | {
+ sync: T;
+ };
+
export function createRoutableExtension<
T extends (props: any) => JSX.Element
->(options: { component: T; mountPoint: RouteRef }): Extension {
+>(options: {
+ component: () => Promise;
+ mountPoint: RouteRef;
+}): Extension {
const { component, mountPoint } = options;
return createReactExtension({
- component,
+ component: {
+ lazy: component,
+ },
data: {
'core.mountPoint': mountPoint,
},
@@ -33,32 +46,47 @@ export function createRoutableExtension<
export function createComponentExtension<
T extends (props: any) => JSX.Element
->(options: { component: T }): Extension {
+>(options: { component: ComponentLoader }): Extension {
const { component } = options;
return createReactExtension({ component });
}
export function createReactExtension<
T extends (props: any) => JSX.Element
->(options: { component: T; data?: Record }): Extension {
+>(options: {
+ component: ComponentLoader;
+ data?: Record;
+}): Extension {
const { data = {} } = options;
- const Component = options.component as T & {
- displayName?: string;
- };
+
+ let Component: T;
+ if ('lazy' in options.component) {
+ const lazyLoader = options.component.lazy;
+ Component = (lazy(() =>
+ lazyLoader().then(component => ({ default: component })),
+ ) as unknown) as T;
+ } else {
+ Component = options.component.sync;
+ }
+ const componentName =
+ (Component as { displayName?: string }).displayName ||
+ Component.name ||
+ 'Component';
return {
expose(plugin: BackstagePlugin) {
- const Result: any = (props: any) => ;
+ const Result: any = (props: any) => (
+
+
+
+ );
attachComponentData(Result, 'core.plugin', plugin);
for (const [key, value] of Object.entries(data)) {
attachComponentData(Result, key, value);
}
- const name = Component.displayName || Component.name || 'Component';
- if (name) {
- Result.displayName = `Extension(${name})`;
- }
+ Result.displayName = `Extension(${componentName})`;
return Result;
},
};
diff --git a/packages/core-api/src/plugin/collectors.test.tsx b/packages/core-api/src/plugin/collectors.test.tsx
index e5f8a123e3..5baf2539ab 100644
--- a/packages/core-api/src/plugin/collectors.test.tsx
+++ b/packages/core-api/src/plugin/collectors.test.tsx
@@ -42,19 +42,25 @@ const ref1 = createRouteRef(mockConfig());
const ref2 = createRouteRef(mockConfig());
const Extension1 = pluginA.provide(
- createRoutableExtension({ component: MockComponent, mountPoint: ref1 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockComponent),
+ mountPoint: ref1,
+ }),
);
const Extension2 = pluginB.provide(
- createRoutableExtension({ component: MockComponent, mountPoint: ref2 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockComponent),
+ mountPoint: ref2,
+ }),
);
const Extension3 = pluginA.provide(
- createComponentExtension({ component: MockComponent }),
+ createComponentExtension({ component: { sync: MockComponent } }),
);
const Extension4 = pluginB.provide(
- createComponentExtension({ component: MockComponent }),
+ createComponentExtension({ component: { sync: MockComponent } }),
);
const Extension5 = pluginC.provide(
- createComponentExtension({ component: MockComponent }),
+ createComponentExtension({ component: { sync: MockComponent } }),
);
describe('collection', () => {
diff --git a/packages/core-api/src/routing/collectors.test.tsx b/packages/core-api/src/routing/collectors.test.tsx
index e44474565c..200846f175 100644
--- a/packages/core-api/src/routing/collectors.test.tsx
+++ b/packages/core-api/src/routing/collectors.test.tsx
@@ -41,19 +41,34 @@ const ref4 = createRouteRef(mockConfig());
const ref5 = createRouteRef(mockConfig());
const Extension1 = plugin.provide(
- createRoutableExtension({ component: MockComponent, mountPoint: ref1 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockComponent),
+ mountPoint: ref1,
+ }),
);
const Extension2 = plugin.provide(
- createRoutableExtension({ component: MockComponent, mountPoint: ref2 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockComponent),
+ mountPoint: ref2,
+ }),
);
const Extension3 = plugin.provide(
- createRoutableExtension({ component: MockComponent, mountPoint: ref3 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockComponent),
+ mountPoint: ref3,
+ }),
);
const Extension4 = plugin.provide(
- createRoutableExtension({ component: MockComponent, mountPoint: ref4 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockComponent),
+ mountPoint: ref4,
+ }),
);
const Extension5 = plugin.provide(
- createRoutableExtension({ component: MockComponent, mountPoint: ref5 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockComponent),
+ mountPoint: ref5,
+ }),
);
describe('discovery', () => {
@@ -190,6 +205,6 @@ describe('discovery', () => {
routeParents: routeParentCollector,
},
}),
- ).toThrow(`Visited element Extension(MockComponent) twice`);
+ ).toThrow(`Visited element Extension(Component) twice`);
});
});
diff --git a/packages/core-api/src/routing/hooks.test.tsx b/packages/core-api/src/routing/hooks.test.tsx
index a29e263e14..3dbeaf8e3e 100644
--- a/packages/core-api/src/routing/hooks.test.tsx
+++ b/packages/core-api/src/routing/hooks.test.tsx
@@ -85,19 +85,34 @@ const MockRouteSource = (props: {
};
const Extension1 = plugin.provide(
- createRoutableExtension({ component: MockComponent, mountPoint: ref1 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockComponent),
+ mountPoint: ref1,
+ }),
);
const Extension2 = plugin.provide(
- createRoutableExtension({ component: MockRouteSource, mountPoint: ref2 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockRouteSource),
+ mountPoint: ref2,
+ }),
);
const Extension3 = plugin.provide(
- createRoutableExtension({ component: MockComponent, mountPoint: ref3 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockComponent),
+ mountPoint: ref3,
+ }),
);
const Extension4 = plugin.provide(
- createRoutableExtension({ component: MockRouteSource, mountPoint: ref4 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockRouteSource),
+ mountPoint: ref4,
+ }),
);
const Extension5 = plugin.provide(
- createRoutableExtension({ component: MockComponent, mountPoint: ref5 }),
+ createRoutableExtension({
+ component: () => Promise.resolve(MockComponent),
+ mountPoint: ref5,
+ }),
);
function withRoutingProvider(
@@ -127,7 +142,7 @@ function withRoutingProvider(
}
describe('discovery', () => {
- it('should handle simple routeRef path creation for routeRefs used in other parts of the app', () => {
+ it('should handle simple routeRef path creation for routeRefs used in other parts of the app', async () => {
const root = (
@@ -151,7 +166,9 @@ describe('discovery', () => {
]),
);
- expect(rendered.getByText('Path at inside: /foo/bar')).toBeInTheDocument();
+ await expect(
+ rendered.findByText('Path at inside: /foo/bar'),
+ ).resolves.toBeInTheDocument();
expect(
rendered.getByText('Path at insideExternal: /baz'),
).toBeInTheDocument();
@@ -164,7 +181,7 @@ describe('discovery', () => {
).toBeInTheDocument();
});
- it('should handle routeRefs with parameters', () => {
+ it('should handle routeRefs with parameters', async () => {
const root = (
@@ -187,37 +204,39 @@ describe('discovery', () => {
const rendered = render(withRoutingProvider(root));
- expect(
- rendered.getByText('Path at inside: /foo/bar/bleb'),
- ).toBeInTheDocument();
+ await expect(
+ rendered.findByText('Path at inside: /foo/bar/bleb'),
+ ).resolves.toBeInTheDocument();
expect(
rendered.getByText('Path at outside: /foo/bar/blob'),
).toBeInTheDocument();
});
- it('should handle relative routing within parameterized routePaths', () => {
+ it('should handle relative routing within parameterized routePaths', async () => {
const root = (
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
const rendered = render(withRoutingProvider(root));
- expect(
- rendered.getByText('Path at inside: /foo/blob/baz'),
- ).toBeInTheDocument();
+ await expect(
+ rendered.findByText('Path at inside: /foo/blob/baz'),
+ ).resolves.toBeInTheDocument();
});
it('should throw errors for routing to other routeRefs with unsupported parameters', () => {
diff --git a/plugins/graphiql/src/plugin.ts b/plugins/graphiql/src/plugin.ts
index fb4c33760d..a0bb79ed29 100644
--- a/plugins/graphiql/src/plugin.ts
+++ b/plugins/graphiql/src/plugin.ts
@@ -19,7 +19,6 @@ import {
createApiFactory,
createRoutableExtension,
} from '@backstage/core';
-import { GraphiQLPage as Component } from './components';
import { graphQlBrowseApiRef, GraphQLEndpoints } from './lib/api';
import { graphiQLRouteRef } from './route-refs';
@@ -42,7 +41,7 @@ export const plugin = createPlugin({
export const GraphiQLPage = plugin.provide(
createRoutableExtension({
- component: Component,
+ component: () => import('./components').then(m => m.GraphiQLPage),
mountPoint: graphiQLRouteRef,
}),
);