diff --git a/.changeset/metal-garlics-fetch.md b/.changeset/metal-garlics-fetch.md
new file mode 100644
index 0000000000..076b5f34a5
--- /dev/null
+++ b/.changeset/metal-garlics-fetch.md
@@ -0,0 +1,6 @@
+---
+'@backstage/frontend-plugin-api': patch
+'@backstage/frontend-app-api': patch
+---
+
+Moved several implementations of built-in APIs from being hardcoded in the app to instead be provided as API extensions. This moves all API-related inputs from the `app` extension to the respective API extensions. For example, extensions created with `ThemeBlueprint` are now attached to the `themes` input of `api:app-theme` rather than the `app` extension.
diff --git a/.changeset/yellow-bees-hope.md b/.changeset/yellow-bees-hope.md
new file mode 100644
index 0000000000..1ea8690c20
--- /dev/null
+++ b/.changeset/yellow-bees-hope.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-app-api': minor
+---
+
+Removed deprecated `icons` property passing to `createApp` and `createSpecializedApp`. Use `IconBundleBlueprint.make` to create extensions instead and include them in the app.
diff --git a/packages/frontend-app-api/api-report.md b/packages/frontend-app-api/api-report.md
index 27d486b6a8..c837ff09b7 100644
--- a/packages/frontend-app-api/api-report.md
+++ b/packages/frontend-app-api/api-report.md
@@ -6,7 +6,6 @@
import { ConfigApi } from '@backstage/core-plugin-api';
import { ExternalRouteRef } from '@backstage/frontend-plugin-api';
import { FrontendFeature } from '@backstage/frontend-plugin-api';
-import { IconComponent } from '@backstage/core-plugin-api';
import { JSX as JSX_2 } from 'react';
import { ReactNode } from 'react';
import { RouteRef } from '@backstage/frontend-plugin-api';
@@ -14,9 +13,6 @@ import { SubRouteRef } from '@backstage/frontend-plugin-api';
// @public (undocumented)
export function createApp(options?: {
- icons?: {
- [key in string]: IconComponent;
- };
features?: (FrontendFeature | CreateAppFeatureLoader)[];
configLoader?: () => Promise<{
config: ConfigApi;
@@ -50,9 +46,6 @@ export type CreateAppRouteBinder = <
// @public
export function createSpecializedApp(options?: {
- icons?: {
- [key in string]: IconComponent;
- };
features?: FrontendFeature[];
config?: ConfigApi;
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx
index e2c7581e63..3d19fc413f 100644
--- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx
+++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx
@@ -15,57 +15,22 @@
*/
import React from 'react';
-import {
- coreExtensionData,
- createComponentExtension,
- createComponentRef,
- createExtension,
- createExtensionOverrides,
-} from '@backstage/frontend-plugin-api';
-import { resolveAppNodeSpecs } from '../../../tree/resolveAppNodeSpecs';
-import { resolveAppTree } from '../../../tree/resolveAppTree';
-import { App } from '../../../extensions/App';
+import { createComponentRef } from '@backstage/frontend-plugin-api';
import { DefaultComponentsApi } from './DefaultComponentsApi';
import { render, screen } from '@testing-library/react';
-import { instantiateAppNodeTree } from '../../../tree/instantiateAppNodeTree';
const testRefA = createComponentRef({ id: 'test.a' });
const testRefB1 = createComponentRef({ id: 'test.b' });
const testRefB2 = createComponentRef({ id: 'test.b' });
-const baseOverrides = createExtensionOverrides({
- extensions: [
- App,
- createExtension({
- namespace: 'app',
- name: 'root',
- attachTo: { id: 'app', input: 'root' },
- output: [coreExtensionData.reactElement],
- factory: () => [coreExtensionData.reactElement(
root
)],
- }),
- ],
-});
-
describe('DefaultComponentsApi', () => {
it('should provide components', () => {
- const tree = resolveAppTree(
- 'app',
- resolveAppNodeSpecs({
- features: [
- baseOverrides,
- createExtensionOverrides({
- extensions: [
- createComponentExtension({
- ref: testRefA,
- loader: { sync: () => () => test.a
},
- }),
- ],
- }),
- ],
- }),
- );
- instantiateAppNodeTree(tree.root);
- const api = DefaultComponentsApi.fromTree(tree);
+ const api = DefaultComponentsApi.fromComponents([
+ {
+ ref: testRefA,
+ impl: () => test.a
,
+ },
+ ]);
const ComponentA = api.getComponent(testRefA);
render();
@@ -74,24 +39,12 @@ describe('DefaultComponentsApi', () => {
});
it('should key extension refs by ID', () => {
- const tree = resolveAppTree(
- 'app',
- resolveAppNodeSpecs({
- features: [
- baseOverrides,
- createExtensionOverrides({
- extensions: [
- createComponentExtension({
- ref: testRefB1,
- loader: { sync: () => () => test.b
},
- }),
- ],
- }),
- ],
- }),
- );
- instantiateAppNodeTree(tree.root);
- const api = DefaultComponentsApi.fromTree(tree);
+ const api = DefaultComponentsApi.fromComponents([
+ {
+ ref: testRefB1,
+ impl: () => test.b
,
+ },
+ ]);
const ComponentB1 = api.getComponent(testRefB1);
const ComponentB2 = api.getComponent(testRefB2);
diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts
index 572b6fc3c4..317233d797 100644
--- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts
+++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts
@@ -16,7 +16,6 @@
import { ComponentType } from 'react';
import {
- AppTree,
ComponentRef,
ComponentsApi,
createComponentExtension,
@@ -30,19 +29,12 @@ import {
export class DefaultComponentsApi implements ComponentsApi {
#components: Map>;
- static fromTree(tree: AppTree) {
- const componentEntries = tree.root.edges.attachments
- .get('components')
- ?.reduce((map, e) => {
- const data = e.instance?.getData(
- createComponentExtension.componentDataRef,
- );
- if (data) {
- map.set(data.ref.id, data.impl);
- }
- return map;
- }, new Map());
- return new DefaultComponentsApi(componentEntries ?? new Map());
+ static fromComponents(
+ components: Array,
+ ) {
+ return new DefaultComponentsApi(
+ new Map(components.map(entry => [entry.ref.id, entry.impl])),
+ );
}
constructor(components: Map) {
diff --git a/packages/frontend-app-api/src/extensions/App.tsx b/packages/frontend-app-api/src/extensions/App.tsx
index ba6c5f65f1..e99b83eb5a 100644
--- a/packages/frontend-app-api/src/extensions/App.tsx
+++ b/packages/frontend-app-api/src/extensions/App.tsx
@@ -16,15 +16,11 @@
import React from 'react';
import {
+ ApiBlueprint,
ExtensionBoundary,
coreExtensionData,
- createComponentExtension,
createExtension,
createExtensionInput,
- IconBundleBlueprint,
- ThemeBlueprint,
- ApiBlueprint,
- TranslationBlueprint,
} from '@backstage/frontend-plugin-api';
export const App = createExtension({
@@ -32,14 +28,6 @@ export const App = createExtension({
attachTo: { id: 'root', input: 'default' }, // ignored
inputs: {
apis: createExtensionInput([ApiBlueprint.dataRefs.factory]),
- themes: createExtensionInput([ThemeBlueprint.dataRefs.theme]),
- components: createExtensionInput([
- createComponentExtension.componentDataRef,
- ]),
- translations: createExtensionInput([
- TranslationBlueprint.dataRefs.translation,
- ]),
- icons: createExtensionInput([IconBundleBlueprint.dataRefs.icons]),
root: createExtensionInput([coreExtensionData.reactElement], {
singleton: true,
}),
diff --git a/packages/frontend-app-api/src/extensions/AppLanguageApi.ts b/packages/frontend-app-api/src/extensions/AppLanguageApi.ts
new file mode 100644
index 0000000000..ff12848d44
--- /dev/null
+++ b/packages/frontend-app-api/src/extensions/AppLanguageApi.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+import { AppLanguageSelector } from '../../../core-app-api/src/apis/implementations/AppLanguageApi';
+import { appLanguageApiRef } from '@backstage/core-plugin-api/alpha';
+import { ApiBlueprint, createApiFactory } from '@backstage/frontend-plugin-api';
+
+export const AppLanguageApi = ApiBlueprint.make({
+ name: 'app-language',
+ params: {
+ factory: createApiFactory(
+ appLanguageApiRef,
+ AppLanguageSelector.createWithStorage(),
+ ),
+ },
+});
diff --git a/packages/frontend-app-api/src/extensions/themes.tsx b/packages/frontend-app-api/src/extensions/AppThemeApi.tsx
similarity index 65%
rename from packages/frontend-app-api/src/extensions/themes.tsx
rename to packages/frontend-app-api/src/extensions/AppThemeApi.tsx
index a4fd0b253e..d0dcad34e8 100644
--- a/packages/frontend-app-api/src/extensions/themes.tsx
+++ b/packages/frontend-app-api/src/extensions/AppThemeApi.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Backstage Authors
+ * Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,34 @@ import {
} from '@backstage/theme';
import DarkIcon from '@material-ui/icons/Brightness2';
import LightIcon from '@material-ui/icons/WbSunny';
-import { ThemeBlueprint } from '@backstage/frontend-plugin-api';
+import {
+ createExtensionInput,
+ ThemeBlueprint,
+ ApiBlueprint,
+ createApiFactory,
+ appThemeApiRef,
+} from '@backstage/frontend-plugin-api';
+import { AppThemeSelector } from '@backstage/core-app-api';
+
+/**
+ * Contains the themes installed into the app.
+ */
+export const AppThemeApi = ApiBlueprint.makeWithOverrides({
+ name: 'app-theme',
+ inputs: {
+ themes: createExtensionInput([ThemeBlueprint.dataRefs.theme]),
+ },
+ factory: (originalFactory, { inputs }) => {
+ return originalFactory({
+ factory: createApiFactory(
+ appThemeApiRef,
+ AppThemeSelector.createWithStorage(
+ inputs.themes.map(i => i.get(ThemeBlueprint.dataRefs.theme)),
+ ),
+ ),
+ });
+ },
+});
export const LightTheme = ThemeBlueprint.make({
name: 'light',
diff --git a/packages/frontend-app-api/src/extensions/ComponentsApi.tsx b/packages/frontend-app-api/src/extensions/ComponentsApi.tsx
new file mode 100644
index 0000000000..5803aacf95
--- /dev/null
+++ b/packages/frontend-app-api/src/extensions/ComponentsApi.tsx
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ createComponentExtension,
+ createExtensionInput,
+ ApiBlueprint,
+ createApiFactory,
+ componentsApiRef,
+} from '@backstage/frontend-plugin-api';
+import { DefaultComponentsApi } from '../apis/implementations/ComponentsApi';
+
+/**
+ * Contains the shareable components installed into the app.
+ */
+export const ComponentsApi = ApiBlueprint.makeWithOverrides({
+ name: 'components',
+ inputs: {
+ components: createExtensionInput([
+ createComponentExtension.componentDataRef,
+ ]),
+ },
+ factory: (originalFactory, { inputs }) => {
+ return originalFactory({
+ factory: createApiFactory(
+ componentsApiRef,
+ DefaultComponentsApi.fromComponents(
+ inputs.components.map(i =>
+ i.get(createComponentExtension.componentDataRef),
+ ),
+ ),
+ ),
+ });
+ },
+});
diff --git a/packages/frontend-app-api/src/extensions/FeatureFlagsApi.ts b/packages/frontend-app-api/src/extensions/FeatureFlagsApi.ts
new file mode 100644
index 0000000000..8016521539
--- /dev/null
+++ b/packages/frontend-app-api/src/extensions/FeatureFlagsApi.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ ApiBlueprint,
+ createApiFactory,
+ featureFlagsApiRef,
+} from '@backstage/frontend-plugin-api';
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+import { LocalStorageFeatureFlags } from '../../../core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags';
+
+/**
+ * Contains the shareable icons installed into the app.
+ */
+export const FeatureFlagsApi = ApiBlueprint.make({
+ name: 'feature-flags',
+ params: {
+ // TODO: properly discovery feature flags, maybe rework the whole thing
+ factory: createApiFactory({
+ api: featureFlagsApiRef,
+ deps: {},
+ factory: () => new LocalStorageFeatureFlags(),
+ }),
+ },
+});
diff --git a/packages/frontend-app-api/src/extensions/IconsApi.ts b/packages/frontend-app-api/src/extensions/IconsApi.ts
new file mode 100644
index 0000000000..d770000197
--- /dev/null
+++ b/packages/frontend-app-api/src/extensions/IconsApi.ts
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ createExtensionInput,
+ IconBundleBlueprint,
+ ApiBlueprint,
+ createApiFactory,
+ iconsApiRef,
+} from '@backstage/frontend-plugin-api';
+import { DefaultIconsApi } from '../apis/implementations/IconsApi';
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+import { icons as defaultIcons } from '../../../app-defaults/src/defaults';
+
+/**
+ * Contains the shareable icons installed into the app.
+ */
+export const IconsApi = ApiBlueprint.makeWithOverrides({
+ name: 'icons',
+ inputs: {
+ icons: createExtensionInput([IconBundleBlueprint.dataRefs.icons]),
+ },
+ factory: (originalFactory, { inputs }) => {
+ return originalFactory({
+ factory: createApiFactory(
+ iconsApiRef,
+ new DefaultIconsApi(
+ inputs.icons
+ .map(i => i.get(IconBundleBlueprint.dataRefs.icons))
+ .reduce((acc, bundle) => ({ ...acc, ...bundle }), defaultIcons),
+ ),
+ ),
+ });
+ },
+});
diff --git a/packages/frontend-app-api/src/extensions/TranslationsApi.tsx b/packages/frontend-app-api/src/extensions/TranslationsApi.tsx
new file mode 100644
index 0000000000..9c13c3b063
--- /dev/null
+++ b/packages/frontend-app-api/src/extensions/TranslationsApi.tsx
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ ApiBlueprint,
+ TranslationBlueprint,
+ createApiFactory,
+ createExtensionInput,
+} from '@backstage/frontend-plugin-api';
+import {
+ appLanguageApiRef,
+ translationApiRef,
+} from '@backstage/core-plugin-api/alpha';
+
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+import { I18nextTranslationApi } from '../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi';
+
+/**
+ * Contains translations that are installed in the app.
+ */
+export const TranslationsApi = ApiBlueprint.makeWithOverrides({
+ name: 'translations',
+ inputs: {
+ translations: createExtensionInput([
+ TranslationBlueprint.dataRefs.translation,
+ ]),
+ },
+ factory: (originalFactory, { inputs }) => {
+ return originalFactory({
+ factory: createApiFactory({
+ api: translationApiRef,
+ deps: { languageApi: appLanguageApiRef },
+ factory: ({ languageApi }) =>
+ I18nextTranslationApi.create({
+ languageApi,
+ resources: inputs.translations.map(i =>
+ i.get(TranslationBlueprint.dataRefs.translation),
+ ),
+ }),
+ }),
+ });
+ },
+});
diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx
index c79d8eed76..6fc4720da2 100644
--- a/packages/frontend-app-api/src/wiring/createApp.test.tsx
+++ b/packages/frontend-app-api/src/wiring/createApp.test.tsx
@@ -278,16 +278,24 @@ describe('createApp', () => {
]
]
- components [
-
-
-
- ]
- themes [
-
-
- ]
apis [
+
+ themes [
+
+
+ ]
+
+
+
+
+
+ components [
+
+
+
+ ]
+
+
diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx
index ca42dce3e4..37594f74d4 100644
--- a/packages/frontend-app-api/src/wiring/createApp.tsx
+++ b/packages/frontend-app-api/src/wiring/createApp.tsx
@@ -20,15 +20,10 @@ import {
ApiBlueprint,
AppTree,
appTreeApiRef,
- componentsApiRef,
coreExtensionData,
FrontendFeature,
- IconBundleBlueprint,
- iconsApiRef,
RouteResolutionApi,
routeResolutionApiRef,
- ThemeBlueprint,
- TranslationBlueprint,
} from '@backstage/frontend-plugin-api';
import { App } from '../extensions/App';
import { AppRoutes } from '../extensions/AppRoutes';
@@ -37,13 +32,10 @@ import { AppNav } from '../extensions/AppNav';
import {
AnyApiFactory,
ApiHolder,
- appThemeApiRef,
ConfigApi,
configApiRef,
- IconComponent,
featureFlagsApiRef,
identityApiRef,
- AppTheme,
errorApiRef,
discoveryApiRef,
fetchApiRef,
@@ -53,7 +45,6 @@ import {
ApiFactoryRegistry,
ApiProvider,
ApiResolver,
- AppThemeSelector,
} from '@backstage/core-app-api';
// TODO: Get rid of all of these
@@ -64,29 +55,20 @@ import { AppThemeProvider } from '../../../core-app-api/src/app/AppThemeProvider
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
-import { LocalStorageFeatureFlags } from '../../../core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags';
-// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { defaultConfigLoaderSync } from '../../../core-app-api/src/app/defaultConfigLoader';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { overrideBaseUrlConfigs } from '../../../core-app-api/src/app/overrideBaseUrlConfigs';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
-import { AppLanguageSelector } from '../../../core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector';
-// eslint-disable-next-line @backstage/no-relative-monorepo-imports
-import { I18nextTranslationApi } from '../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi';
-// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { apis as defaultApis } from '../../../app-defaults/src/defaults';
-import { DarkTheme, LightTheme } from '../extensions/themes';
+
import {
oauthRequestDialogAppRootElement,
alertDisplayAppRootElement,
} from '../extensions/elements';
import { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode';
-import {
- appLanguageApiRef,
- translationApiRef,
-} from '@backstage/core-plugin-api/alpha';
+
import { CreateAppRouteBinder } from '../routing';
import { RouteResolver } from '../routing/RouteResolver';
import { resolveRouteBindings } from '../routing/resolveRouteBindings';
@@ -103,12 +85,14 @@ import { AppRoot } from '../extensions/AppRoot';
import { toInternalBackstagePlugin } from '../../../frontend-plugin-api/src/wiring/createFrontendPlugin';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides';
-import { DefaultComponentsApi } from '../apis/implementations/ComponentsApi';
-import { DefaultIconsApi } from '../apis/implementations/IconsApi';
import { stringifyError } from '@backstage/errors';
-// eslint-disable-next-line @backstage/no-relative-monorepo-imports
-import { icons as defaultIcons } from '../../../app-defaults/src/defaults';
import { getBasePath } from '../routing/getBasePath';
+import { AppThemeApi, DarkTheme, LightTheme } from '../extensions/AppThemeApi';
+import { IconsApi } from '../extensions/IconsApi';
+import { TranslationsApi } from '../extensions/TranslationsApi';
+import { ComponentsApi } from '../extensions/ComponentsApi';
+import { AppLanguageApi } from '../extensions/AppLanguageApi';
+import { FeatureFlagsApi } from '../extensions/FeatureFlagsApi';
const DefaultApis = defaultApis.map(factory =>
ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }),
@@ -127,6 +111,12 @@ export const builtinExtensions = [
DarkTheme,
oauthRequestDialogAppRootElement,
alertDisplayAppRootElement,
+ AppThemeApi,
+ AppLanguageApi,
+ IconsApi,
+ TranslationsApi,
+ ComponentsApi,
+ FeatureFlagsApi,
...DefaultApis,
].map(def => resolveExtensionDefinition(def));
@@ -174,8 +164,6 @@ export interface CreateAppFeatureLoader {
/** @public */
export function createApp(options?: {
- /** @deprecated - Please use {@link @backstage/frontend-plugin-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */
- icons?: { [key in string]: IconComponent };
features?: (FrontendFeature | CreateAppFeatureLoader)[];
configLoader?: () => Promise<{ config: ConfigApi }>;
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
@@ -222,7 +210,6 @@ export function createApp(options?: {
}
const app = createSpecializedApp({
- icons: options?.icons,
config,
features: [...discoveredFeatures, ...providedFeatures],
bindRoutes: options?.bindRoutes,
@@ -250,8 +237,6 @@ export function createApp(options?: {
* @public
*/
export function createSpecializedApp(options?: {
- /** @deprecated - Please use {@link @backstage/frontend-plugin-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */
- icons?: { [key in string]: IconComponent };
features?: FrontendFeature[];
config?: ConfigApi;
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
@@ -288,7 +273,6 @@ export function createSpecializedApp(options?: {
routeBindings,
getBasePath(config),
),
- options?.icons,
);
if (isProtectedApp()) {
@@ -352,7 +336,6 @@ function createApiHolder(
configApi: ConfigApi,
appIdentityProxy: AppIdentityProxy,
routeResolutionApi: RouteResolutionApi,
- icons?: { [key in string]: IconComponent },
): ApiHolder {
const factoryRegistry = new ApiFactoryRegistry();
@@ -362,36 +345,10 @@ function createApiHolder(
?.map(e => e.instance?.getData(ApiBlueprint.dataRefs.factory))
.filter((x): x is AnyApiFactory => !!x) ?? [];
- const themeExtensions =
- tree.root.edges.attachments
- .get('themes')
- ?.map(e => e.instance?.getData(ThemeBlueprint.dataRefs.theme))
- .filter((x): x is AppTheme => !!x) ?? [];
-
- const translationResources =
- tree.root.edges.attachments
- .get('translations')
- ?.map(e => e.instance?.getData(TranslationBlueprint.dataRefs.translation))
- .filter(
- (x): x is typeof TranslationBlueprint.dataRefs.translation.T => !!x,
- ) ?? [];
-
- const extensionIcons = tree.root.edges.attachments
- .get('icons')
- ?.map(e => e.instance?.getData(IconBundleBlueprint.dataRefs.icons))
- .reduce((acc, bundle) => ({ ...acc, ...bundle }), {});
-
for (const factory of pluginApis) {
factoryRegistry.register('default', factory);
}
- // TODO: properly discovery feature flags, maybe rework the whole thing
- factoryRegistry.register('default', {
- api: featureFlagsApiRef,
- deps: {},
- factory: () => new LocalStorageFeatureFlags(),
- });
-
factoryRegistry.register('static', {
api: identityApiRef,
deps: {},
@@ -412,54 +369,12 @@ function createApiHolder(
factory: () => routeResolutionApi,
});
- factoryRegistry.register('static', {
- api: componentsApiRef,
- deps: {},
- factory: () => DefaultComponentsApi.fromTree(tree),
- });
-
- factoryRegistry.register('static', {
- api: iconsApiRef,
- deps: {},
- factory: () =>
- new DefaultIconsApi({ ...defaultIcons, ...extensionIcons, ...icons }),
- });
-
- factoryRegistry.register('static', {
- api: appThemeApiRef,
- deps: {},
- // TODO: add extension for registering themes
- factory: () => AppThemeSelector.createWithStorage(themeExtensions),
- });
-
- factoryRegistry.register('static', {
- api: appLanguageApiRef,
- deps: {},
- factory: () => AppLanguageSelector.createWithStorage(),
- });
-
factoryRegistry.register('static', {
api: configApiRef,
deps: {},
factory: () => configApi,
});
- factoryRegistry.register('static', {
- api: appLanguageApiRef,
- deps: {},
- factory: () => AppLanguageSelector.createWithStorage(),
- });
-
- factoryRegistry.register('static', {
- api: translationApiRef,
- deps: { languageApi: appLanguageApiRef },
- factory: ({ languageApi }) =>
- I18nextTranslationApi.create({
- languageApi,
- resources: translationResources,
- }),
- });
-
ApiResolver.validateFactories(factoryRegistry, factoryRegistry.getAllApis());
return new ApiResolver(factoryRegistry);
diff --git a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts
index 52d6e2482d..de2df761ff 100644
--- a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts
+++ b/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts
@@ -25,7 +25,7 @@ const iconsDataRef = createExtensionDataRef<{
export const IconBundleBlueprint = createExtensionBlueprint({
kind: 'icon-bundle',
namespace: 'app',
- attachTo: { id: 'app', input: 'icons' },
+ attachTo: { id: 'api:icons', input: 'icons' },
output: [iconsDataRef],
config: {
schema: {
diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts
index e6daf510eb..75d342fdcb 100644
--- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts
+++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts
@@ -32,7 +32,7 @@ describe('ThemeBlueprint', () => {
{
"$$type": "@backstage/ExtensionDefinition",
"attachTo": {
- "id": "app",
+ "id": "api:app-theme",
"input": "themes",
},
"configSchema": undefined,
diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts
index a4f550595f..c06078b122 100644
--- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts
+++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts
@@ -29,7 +29,7 @@ const themeDataRef = createExtensionDataRef().with({
export const ThemeBlueprint = createExtensionBlueprint({
kind: 'theme',
namespace: 'app',
- attachTo: { id: 'app', input: 'themes' },
+ attachTo: { id: 'api:app-theme', input: 'themes' },
output: [themeDataRef],
dataRefs: {
theme: themeDataRef,
diff --git a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts
index 3c7b271322..65ef4f7ee0 100644
--- a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts
+++ b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.test.ts
@@ -47,7 +47,7 @@ describe('TranslationBlueprint', () => {
{
"$$type": "@backstage/ExtensionDefinition",
"attachTo": {
- "id": "app",
+ "id": "api:translations",
"input": "translations",
},
"configSchema": undefined,
diff --git a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts
index 7b01d0b28f..ea7aa64713 100644
--- a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts
+++ b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts
@@ -28,7 +28,7 @@ const translationDataRef = createExtensionDataRef<
*/
export const TranslationBlueprint = createExtensionBlueprint({
kind: 'translation',
- attachTo: { id: 'app', input: 'translations' },
+ attachTo: { id: 'api:translations', input: 'translations' },
output: [translationDataRef],
dataRefs: {
translation: translationDataRef,
diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx
index 9561636d22..35d003e99b 100644
--- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx
+++ b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx
@@ -35,7 +35,7 @@ export function createComponentExtension(options: {
kind: 'component',
namespace: options.ref.id,
name: options.name,
- attachTo: { id: 'app', input: 'components' },
+ attachTo: { id: 'api:components', input: 'components' },
disabled: options.disabled,
output: [createComponentExtension.componentDataRef],
factory() {