diff --git a/.changeset/fruity-rockets-rhyme.md b/.changeset/fruity-rockets-rhyme.md
new file mode 100644
index 0000000000..13e957c5f1
--- /dev/null
+++ b/.changeset/fruity-rockets-rhyme.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-defaults': minor
+---
+
+**BREAKING**: Removed the deprecated `CreateAppFeatureLoader` and support for it in other APIs. Switch existing usage to use the newer `createFrontendFeatureLoader` from `@backstage/frontend-plugin-api` instead.
diff --git a/packages/frontend-defaults/report.api.md b/packages/frontend-defaults/report.api.md
index a5fe3e5f07..37c770074e 100644
--- a/packages/frontend-defaults/report.api.md
+++ b/packages/frontend-defaults/report.api.md
@@ -18,14 +18,6 @@ export function createApp(options?: CreateAppOptions): {
createRoot(): JSX_2.Element;
};
-// @public @deprecated
-export interface CreateAppFeatureLoader {
- getLoaderName(): string;
- load(options: { config: ConfigApi }): Promise<{
- features: FrontendFeature[];
- }>;
-}
-
// @public
export interface CreateAppOptions {
// (undocumented)
@@ -39,11 +31,7 @@ export interface CreateAppOptions {
| ExtensionFactoryMiddleware
| ExtensionFactoryMiddleware[];
// (undocumented)
- features?: (
- | FrontendFeature
- | FrontendFeatureLoader
- | CreateAppFeatureLoader
- )[];
+ features?: (FrontendFeature | FrontendFeatureLoader)[];
// (undocumented)
flags?: {
allowUnknownExtensionConfig?: boolean;
@@ -66,11 +54,7 @@ export function discoverAvailableFeatures(config: Config): {
// @public (undocumented)
export function resolveAsyncFeatures(options: {
config: Config;
- features?: (
- | FrontendFeature
- | FrontendFeatureLoader
- | CreateAppFeatureLoader
- )[];
+ features?: (FrontendFeature | FrontendFeatureLoader)[];
}): Promise<{
features: FrontendFeature[];
}>;
diff --git a/packages/frontend-defaults/src/createApp.test.tsx b/packages/frontend-defaults/src/createApp.test.tsx
index 1d96c22ad0..5bf8356c08 100644
--- a/packages/frontend-defaults/src/createApp.test.tsx
+++ b/packages/frontend-defaults/src/createApp.test.tsx
@@ -21,13 +21,14 @@ import {
createExtension,
PageBlueprint,
createFrontendPlugin,
+ createFrontendFeatureLoader,
ThemeBlueprint,
createFrontendModule,
useAppNode,
FrontendPluginInfo,
} from '@backstage/frontend-plugin-api';
import { screen, waitFor } from '@testing-library/react';
-import { CreateAppFeatureLoader, createApp } from './createApp';
+import { createApp } from './createApp';
import { mockApis, renderWithEffects } from '@backstage/test-utils';
import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api';
import { default as appPluginOriginal } from '@backstage/plugin-app';
@@ -169,28 +170,21 @@ describe('createApp', () => {
});
it('should support feature loaders', async () => {
- const loader: CreateAppFeatureLoader = {
- getLoaderName() {
- return 'test-loader';
- },
- async load({ config }) {
- return {
- features: [
- createFrontendPlugin({
- pluginId: 'test',
- extensions: [
- PageBlueprint.make({
- params: {
- defaultPath: '/',
- loader: async () =>
{config.getString('key')}
,
- },
- }),
- ],
+ const loader = createFrontendFeatureLoader({
+ async *loader({ config }) {
+ yield createFrontendPlugin({
+ pluginId: 'test',
+ extensions: [
+ PageBlueprint.make({
+ params: {
+ defaultPath: '/',
+ loader: async () => {config.getString('key')}
,
+ },
}),
],
- };
+ });
},
- };
+ });
const app = createApp({
configLoader: async () => ({
@@ -207,14 +201,11 @@ describe('createApp', () => {
});
it('should propagate errors thrown by feature loaders', async () => {
- const loader: CreateAppFeatureLoader = {
- getLoaderName() {
- return 'test-loader';
- },
- async load() {
+ const loader = createFrontendFeatureLoader({
+ async loader() {
throw new TypeError('boom');
},
- };
+ });
const app = createApp({
configLoader: async () => ({
@@ -223,10 +214,8 @@ describe('createApp', () => {
features: [loader],
});
- await expect(
- renderWithEffects(app.createRoot()),
- ).rejects.toThrowErrorMatchingInlineSnapshot(
- `"Failed to read frontend features from loader 'test-loader', TypeError: boom"`,
+ await expect(renderWithEffects(app.createRoot())).rejects.toThrow(
+ /Failed to read frontend features from loader created at '.*\/createApp\.test\.tsx:\d+:\d+': TypeError: boom/,
);
});
diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx
index ab0ddf85a7..e6702a4fcc 100644
--- a/packages/frontend-defaults/src/createApp.tsx
+++ b/packages/frontend-defaults/src/createApp.tsx
@@ -36,37 +36,13 @@ import appPlugin from '@backstage/plugin-app';
import { discoverAvailableFeatures } from './discovery';
import { resolveAsyncFeatures } from './resolution';
-/**
- * A source of dynamically loaded frontend features.
- *
- * @public
- * @deprecated Use the {@link @backstage/frontend-plugin-api#createFrontendFeatureLoader} function instead.
- */
-export interface CreateAppFeatureLoader {
- /**
- * Returns name of this loader. suitable for showing to users.
- */
- getLoaderName(): string;
-
- /**
- * Loads a number of features dynamically.
- */
- load(options: { config: ConfigApi }): Promise<{
- features: FrontendFeature[];
- }>;
-}
-
/**
* Options for {@link createApp}.
*
* @public
*/
export interface CreateAppOptions {
- features?: (
- | FrontendFeature
- | FrontendFeatureLoader
- | CreateAppFeatureLoader
- )[];
+ features?: (FrontendFeature | FrontendFeatureLoader)[];
configLoader?: () => Promise<{ config: ConfigApi }>;
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
/**
diff --git a/packages/frontend-defaults/src/index.ts b/packages/frontend-defaults/src/index.ts
index 573cc9b08c..68d4c72568 100644
--- a/packages/frontend-defaults/src/index.ts
+++ b/packages/frontend-defaults/src/index.ts
@@ -20,11 +20,7 @@
* @packageDocumentation
*/
-export {
- createApp,
- type CreateAppOptions,
- type CreateAppFeatureLoader,
-} from './createApp';
+export { createApp, type CreateAppOptions } from './createApp';
export { createPublicSignInApp } from './createPublicSignInApp';
export { discoverAvailableFeatures } from './discovery';
export { resolveAsyncFeatures } from './resolution';
diff --git a/packages/frontend-defaults/src/resolution.test.ts b/packages/frontend-defaults/src/resolution.test.ts
index b39e302b24..991cb2d827 100644
--- a/packages/frontend-defaults/src/resolution.test.ts
+++ b/packages/frontend-defaults/src/resolution.test.ts
@@ -20,7 +20,6 @@ import {
FrontendFeatureLoader,
PageBlueprint,
} from '@backstage/frontend-plugin-api';
-import { CreateAppFeatureLoader } from './createApp';
import { resolveAsyncFeatures } from './resolution';
import { mockApis } from '@backstage/test-utils';
@@ -71,75 +70,6 @@ describe('resolveAsyncFeatures', () => {
]);
});
- it('supports deprecated feature loaders', async () => {
- const loader: CreateAppFeatureLoader = {
- getLoaderName() {
- return 'test-loader';
- },
- async load() {
- return {
- features: [
- createFrontendPlugin({
- pluginId: 'test',
- extensions: [
- PageBlueprint.make({
- params: {
- defaultPath: '/',
- loader: () => new Promise(() => {}),
- },
- }),
- ],
- }),
- ],
- };
- },
- };
-
- const { features } = await resolveAsyncFeatures({
- config: mockApis.config(),
- features: [loader],
- });
-
- expect(features).toMatchObject([
- {
- $$type: '@backstage/FrontendPlugin',
- id: 'test',
- version: 'v1',
- extensions: [
- {
- $$type: '@backstage/Extension',
- id: 'page:test',
- version: 'v2',
- attachTo: {
- id: 'app/routes',
- input: 'routes',
- },
- },
- ],
- },
- ]);
- });
-
- it('should propagate errors thrown by deprecated feature loaders', async () => {
- const loader: CreateAppFeatureLoader = {
- getLoaderName() {
- return 'test-loader';
- },
- async load() {
- throw new TypeError('boom');
- },
- };
-
- await expect(() =>
- resolveAsyncFeatures({
- config: mockApis.config(),
- features: [loader],
- }),
- ).rejects.toThrowErrorMatchingInlineSnapshot(
- `"Failed to read frontend features from loader 'test-loader', TypeError: boom"`,
- );
- });
-
it('supports feature loaders', async () => {
const loader: FrontendFeatureLoader = createFrontendFeatureLoader({
async loader({ config: _ }) {
diff --git a/packages/frontend-defaults/src/resolution.ts b/packages/frontend-defaults/src/resolution.ts
index 6ab26c3d1d..58f91c45be 100644
--- a/packages/frontend-defaults/src/resolution.ts
+++ b/packages/frontend-defaults/src/resolution.ts
@@ -20,40 +20,14 @@ import {
FrontendFeature,
FrontendFeatureLoader,
} from '@backstage/frontend-plugin-api';
-import { CreateAppFeatureLoader } from './createApp';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { isInternalFrontendFeatureLoader } from '../../frontend-plugin-api/src/wiring/createFrontendFeatureLoader';
/** @public */
export async function resolveAsyncFeatures(options: {
config: Config;
- features?: (
- | FrontendFeature
- | FrontendFeatureLoader
- | CreateAppFeatureLoader
- )[];
+ features?: (FrontendFeature | FrontendFeatureLoader)[];
}): Promise<{ features: FrontendFeature[] }> {
- const features: (FrontendFeature | FrontendFeatureLoader)[] = [];
-
- // Separate deprecated CreateAppFeatureLoader elements from the frontend features,
- // and manage the deprecated elements first.
- for (const item of options?.features ?? []) {
- if ('load' in item) {
- try {
- const result = await item.load({ config: options.config });
- features.push(...result.features);
- } catch (e) {
- throw new Error(
- `Failed to read frontend features from loader '${item.getLoaderName()}', ${stringifyError(
- e,
- )}`,
- );
- }
- } else {
- features.push(item);
- }
- }
-
const loadedFeatures: FrontendFeature[] = [];
const alreadyMetFeatureLoaders: FrontendFeatureLoader[] = [];
const maxRecursionDepth = 5;
@@ -96,7 +70,7 @@ export async function resolveAsyncFeatures(options: {
}
}
- await applyFeatureLoaders(features, 1);
+ await applyFeatureLoaders(options.features ?? [], 1);
return { features: loadedFeatures };
}