Move module federation enablement in app-next at runtime.

Signed-off-by: David Festal <dfestal@redhat.com>
Assisted-by: Cursor
This commit is contained in:
David Festal
2025-09-16 21:32:03 +02:00
parent 0a67ed0dde
commit c324bf71be
25 changed files with 1992 additions and 331 deletions
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
@@ -0,0 +1,11 @@
# @backstage/module-federation-common
This package provides a helper library for module federation, enabling a consistent management of shared dependencies
in both the module federation host (frontend application) at runtime, and remote modules at build-time through the CLI.
It provides:
- TypeScript types for both host and remote shared dependency definitions,
- a default list of shared dependencies (React, React Router, Material-UI, Backstage core packages, etc.) for both the host and remote modules,
- utilities used by the CLI to resolve versions of the shared dependencies for the host at build-time,
- utilities available for the frontend application to provide the list of resolved shared dependencies at runtime.
@@ -0,0 +1,10 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-module-federation-common
title: '@backstage/module-federation-common'
description: Helper library for module federation
spec:
lifecycle: experimental
type: backstage-common-library
owner: framework-maintainers
@@ -0,0 +1,70 @@
{
"name": "@backstage/module-federation-common",
"version": "0.0.0",
"description": "Helper library for module federation",
"backstage": {
"role": "common-library"
},
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"keywords": [
"backstage"
],
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "packages/module-federation-common"
},
"license": "Apache-2.0",
"sideEffects": false,
"main": "src/index.ts",
"types": "src/index.ts",
"files": [
"dist"
],
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/types": "workspace:^",
"@module-federation/runtime": "^0.21.6",
"serialize-javascript": "^6.0.0"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@types/react": "^18.0.0",
"@types/serialize-javascript": "^5.0.4",
"react": "^18.0.2",
"react-dom": "^18.0.2",
"react-router-dom": "^6.3.0"
},
"peerDependencies": {
"@emotion/react": "^11.10.5",
"@material-ui/core": "^4.12.2",
"@material-ui/styles": "^4.10.0",
"@mui/material": "^5.12.2",
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
}
@@ -0,0 +1,50 @@
## API Report File for "@backstage/module-federation-common"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { ForwardedError } from '@backstage/errors';
import type { UserOptions } from '@module-federation/runtime/types';
// @public
export function buildRuntimeSharedUserOption(): Promise<{
shared: UserOptions['shared'];
errors: ForwardedError[];
}>;
// @public
export function defaultHostSharedDependencies(): SharedDependencies<Host>;
// @public
export function defaultRemoteSharedDependencies(): SharedDependencies<Remote>;
// @public
export type Host = {
eager?: boolean;
requiredVersion: false | string;
version?: string;
};
// @public
export function prepareRuntimeSharedDependenciesScript(
hostSharedDependencies: SharedDependencies<
Host & {
version: string;
}
>,
): string;
// @public
export type Remote = {
import?: false | string;
requiredVersion?: false | string;
version?: false | string;
};
// @public
export type SharedDependencies<ContextFields> = {
[name: string]: {
singleton?: boolean;
} & ContextFields;
};
```
@@ -0,0 +1,44 @@
/*
* Copyright 2025 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 {
defaultHostSharedDependencies,
defaultRemoteSharedDependencies,
} from './defaults';
describe('defaultHostSharedDependencies', () => {
it('should return shared dependencies with the host-only eager property', () => {
const result = defaultHostSharedDependencies();
expect(result.react).toEqual({
requiredVersion: '*',
singleton: true,
eager: true,
});
});
});
describe('defaultRemoteSharedDependencies', () => {
it('should return shared dependencies with the remote-only import property', () => {
const result = defaultRemoteSharedDependencies();
expect(result.react).toEqual({
requiredVersion: '*',
singleton: true,
import: false,
});
});
});
@@ -0,0 +1,155 @@
/*
* Copyright 2025 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 { Host, Remote, SharedDependencies } from './types';
/**
* The list of default shared dependencies, expected to be the same
* for both the module federation host and remotes.
*
* @internal
*/
const defaultSharedDependencies: SharedDependencies<{
host: Host;
remote: Remote;
}> = {
// React
react: {
singleton: true,
host: {
eager: true,
requiredVersion: '*',
},
remote: {
import: false,
requiredVersion: '*',
},
},
'react-dom': {
singleton: true,
host: {
eager: true,
requiredVersion: '*',
},
remote: {
import: false,
requiredVersion: '*',
},
},
// React Router
'react-router': {
singleton: true,
host: {
eager: true,
requiredVersion: '*',
},
remote: {
import: false,
requiredVersion: '*',
},
},
'react-router-dom': {
singleton: true,
host: {
eager: true,
requiredVersion: '*',
},
remote: {
import: false,
requiredVersion: '*',
},
},
// MUI v4
// not setting import: false for MUI packages as this
// will break once Backstage moves to BUI
'@material-ui/core/styles': {
singleton: true,
host: {
eager: true,
requiredVersion: '*',
},
remote: {
requiredVersion: '*',
},
},
'@material-ui/styles': {
singleton: true,
host: {
eager: true,
requiredVersion: '*',
},
remote: {
requiredVersion: '*',
},
},
// MUI v5
// not setting import: false for MUI packages as this
// will break once Backstage moves to BUI
'@mui/material/styles/': {
singleton: true,
host: {
eager: true,
requiredVersion: '*',
},
remote: {
requiredVersion: '*',
},
},
'@emotion/react': {
singleton: true,
host: {
eager: true,
requiredVersion: '*',
},
remote: {
requiredVersion: '*',
},
},
};
/**
* Returns the list of default shared dependencies for the host, with host-only properties.
*
* @public
*/
export function defaultHostSharedDependencies(): SharedDependencies<Host> {
return Object.fromEntries(
Object.entries(defaultSharedDependencies).map(([name, p]) => [
name,
{
singleton: p.singleton,
...p.host,
},
]),
);
}
/**
* Returns the list of default shared dependencies for the remote, with remote-only properties.
*
* @public
*/
export function defaultRemoteSharedDependencies(): SharedDependencies<Remote> {
return Object.fromEntries(
Object.entries(defaultSharedDependencies).map(([name, p]) => [
name,
{
singleton: p.singleton,
...p.remote,
},
]),
);
}
@@ -0,0 +1,33 @@
/*
* Copyright 2025 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.
*/
/**
* Helper library for module federation, enabling a consistent management of shared dependencies
* in both the module federation host (frontend application) at runtime,
* and remote modules at build time through the CLI.
*
* @packageDocumentation
*/
export {
prepareRuntimeSharedDependenciesScript,
buildRuntimeSharedUserOption,
} from './runtime';
export {
defaultHostSharedDependencies,
defaultRemoteSharedDependencies,
} from './defaults';
export type { Host, Remote, SharedDependencies } from './types';
@@ -0,0 +1,523 @@
/*
* Copyright 2025 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 {
prepareRuntimeSharedDependenciesScript,
buildRuntimeSharedUserOption,
} from './runtime';
import { ForwardedError } from '@backstage/errors';
describe('prepareRuntimeSharedDependenciesScript', () => {
it('should generate script with minimal required properties', () => {
const sharedDependencies = {
react: {
version: '18.2.0',
requiredVersion: '*',
},
};
const result = prepareRuntimeSharedDependenciesScript(sharedDependencies);
expect(result)
.toBe(`window['__backstage-module-federation-shared-dependencies__'] = {
"react": {
"version": "18.2.0",
"requiredVersion": "*",
"module": () => import('react')
}
};`);
});
it('should handle multiple shared dependencies', () => {
const sharedDependencies = {
react: {
version: '18.2.0',
requiredVersion: '*',
},
'react-dom': {
version: '18.2.0',
requiredVersion: '*',
},
lodash: {
version: '4.17.21',
requiredVersion: '*',
},
};
const result = prepareRuntimeSharedDependenciesScript(sharedDependencies);
expect(result)
.toBe(`window['__backstage-module-federation-shared-dependencies__'] = {
"react": {
"version": "18.2.0",
"requiredVersion": "*",
"module": () => import('react')
},
"react-dom": {
"version": "18.2.0",
"requiredVersion": "*",
"module": () => import('react-dom')
},
"lodash": {
"version": "4.17.21",
"requiredVersion": "*",
"module": () => import('lodash')
}
};`);
});
it('should include requiredVersion when provided', () => {
const sharedDependencies = {
react: {
version: '18.2.0',
requiredVersion: '^18.0.0',
},
};
const result = prepareRuntimeSharedDependenciesScript(sharedDependencies);
expect(result)
.toBe(`window['__backstage-module-federation-shared-dependencies__'] = {
"react": {
"version": "18.2.0",
"requiredVersion": "^18.0.0",
"module": () => import('react')
}
};`);
});
it('should include requiredVersion when set to false', () => {
const sharedDependencies = {
react: {
version: '18.2.0',
requiredVersion: false as const,
},
};
const result = prepareRuntimeSharedDependenciesScript(sharedDependencies);
expect(result)
.toBe(`window['__backstage-module-federation-shared-dependencies__'] = {
"react": {
"version": "18.2.0",
"requiredVersion": false,
"module": () => import('react')
}
};`);
});
it('should include singleton when true', () => {
const sharedDependencies = {
react: {
version: '18.2.0',
requiredVersion: '*',
singleton: true,
},
};
const result = prepareRuntimeSharedDependenciesScript(sharedDependencies);
expect(result)
.toBe(`window['__backstage-module-federation-shared-dependencies__'] = {
"react": {
"version": "18.2.0",
"requiredVersion": "*",
"singleton": true,
"module": () => import('react')
}
};`);
});
it('should include singleton when false', () => {
const sharedDependencies = {
lodash: {
version: '4.17.21',
requiredVersion: '*',
singleton: false,
},
};
const result = prepareRuntimeSharedDependenciesScript(sharedDependencies);
expect(result)
.toBe(`window['__backstage-module-federation-shared-dependencies__'] = {
"lodash": {
"version": "4.17.21",
"requiredVersion": "*",
"singleton": false,
"module": () => import('lodash')
}
};`);
});
it('should include eager when true', () => {
const sharedDependencies = {
react: {
version: '18.2.0',
requiredVersion: '*',
eager: true,
},
};
const result = prepareRuntimeSharedDependenciesScript(sharedDependencies);
expect(result)
.toBe(`window['__backstage-module-federation-shared-dependencies__'] = {
"react": {
"version": "18.2.0",
"requiredVersion": "*",
"eager": true,
"module": () => import('react')
}
};`);
});
it('should include eager when false', () => {
const sharedDependencies = {
lodash: {
version: '4.17.21',
requiredVersion: '*',
eager: false,
},
};
const result = prepareRuntimeSharedDependenciesScript(sharedDependencies);
expect(result)
.toBe(`window['__backstage-module-federation-shared-dependencies__'] = {
"lodash": {
"version": "4.17.21",
"requiredVersion": "*",
"eager": false,
"module": () => import('lodash')
}
};`);
});
it('should handle all properties together', () => {
const sharedDependencies = {
react: {
version: '18.2.0',
requiredVersion: '^18.0.0',
singleton: true,
eager: false,
},
};
const result = prepareRuntimeSharedDependenciesScript(sharedDependencies);
expect(result)
.toBe(`window['__backstage-module-federation-shared-dependencies__'] = {
"react": {
"version": "18.2.0",
"requiredVersion": "^18.0.0",
"singleton": true,
"eager": false,
"module": () => import('react')
}
};`);
});
it('should handle empty object', () => {
const sharedDependencies = {};
const result = prepareRuntimeSharedDependenciesScript(sharedDependencies);
expect(result).toBe(
`window['__backstage-module-federation-shared-dependencies__'] = {};`,
);
});
it('should handle scoped package names and special characters', () => {
const sharedDependencies = {
'@backstage/core-plugin-api': {
version: '1.0.0',
requiredVersion: '*',
},
};
const result = prepareRuntimeSharedDependenciesScript(sharedDependencies);
expect(result)
.toBe(`window['__backstage-module-federation-shared-dependencies__'] = {
"@backstage/core-plugin-api": {
"version": "1.0.0",
"requiredVersion": "*",
"module": () => import('@backstage/core-plugin-api')
}
};`);
});
});
const globalSpy = jest.fn();
Object.defineProperty(
global,
'__backstage-module-federation-shared-dependencies__',
{
get: globalSpy,
},
);
describe('getRuntimeSharedDependencies', () => {
afterEach(jest.resetAllMocks);
it('should get runtime shared dependencies with minimal required properties', async () => {
const reactMock = { default: { React: 'react' } };
const reactDomMock = { default: { ReactDom: 'react-dom' } };
globalSpy.mockReturnValue({
react: {
version: '18.2.0',
requiredVersion: '*',
module: async () => reactMock,
},
'react-dom': {
version: '18.2.0',
requiredVersion: '*',
module: async () => reactDomMock,
},
});
const result = await buildRuntimeSharedUserOption();
expect(result.errors).toEqual([]);
expect(result.shared).toEqual({
react: {
version: '18.2.0',
lib: expect.any(Function),
shareConfig: {
requiredVersion: '*',
},
},
'react-dom': {
version: '18.2.0',
lib: expect.any(Function),
shareConfig: {
requiredVersion: '*',
},
},
});
// Test that lib functions return the correct modules
expect((result?.shared?.react as { lib: () => any }).lib()).toBe(reactMock);
expect((result?.shared?.['react-dom'] as { lib: () => any }).lib()).toBe(
reactDomMock,
);
});
it('should get runtime shared dependencies with custom version and requiredVersion', async () => {
globalSpy.mockReturnValue({
react: {
module: async () => ({ default: {} }),
version: '18.2.0',
requiredVersion: '^18.0.0',
},
lodash: {
module: async () => ({ default: {} }),
version: '4.17.21',
requiredVersion: '^4.17.0',
},
});
const result = await buildRuntimeSharedUserOption();
expect(result.errors).toEqual([]);
expect(result.shared).toEqual({
react: {
version: '18.2.0',
lib: expect.any(Function),
shareConfig: {
requiredVersion: '^18.0.0',
},
},
lodash: {
version: '4.17.21',
lib: expect.any(Function),
shareConfig: {
requiredVersion: '^4.17.0',
},
},
});
});
it('should handle eager', async () => {
globalSpy.mockReturnValue({
react: {
version: '18.2.0',
requiredVersion: '*',
eager: false,
module: async () => ({ default: {} }),
},
lodash: {
version: '4.17.21',
requiredVersion: '*',
eager: true,
module: async () => ({ default: {} }),
},
});
const result = await buildRuntimeSharedUserOption();
expect(result.errors).toEqual([]);
expect(result.shared).toEqual({
react: {
version: '18.2.0',
lib: expect.any(Function),
shareConfig: {
requiredVersion: '*',
eager: false,
},
},
lodash: {
version: '4.17.21',
lib: expect.any(Function),
shareConfig: {
requiredVersion: '*',
eager: true,
},
},
});
});
it('should handle singleton', async () => {
globalSpy.mockReturnValue({
react: {
module: async () => ({ default: {} }),
version: '18.2.0',
requiredVersion: '*',
singleton: true,
},
lodash: {
module: async () => ({ default: {} }),
version: '4.17.21',
requiredVersion: '*',
singleton: false,
},
});
const result = await buildRuntimeSharedUserOption();
expect(result.errors).toEqual([]);
expect(result.shared).toEqual({
react: {
version: '18.2.0',
lib: expect.any(Function),
shareConfig: {
requiredVersion: '*',
singleton: true,
},
},
lodash: {
version: '4.17.21',
lib: expect.any(Function),
shareConfig: {
requiredVersion: '*',
singleton: false,
},
},
});
});
it('should handle an empty object', async () => {
globalSpy.mockReturnValue({});
const result = await buildRuntimeSharedUserOption();
expect(result.errors).toEqual([]);
expect(result.shared).toEqual({});
});
it('should handle module import failures and collect errors', async () => {
const mockError = new Error('Module import failed');
const reactMock = { default: { React: 'react' } };
const lodashMock = { default: { _: 'lodash' } };
globalSpy.mockReturnValue({
react: {
module: async () => reactMock,
version: '18.2.0',
requiredVersion: '^18.0.0',
},
'failing-module': {
module: async () => {
throw mockError;
},
},
lodash: {
module: async () => lodashMock,
version: '4.17.21',
requiredVersion: '^4.17.0',
},
});
const result = await buildRuntimeSharedUserOption();
expect(result.errors).toHaveLength(1);
expect(result.errors[0]).toBeInstanceOf(ForwardedError);
expect(result.errors[0].message).toContain(
'Failed to dynamically import "failing-module" and add it to module federation shared dependencies:',
);
expect(result.errors[0].cause).toBe(mockError);
// Should still include successful modules
expect(result.shared).toEqual({
react: {
version: '18.2.0',
lib: expect.any(Function),
shareConfig: {
requiredVersion: '^18.0.0',
},
},
lodash: {
version: '4.17.21',
lib: expect.any(Function),
shareConfig: {
requiredVersion: '^4.17.0',
},
},
});
// Test that lib functions work correctly
expect((result?.shared?.react as { lib: () => any }).lib()).toBe(reactMock);
expect((result?.shared?.lodash as { lib: () => any }).lib()).toBe(
lodashMock,
);
});
it('should handle multiple module import failures', async () => {
const mockError1 = new Error('First module failed');
const mockError2 = new Error('Second module failed');
globalSpy.mockReturnValue({
'failing-module-1': {
module: async () => {
throw mockError1;
},
},
'failing-module-2': {
module: async () => {
throw mockError2;
},
},
});
const result = await buildRuntimeSharedUserOption();
expect(result.errors).toHaveLength(2);
expect(result.errors[0]).toBeInstanceOf(ForwardedError);
expect(result.errors[0].message).toContain('failing-module-1');
expect(result.errors[0].cause).toBe(mockError1);
expect(result.errors[1]).toBeInstanceOf(ForwardedError);
expect(result.errors[1].message).toContain('failing-module-2');
expect(result.errors[1].cause).toBe(mockError2);
expect(result.shared).toEqual({});
});
});
@@ -0,0 +1,112 @@
/*
* Copyright 2025 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 { Host, Runtime, SharedDependencies } from './types';
import { default as serialize } from 'serialize-javascript';
import type { UserOptions } from '@module-federation/runtime/types';
import { ForwardedError } from '@backstage/errors';
const BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL =
'__backstage-module-federation-shared-dependencies__';
/**
* Prepares the runtime shared dependencies script for the module federation host,
* which will be written by the CLI into a Javascript file added as an additional entry point for the frontend bundler.
* This script is used in the browser to build the list of shared dependencies provided to the module federation runtime.
*
* @see {@link buildRuntimeSharedUserOption}
*
* @param hostSharedDependencies - The shared dependencies for the module federation host.
* @returns The runtime shared dependencies script.
*
* @public
*/
export function prepareRuntimeSharedDependenciesScript(
hostSharedDependencies: SharedDependencies<Host & { version: string }>,
) {
const runtimeSharedDependencies: SharedDependencies<Host & Runtime> =
Object.fromEntries(
Object.entries(hostSharedDependencies).map(([name, sharedDep]) => [
name,
{
version: sharedDep.version,
requiredVersion: sharedDep.requiredVersion,
...(sharedDep.singleton !== undefined
? { singleton: sharedDep.singleton }
: {}),
...(sharedDep.eager !== undefined ? { eager: sharedDep.eager } : {}),
// eslint-disable-next-line no-new-func
module: new Function(
`return () => import('${name}')`,
)() as () => Promise<any>,
},
]),
);
return `window['${BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL}'] = ${serialize(
runtimeSharedDependencies,
{
space: 2,
unsafe: true,
},
)};`;
}
/**
* Builds the list of shared dependencies provided to the module federation runtime.
* It uses the runtime shared dependencies script prepared by {@link prepareRuntimeSharedDependenciesScript}.
*
* @public
*/
export async function buildRuntimeSharedUserOption(): Promise<{
shared: UserOptions['shared'];
errors: ForwardedError[];
}> {
const runtimeSharedDependencies =
(
window as {
[BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL]?: SharedDependencies<
Host & Runtime
>;
}
)[BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL] ?? {};
const result: UserOptions['shared'] = {};
const errors: ForwardedError[] = [];
for (const [name, sharedDep] of Object.entries(runtimeSharedDependencies)) {
try {
const module = await sharedDep.module();
result[name] = {
version: sharedDep.version,
lib: () => module,
shareConfig: {
singleton: sharedDep.singleton,
requiredVersion: sharedDep.requiredVersion,
eager: sharedDep.eager,
},
};
} catch (e) {
errors.push(
new ForwardedError(
`Failed to dynamically import "${name}" and add it to module federation shared dependencies:`,
e,
),
);
}
}
return { shared: result, errors };
}
@@ -0,0 +1,69 @@
/*
* Copyright 2025 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.
*/
/**
* Generic type for shared dependencies configuration in module federation.
*
* The ContextFields type parameter is used to provide additional fields
* to the shared dependencies configuration according to the context.
*
* @public
*/
export type SharedDependencies<ContextFields> = {
[name: string]: {
/** Whether this dependency should be a singleton */
singleton?: boolean;
} & ContextFields;
};
/**
* Specific fields for shared dependencies configured in module federation remote modules.
*
* @public
*/
export type Remote = {
/** Provided module for fallback. Set to false to not provide a fallback, or a custom import path. */
import?: false | string;
/** Required version range. Optional for remotes - can be auto-filled from package.json at build time. */
requiredVersion?: false | string;
/** Version of the shared dependency. Will be resolved at build time by default but can be overridden, but not completely removed. */
version?: false | string;
};
/**
* Specific fields for shared dependencies configured in the module federation host.
*
* @public
*/
export type Host = {
/** Whether to load this dependency eagerly */
eager?: boolean;
/** Required version range. Required for host. */
requiredVersion: false | string;
/** Version of the shared dependency. Will be resolved at build time by default but can be overridden, but not completely removed. */
version?: string;
};
/**
* Specific fields for shared dependencies configured when bootstrapping the module federation host at runtime.
*
* @internal
*/
export type Runtime = {
// version is always expected for runtime host dependencies
version: string;
module: () => Promise<any>;
};