@backstage/frontend-dynamic-feature-loader 0.0.0-nightly-20250425023938

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # @backstage/frontend-dynamic-feature-loader
2
+
3
+ ## 0.0.0-nightly-20250425023938
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @backstage/frontend-plugin-api@0.0.0-nightly-20250425023938
9
+ - @backstage/config@1.3.2
10
+
11
+ ## 0.1.0
12
+
13
+ ### Minor Changes
14
+
15
+ - 3bee3c3: The new package `frontend-dynamic-features-loader` provides a frontend feature loader that dynamically
16
+ loads frontend features based on the new frontend system and exposed as module federation remotes.
17
+ This new frontend feature loader works hand-in-hand with a new server of frontend plugin module federation
18
+ remotes, which is added as part of backend dynamic feature service in package `@backstage/backend-dynamic-feature-service`.
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies
23
+ - @backstage/frontend-plugin-api@0.10.1
24
+ - @backstage/config@1.3.2
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @backstage/frontend-dynamic-feature-loader
2
+
3
+ Backstage frontend feature loader to load new frontend system plugins exposed as module federation remotes.
4
+ The frontend feature loader provided in this package works hand-in-hand with the server of frontend plugin module federation remotes server which is part of backend dynamic feature service in package `@backstage/backend-dynamic-feature-service`.
5
+
6
+ **NOTE: The [new frontend system](https://backstage.io/docs/frontend-system/) that this package is relaying upon is in alpha, and we do not yet recommend using it for production deployments**
7
+
8
+ ## Usage
9
+
10
+ - To enable this loader, you should:
11
+
12
+ - Enable the backend dynamic features in your backend application, as explained in the [`backend-dynamic-feature-service` README.md file](../backend-dynamic-feature-service/README.md#how-it-works)
13
+ - Add the frontend feature loader to the list of features when creating the frontend application:
14
+
15
+ ```typescript
16
+ const app = createApp({
17
+ features: [...someOtherFeatures, dynamicFrontendFeaturesLoader()],
18
+ });
19
+ ```
20
+
21
+ ## How to add a frontend plugin for dynamic loading
22
+
23
+ Adding a frontend plugin (with new frontend system support, possibly in alpha support), is straightforward and consists in:
24
+
25
+ - building the frontend plugin with the `frontend-dynamic-container` role, which enables the module federation support, and packages the plugin as a module remote
26
+ - copying the frontend package folder, with the `dist` folder generated during the build, to the dynamic plugins root folder of the Backstage installation (defined by the `dynamicPlugins.rootDirectory` configuration value, which is usually set as `dynamic-plugins-root`).
27
+
28
+ So from a frontend plugin package folder, you would use the following command:
29
+
30
+ ```bash
31
+ yarn build --role frontend-dynamic-container && cp -R $(pwd) <target backstage>/dynamic-plugins-root/
32
+ ```
package/config.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /*
2
+ * Copyright 2024 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ export interface Config {
18
+ /**
19
+ * @visibility frontend
20
+ */
21
+ dynamicPlugins?: {};
22
+ }
@@ -0,0 +1,27 @@
1
+ import { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
2
+ import { FrontendFeatureLoader } from '@backstage/frontend-plugin-api';
3
+ import { UserOptions, ShareStrategy } from '@module-federation/runtime/types';
4
+
5
+ /**
6
+ *
7
+ * @public
8
+ */
9
+ type DynamicFrontendFeaturesLoaderOptions = {
10
+ /**
11
+ * Additional module federation arguments for the Module Federation runtime initialization.
12
+ */
13
+ moduleFederation: {
14
+ shared?: UserOptions['shared'];
15
+ shareStrategy?: ShareStrategy;
16
+ plugins?: Array<FederationRuntimePlugin>;
17
+ };
18
+ };
19
+ /**
20
+ * A function providing a loader of frontend features exposed as module federation remotes
21
+ * from the backend dynamic features service.
22
+ *
23
+ * @public
24
+ */
25
+ declare function dynamicFrontendFeaturesLoader(options?: DynamicFrontendFeaturesLoaderOptions): FrontendFeatureLoader;
26
+
27
+ export { type DynamicFrontendFeaturesLoaderOptions, dynamicFrontendFeaturesLoader };
@@ -0,0 +1,2 @@
1
+ export { dynamicFrontendFeaturesLoader } from './loader.esm.js';
2
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,108 @@
1
+ import { init, loadRemote } from '@module-federation/enhanced/runtime';
2
+ import { DefaultApiClient } from './schema/openapi/generated/apis/Api.client.esm.js';
3
+ import { createFrontendFeatureLoader } from '@backstage/frontend-plugin-api';
4
+
5
+ function dynamicFrontendFeaturesLoader(options) {
6
+ return createFrontendFeatureLoader({
7
+ async loader({ config }) {
8
+ const dynamicPLuginsConfig = config.getOptionalConfig("dynamicPlugins");
9
+ if (!dynamicPLuginsConfig) {
10
+ return [];
11
+ }
12
+ function error(message, err) {
13
+ console.error(
14
+ `${message}: ${err instanceof Error ? err.toString() : JSON.stringify(err)}`
15
+ );
16
+ }
17
+ const backendBaseUrl = config.getString("backend.baseUrl");
18
+ const appPackageName = config.getOptionalString("app.packageName") ?? "app";
19
+ let frontendPluginRemotes;
20
+ try {
21
+ const apiClient = new DefaultApiClient({
22
+ discoveryApi: {
23
+ getBaseUrl: async (rootPath) => `${backendBaseUrl}/${rootPath}`
24
+ },
25
+ fetchApi: {
26
+ fetch(input) {
27
+ return global.fetch(input);
28
+ }
29
+ }
30
+ });
31
+ const response = await apiClient.getRemotes({});
32
+ if (!response.ok) {
33
+ throw new Error(`${response.status} - ${response.statusText}`);
34
+ }
35
+ frontendPluginRemotes = await response.json();
36
+ } catch (err) {
37
+ error(
38
+ `Failed fetching module federation configuration of dynamic frontend plugins`,
39
+ err
40
+ );
41
+ return [];
42
+ }
43
+ try {
44
+ init({
45
+ ...options?.moduleFederation,
46
+ name: appPackageName.replaceAll("@", "").replaceAll("/", "__").replaceAll("-", "_"),
47
+ remotes: frontendPluginRemotes.map((remote) => ({
48
+ alias: remote.packageName,
49
+ ...remote.remoteInfo
50
+ }))
51
+ });
52
+ } catch (err) {
53
+ error(`Failed initializing module federation`, err);
54
+ return [];
55
+ }
56
+ const features = (await Promise.all(
57
+ frontendPluginRemotes.map(async (remote) => {
58
+ console.debug(
59
+ `Loading dynamic plugin '${remote.packageName}' from '${remote.remoteInfo.entry}'`
60
+ );
61
+ const moduleFeatures = await Promise.all(
62
+ remote.exposedModules.map(async (exposedModuleName) => {
63
+ const remoteModuleName = exposedModuleName === "." ? remote.remoteInfo.name : `${remote.remoteInfo.name}/${exposedModuleName}`;
64
+ let module;
65
+ try {
66
+ module = await loadRemote(remoteModuleName);
67
+ } catch (err) {
68
+ error(
69
+ `Failed loading remote module '${remoteModuleName}' of dynamic plugin '${remote.packageName}'`,
70
+ err
71
+ );
72
+ return void 0;
73
+ }
74
+ if (!module) {
75
+ console.warn(
76
+ `Skipping empty dynamic plugin remote module '${remoteModuleName}'.`
77
+ );
78
+ return void 0;
79
+ }
80
+ console.info(
81
+ `Remote module '${remoteModuleName}' of dynamic plugin '${remote.packageName}' loaded from ${remote.remoteInfo.entry}`
82
+ );
83
+ const defaultEntry = module.default;
84
+ if (!isLoadable(defaultEntry)) {
85
+ console.debug(
86
+ `Skipping dynamic plugin remote module '${remote}' since it doesn't export a new 'FrontendFeature' as default export.`
87
+ );
88
+ return void 0;
89
+ }
90
+ return defaultEntry;
91
+ })
92
+ );
93
+ return moduleFeatures;
94
+ })
95
+ )).flat().filter((feature) => feature !== void 0);
96
+ return [...features];
97
+ }
98
+ });
99
+ }
100
+ function isLoadable(obj) {
101
+ if (obj !== null && typeof obj === "object" && "$$type" in obj) {
102
+ return obj.$$type === "@backstage/FrontendPlugin" || obj.$$type === "@backstage/FrontendModule";
103
+ }
104
+ return false;
105
+ }
106
+
107
+ export { dynamicFrontendFeaturesLoader };
108
+ //# sourceMappingURL=loader.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.esm.js","sources":["../src/loader.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n FederationRuntimePlugin,\n init,\n loadRemote,\n} from '@module-federation/enhanced/runtime';\nimport { Module } from '@module-federation/sdk';\nimport { DefaultApiClient, Remote } from './schema/openapi';\nimport {\n FrontendFeature,\n FrontendFeatureLoader,\n createFrontendFeatureLoader,\n} from '@backstage/frontend-plugin-api';\nimport { ShareStrategy, UserOptions } from '@module-federation/runtime/types';\n\n/**\n *\n * @public\n */\nexport type DynamicFrontendFeaturesLoaderOptions = {\n /**\n * Additional module federation arguments for the Module Federation runtime initialization.\n */\n moduleFederation: {\n shared?: UserOptions['shared'];\n shareStrategy?: ShareStrategy;\n plugins?: Array<FederationRuntimePlugin>;\n };\n};\n\n/**\n * A function providing a loader of frontend features exposed as module federation remotes\n * from the backend dynamic features service.\n *\n * @public\n */\nexport function dynamicFrontendFeaturesLoader(\n options?: DynamicFrontendFeaturesLoaderOptions,\n): FrontendFeatureLoader {\n return createFrontendFeatureLoader({\n async loader({ config }) {\n const dynamicPLuginsConfig = config.getOptionalConfig('dynamicPlugins');\n if (!dynamicPLuginsConfig) {\n return [];\n }\n\n function error(message: string, err: unknown) {\n // eslint-disable-next-line no-console\n console.error(\n `${message}: ${\n err instanceof Error ? err.toString() : JSON.stringify(err)\n }`,\n );\n }\n\n const backendBaseUrl = config.getString('backend.baseUrl');\n\n const appPackageName =\n config.getOptionalString('app.packageName') ?? 'app';\n let frontendPluginRemotes: Array<Remote>;\n try {\n const apiClient = new DefaultApiClient({\n discoveryApi: {\n getBaseUrl: async rootPath => `${backendBaseUrl}/${rootPath}`,\n },\n fetchApi: {\n fetch(input) {\n return global.fetch(input);\n },\n },\n });\n\n const response = await apiClient.getRemotes({});\n if (!response.ok) {\n throw new Error(`${response.status} - ${response.statusText}`);\n }\n frontendPluginRemotes = await response.json();\n } catch (err) {\n error(\n `Failed fetching module federation configuration of dynamic frontend plugins`,\n err,\n );\n return [];\n }\n\n try {\n init({\n ...options?.moduleFederation,\n name: appPackageName\n .replaceAll('@', '')\n .replaceAll('/', '__')\n .replaceAll('-', '_'),\n remotes: frontendPluginRemotes.map(remote => ({\n alias: remote.packageName,\n ...remote.remoteInfo,\n })),\n });\n } catch (err) {\n error(`Failed initializing module federation`, err);\n return [];\n }\n\n const features = (\n await Promise.all(\n frontendPluginRemotes.map(async remote => {\n // eslint-disable-next-line no-console\n console.debug(\n `Loading dynamic plugin '${remote.packageName}' from '${remote.remoteInfo.entry}'`,\n );\n\n const moduleFeatures = await Promise.all(\n remote.exposedModules.map(async exposedModuleName => {\n const remoteModuleName =\n exposedModuleName === '.'\n ? remote.remoteInfo.name\n : `${remote.remoteInfo.name}/${exposedModuleName}`;\n let module: Module;\n try {\n module = await loadRemote<Module>(remoteModuleName);\n } catch (err) {\n error(\n `Failed loading remote module '${remoteModuleName}' of dynamic plugin '${remote.packageName}'`,\n err,\n );\n return undefined;\n }\n if (!module) {\n // eslint-disable-next-line no-console\n console.warn(\n `Skipping empty dynamic plugin remote module '${remoteModuleName}'.`,\n );\n return undefined;\n }\n // eslint-disable-next-line no-console\n console.info(\n `Remote module '${remoteModuleName}' of dynamic plugin '${remote.packageName}' loaded from ${remote.remoteInfo.entry}`,\n );\n const defaultEntry = module.default;\n if (!isLoadable(defaultEntry)) {\n // eslint-disable-next-line no-console\n console.debug(\n `Skipping dynamic plugin remote module '${remote}' since it doesn't export a new 'FrontendFeature' as default export.`,\n );\n return undefined;\n }\n return defaultEntry;\n }),\n );\n return moduleFeatures;\n }),\n )\n )\n .flat()\n .filter((feature): feature is FrontendFeature => feature !== undefined);\n\n return [...features];\n },\n });\n}\n\nfunction isLoadable(obj: unknown): obj is FrontendFeature {\n if (obj !== null && typeof obj === 'object' && '$$type' in obj) {\n return (\n obj.$$type === '@backstage/FrontendPlugin' ||\n obj.$$type === '@backstage/FrontendModule'\n );\n }\n return false;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import crossFetch from 'cross-fetch';
2
+ import { pluginId } from '../pluginId.esm.js';
3
+ import * as parser from 'uri-template';
4
+
5
+ class DefaultApiClient {
6
+ discoveryApi;
7
+ fetchApi;
8
+ constructor(options) {
9
+ this.discoveryApi = options.discoveryApi;
10
+ this.fetchApi = options.fetchApi || { fetch: crossFetch };
11
+ }
12
+ /**
13
+ * Get the Module Federation remote definitions.
14
+ */
15
+ async getRemotes(request, options) {
16
+ const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);
17
+ const uriTemplate = `/remotes`;
18
+ const uri = parser.parse(uriTemplate).expand({});
19
+ return await this.fetchApi.fetch(`${baseUrl}${uri}`, {
20
+ headers: {
21
+ "Content-Type": "application/json",
22
+ ...options?.token && { Authorization: `Bearer ${options?.token}` }
23
+ },
24
+ method: "GET"
25
+ });
26
+ }
27
+ }
28
+
29
+ export { DefaultApiClient };
30
+ //# sourceMappingURL=Api.client.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Api.client.esm.js","sources":["../../../../../src/schema/openapi/generated/apis/Api.client.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// ******************************************************************\n// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *\n// ******************************************************************\nimport { DiscoveryApi } from '../types/discovery';\nimport { FetchApi } from '../types/fetch';\nimport crossFetch from 'cross-fetch';\nimport { pluginId } from '../pluginId';\nimport * as parser from 'uri-template';\nimport { Remote } from '../models/Remote.model';\n\n/**\n * Wraps the Response type to convey a type on the json call.\n *\n * @public\n */\nexport type TypedResponse<T> = Omit<Response, 'json'> & {\n json: () => Promise<T>;\n};\n\n/**\n * Options you can pass into a request for additional information.\n *\n * @public\n */\nexport interface RequestOptions {\n token?: string;\n}\n/**\n * @public\n */\nexport type GetRemotes = {};\n\n/**\n * @public\n */\nexport class DefaultApiClient {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n\n constructor(options: {\n discoveryApi: { getBaseUrl(pluginId: string): Promise<string> };\n fetchApi?: { fetch: typeof fetch };\n }) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi || { fetch: crossFetch };\n }\n\n /**\n * Get the Module Federation remote definitions.\n */\n public async getRemotes(\n // @ts-ignore\n request: GetRemotes,\n options?: RequestOptions,\n ): Promise<TypedResponse<Array<Remote>>> {\n const baseUrl = await this.discoveryApi.getBaseUrl(pluginId);\n\n const uriTemplate = `/remotes`;\n\n const uri = parser.parse(uriTemplate).expand({});\n\n return await this.fetchApi.fetch(`${baseUrl}${uri}`, {\n headers: {\n 'Content-Type': 'application/json',\n ...(options?.token && { Authorization: `Bearer ${options?.token}` }),\n },\n method: 'GET',\n });\n }\n}\n"],"names":[],"mappings":";;;;AAmDO,MAAM,gBAAiB,CAAA;AAAA,EACX,YAAA;AAAA,EACA,QAAA;AAAA,EAEjB,YAAY,OAGT,EAAA;AACD,IAAA,IAAA,CAAK,eAAe,OAAQ,CAAA,YAAA;AAC5B,IAAA,IAAA,CAAK,QAAW,GAAA,OAAA,CAAQ,QAAY,IAAA,EAAE,OAAO,UAAW,EAAA;AAAA;AAC1D;AAAA;AAAA;AAAA,EAKA,MAAa,UAEX,CAAA,OAAA,EACA,OACuC,EAAA;AACvC,IAAA,MAAM,OAAU,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,WAAW,QAAQ,CAAA;AAE3D,IAAA,MAAM,WAAc,GAAA,CAAA,QAAA,CAAA;AAEpB,IAAA,MAAM,MAAM,MAAO,CAAA,KAAA,CAAM,WAAW,CAAE,CAAA,MAAA,CAAO,EAAE,CAAA;AAE/C,IAAO,OAAA,MAAM,KAAK,QAAS,CAAA,KAAA,CAAM,GAAG,OAAO,CAAA,EAAG,GAAG,CAAI,CAAA,EAAA;AAAA,MACnD,OAAS,EAAA;AAAA,QACP,cAAgB,EAAA,kBAAA;AAAA,QAChB,GAAI,SAAS,KAAS,IAAA,EAAE,eAAe,CAAU,OAAA,EAAA,OAAA,EAAS,KAAK,CAAG,CAAA;AAAA,OACpE;AAAA,MACA,MAAQ,EAAA;AAAA,KACT,CAAA;AAAA;AAEL;;;;"}
@@ -0,0 +1,4 @@
1
+ const pluginId = ".backstage/dynamic-features";
2
+
3
+ export { pluginId };
4
+ //# sourceMappingURL=pluginId.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pluginId.esm.js","sources":["../../../../src/schema/openapi/generated/pluginId.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport const pluginId = '.backstage/dynamic-features';\n"],"names":[],"mappings":"AAgBO,MAAM,QAAW,GAAA;;;;"}
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@backstage/frontend-dynamic-feature-loader",
3
+ "version": "0.0.0-nightly-20250425023938",
4
+ "backstage": {
5
+ "role": "web-library"
6
+ },
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "main": "dist/index.esm.js",
10
+ "types": "dist/index.d.ts"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/backstage/backstage",
15
+ "directory": "packages/frontend-dynamic-feature-loader"
16
+ },
17
+ "license": "Apache-2.0",
18
+ "sideEffects": false,
19
+ "main": "dist/index.esm.js",
20
+ "types": "dist/index.d.ts",
21
+ "files": [
22
+ "config.d.ts",
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "build": "backstage-cli package build",
27
+ "clean": "backstage-cli package clean",
28
+ "lint": "backstage-cli package lint",
29
+ "prepack": "backstage-cli package prepack",
30
+ "postpack": "backstage-cli package postpack",
31
+ "start": "backstage-cli package start",
32
+ "test": "backstage-cli package test"
33
+ },
34
+ "dependencies": {
35
+ "@backstage/config": "1.3.2",
36
+ "@backstage/frontend-plugin-api": "0.0.0-nightly-20250425023938",
37
+ "@module-federation/enhanced": "^0.9.0",
38
+ "@module-federation/runtime": "^0.9.0",
39
+ "@module-federation/sdk": "^0.9.0",
40
+ "cross-fetch": "^4.0.0",
41
+ "uri-template": "^2.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@backstage/cli": "0.0.0-nightly-20250425023938",
45
+ "@backstage/test-utils": "1.7.7",
46
+ "@testing-library/jest-dom": "^6.0.0",
47
+ "@testing-library/react": "^16.0.0",
48
+ "@types/react": "^18.0.0",
49
+ "msw": "^1.0.0",
50
+ "react": "^18.0.2",
51
+ "react-dom": "^18.0.2",
52
+ "react-router-dom": "^6.3.0"
53
+ },
54
+ "peerDependencies": {
55
+ "@types/react": "^17.0.0 || ^18.0.0",
56
+ "react": "^17.0.0 || ^18.0.0",
57
+ "react-dom": "^17.0.0 || ^18.0.0",
58
+ "react-router-dom": "^6.3.0"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "@types/react": {
62
+ "optional": true
63
+ }
64
+ },
65
+ "configSchema": "config.d.ts",
66
+ "typesVersions": {
67
+ "*": {
68
+ "package.json": [
69
+ "package.json"
70
+ ]
71
+ }
72
+ },
73
+ "module": "./dist/index.esm.js"
74
+ }