module-federation-common: followup to initial implementation
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -3,48 +3,60 @@
|
||||
> 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 const BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL =
|
||||
'__@backstage/module_federation_shared_dependencies__';
|
||||
|
||||
// @public
|
||||
export function buildRuntimeSharedUserOption(): Promise<{
|
||||
shared: UserOptions['shared'];
|
||||
errors: ForwardedError[];
|
||||
}>;
|
||||
export function defaultHostSharedDependencies(): HostSharedDependencies;
|
||||
|
||||
// @public
|
||||
export function defaultHostSharedDependencies(): SharedDependencies<Host>;
|
||||
export function defaultRemoteSharedDependencies(): RemoteSharedDependencies;
|
||||
|
||||
// @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> = {
|
||||
export type HostSharedDependencies = {
|
||||
[name: string]: {
|
||||
singleton?: boolean;
|
||||
} & ContextFields;
|
||||
singleton: boolean;
|
||||
eager: boolean;
|
||||
requiredVersion: string;
|
||||
version?: string;
|
||||
};
|
||||
};
|
||||
|
||||
// @public
|
||||
export type LoadedRuntimeSharedDependency = {
|
||||
version: string;
|
||||
lib: () => unknown;
|
||||
shareConfig: HostSharedDependencies[string];
|
||||
};
|
||||
|
||||
// @public
|
||||
export function loadModuleFederationHostShared(options?: {
|
||||
onError?: (error: Error) => void;
|
||||
}): Promise<Record<string, LoadedRuntimeSharedDependency>>;
|
||||
|
||||
// @public
|
||||
export type RemoteSharedDependencies = {
|
||||
[name: string]: {
|
||||
singleton: boolean;
|
||||
eager: boolean;
|
||||
import?: false | string;
|
||||
requiredVersion: string;
|
||||
};
|
||||
};
|
||||
|
||||
// @public
|
||||
export type RuntimeSharedDependenciesGlobal = {
|
||||
items: Array<{
|
||||
name: string;
|
||||
version: string;
|
||||
lib: () => Promise<unknown>;
|
||||
shareConfig: {
|
||||
singleton: boolean;
|
||||
requiredVersion: string;
|
||||
eager: boolean;
|
||||
};
|
||||
}>;
|
||||
version: 'v1';
|
||||
};
|
||||
```
|
||||
|
||||
@@ -38,6 +38,7 @@ describe('defaultRemoteSharedDependencies', () => {
|
||||
expect(result.react).toEqual({
|
||||
requiredVersion: '*',
|
||||
singleton: true,
|
||||
eager: false,
|
||||
import: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Host, Remote, SharedDependencies } from './types';
|
||||
import { RemoteSharedDependencies, HostSharedDependencies } from './types';
|
||||
|
||||
/**
|
||||
* The list of default shared dependencies, expected to be the same
|
||||
@@ -22,115 +22,62 @@ import { Host, Remote, SharedDependencies } from './types';
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const defaultSharedDependencies: SharedDependencies<{
|
||||
host: Host;
|
||||
remote: Remote;
|
||||
}> = {
|
||||
const defaultSharedDependencies = {
|
||||
// React
|
||||
react: {
|
||||
singleton: true,
|
||||
host: {
|
||||
eager: true,
|
||||
requiredVersion: '*',
|
||||
},
|
||||
remote: {
|
||||
import: false,
|
||||
requiredVersion: '*',
|
||||
},
|
||||
host: {},
|
||||
remote: { import: false },
|
||||
},
|
||||
'react-dom': {
|
||||
singleton: true,
|
||||
host: {
|
||||
eager: true,
|
||||
requiredVersion: '*',
|
||||
},
|
||||
remote: {
|
||||
import: false,
|
||||
requiredVersion: '*',
|
||||
},
|
||||
host: {},
|
||||
remote: { import: false },
|
||||
},
|
||||
// React Router
|
||||
'react-router': {
|
||||
singleton: true,
|
||||
host: {
|
||||
eager: true,
|
||||
requiredVersion: '*',
|
||||
},
|
||||
remote: {
|
||||
import: false,
|
||||
requiredVersion: '*',
|
||||
},
|
||||
host: {},
|
||||
remote: { import: false },
|
||||
},
|
||||
'react-router-dom': {
|
||||
singleton: true,
|
||||
host: {
|
||||
eager: true,
|
||||
requiredVersion: '*',
|
||||
},
|
||||
remote: {
|
||||
import: false,
|
||||
requiredVersion: '*',
|
||||
},
|
||||
host: {},
|
||||
remote: { import: false },
|
||||
},
|
||||
// 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: '*',
|
||||
},
|
||||
host: {},
|
||||
remote: {},
|
||||
},
|
||||
'@material-ui/styles': {
|
||||
singleton: true,
|
||||
host: {
|
||||
eager: true,
|
||||
requiredVersion: '*',
|
||||
},
|
||||
remote: {
|
||||
requiredVersion: '*',
|
||||
},
|
||||
host: {},
|
||||
remote: {},
|
||||
},
|
||||
// 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: '*',
|
||||
},
|
||||
host: {},
|
||||
remote: {},
|
||||
},
|
||||
'@emotion/react': {
|
||||
singleton: true,
|
||||
host: {
|
||||
eager: true,
|
||||
requiredVersion: '*',
|
||||
},
|
||||
remote: {
|
||||
requiredVersion: '*',
|
||||
},
|
||||
host: {},
|
||||
remote: {},
|
||||
},
|
||||
};
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Returns the list of default shared dependencies for the host, with host-only properties.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function defaultHostSharedDependencies(): SharedDependencies<Host> {
|
||||
export function defaultHostSharedDependencies(): HostSharedDependencies {
|
||||
return Object.fromEntries(
|
||||
Object.entries(defaultSharedDependencies).map(([name, p]) => [
|
||||
name,
|
||||
{
|
||||
singleton: p.singleton,
|
||||
eager: true,
|
||||
singleton: true,
|
||||
requiredVersion: '*',
|
||||
...p.host,
|
||||
},
|
||||
]),
|
||||
@@ -142,12 +89,14 @@ export function defaultHostSharedDependencies(): SharedDependencies<Host> {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function defaultRemoteSharedDependencies(): SharedDependencies<Remote> {
|
||||
export function defaultRemoteSharedDependencies(): RemoteSharedDependencies {
|
||||
return Object.fromEntries(
|
||||
Object.entries(defaultSharedDependencies).map(([name, p]) => [
|
||||
name,
|
||||
{
|
||||
singleton: p.singleton,
|
||||
eager: false,
|
||||
singleton: true,
|
||||
requiredVersion: '*',
|
||||
...p.remote,
|
||||
},
|
||||
]),
|
||||
|
||||
@@ -22,12 +22,15 @@
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export {
|
||||
prepareRuntimeSharedDependenciesScript,
|
||||
buildRuntimeSharedUserOption,
|
||||
} from './runtime';
|
||||
export { loadModuleFederationHostShared } from './loadModuleFederationHostShared';
|
||||
export {
|
||||
defaultHostSharedDependencies,
|
||||
defaultRemoteSharedDependencies,
|
||||
} from './defaults';
|
||||
export type { Host, Remote, SharedDependencies } from './types';
|
||||
export {
|
||||
BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL,
|
||||
type HostSharedDependencies,
|
||||
type RemoteSharedDependencies,
|
||||
type LoadedRuntimeSharedDependency,
|
||||
type RuntimeSharedDependenciesGlobal,
|
||||
} from './types';
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* 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 { loadModuleFederationHostShared } from './loadModuleFederationHostShared';
|
||||
import {
|
||||
BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL,
|
||||
RuntimeSharedDependenciesGlobal,
|
||||
} from './types';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
|
||||
const globalSpy = jest.fn();
|
||||
Object.defineProperty(global, BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL, {
|
||||
get: globalSpy,
|
||||
});
|
||||
|
||||
function mockGlobal(items: RuntimeSharedDependenciesGlobal['items']) {
|
||||
globalSpy.mockReturnValue({ items, version: 'v1' });
|
||||
}
|
||||
|
||||
describe('loadModuleFederationHostShared', () => {
|
||||
afterEach(jest.resetAllMocks);
|
||||
|
||||
it('should preload and return shared dependencies keyed by name', async () => {
|
||||
const reactMock = { default: { React: 'react' } };
|
||||
const reactDomMock = { default: { ReactDom: 'react-dom' } };
|
||||
|
||||
mockGlobal([
|
||||
{
|
||||
name: 'react',
|
||||
version: '18.2.0',
|
||||
lib: async () => reactMock,
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '*',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'react-dom',
|
||||
version: '18.2.0',
|
||||
lib: async () => reactDomMock,
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '*',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
const shared = await loadModuleFederationHostShared();
|
||||
|
||||
expect(shared).toEqual({
|
||||
react: {
|
||||
version: '18.2.0',
|
||||
lib: expect.any(Function),
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '*',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
'react-dom': {
|
||||
version: '18.2.0',
|
||||
lib: expect.any(Function),
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '*',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(shared?.react?.lib()).toBe(reactMock);
|
||||
expect(shared?.['react-dom']?.lib()).toBe(reactDomMock);
|
||||
});
|
||||
|
||||
it('should preserve shareConfig properties from each remote', async () => {
|
||||
mockGlobal([
|
||||
{
|
||||
name: 'react',
|
||||
version: '18.2.0',
|
||||
lib: async () => ({ default: {} }),
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '^18.0.0',
|
||||
eager: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lodash',
|
||||
version: '4.17.21',
|
||||
lib: async () => ({ default: {} }),
|
||||
shareConfig: {
|
||||
singleton: false,
|
||||
requiredVersion: '^4.17.0',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
const shared = await loadModuleFederationHostShared();
|
||||
|
||||
expect(shared).toEqual({
|
||||
react: {
|
||||
version: '18.2.0',
|
||||
lib: expect.any(Function),
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '^18.0.0',
|
||||
eager: true,
|
||||
},
|
||||
},
|
||||
lodash: {
|
||||
version: '4.17.21',
|
||||
lib: expect.any(Function),
|
||||
shareConfig: {
|
||||
singleton: false,
|
||||
requiredVersion: '^4.17.0',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty items', async () => {
|
||||
mockGlobal([]);
|
||||
expect(await loadModuleFederationHostShared()).toEqual({});
|
||||
});
|
||||
|
||||
it('should throw on unsupported or missing version', async () => {
|
||||
globalSpy.mockReturnValue({ items: [], version: 'v2' });
|
||||
await expect(loadModuleFederationHostShared()).rejects.toThrow(
|
||||
'Unsupported version of the runtime shared dependencies: v2',
|
||||
);
|
||||
|
||||
globalSpy.mockReturnValue({ items: [] });
|
||||
await expect(loadModuleFederationHostShared()).rejects.toThrow(
|
||||
'Unsupported version of the runtime shared dependencies: undefined',
|
||||
);
|
||||
|
||||
globalSpy.mockReturnValue(undefined);
|
||||
await expect(loadModuleFederationHostShared()).rejects.toThrow(
|
||||
'Unsupported version of the runtime shared dependencies: undefined',
|
||||
);
|
||||
});
|
||||
|
||||
it('should report errors via onError and still return successful modules', async () => {
|
||||
const mockError = new Error('Module import failed');
|
||||
const reactMock = { default: { React: 'react' } };
|
||||
const lodashMock = { default: { _: 'lodash' } };
|
||||
|
||||
mockGlobal([
|
||||
{
|
||||
name: 'react',
|
||||
version: '18.2.0',
|
||||
lib: async () => reactMock,
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '^18.0.0',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'failing-module',
|
||||
version: '0.0.0',
|
||||
lib: async () => {
|
||||
throw mockError;
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '*',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lodash',
|
||||
version: '4.17.21',
|
||||
lib: async () => lodashMock,
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '^4.17.0',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const errors: Error[] = [];
|
||||
const shared = await loadModuleFederationHostShared({
|
||||
onError: err => errors.push(err),
|
||||
});
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0]).toBeInstanceOf(ForwardedError);
|
||||
expect(errors[0].message).toContain(
|
||||
'Failed to preload module federation shared dependency',
|
||||
);
|
||||
expect(errors[0]).toHaveProperty('cause', mockError);
|
||||
|
||||
expect(shared).toEqual({
|
||||
react: {
|
||||
version: '18.2.0',
|
||||
lib: expect.any(Function),
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '^18.0.0',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
lodash: {
|
||||
version: '4.17.21',
|
||||
lib: expect.any(Function),
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '^4.17.0',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(shared?.react?.lib()).toBe(reactMock);
|
||||
expect(shared?.lodash?.lib()).toBe(lodashMock);
|
||||
});
|
||||
|
||||
it('should throw on failure when no onError is provided', async () => {
|
||||
const mockError = new Error('Module import failed');
|
||||
|
||||
mockGlobal([
|
||||
{
|
||||
name: 'react',
|
||||
version: '18.2.0',
|
||||
lib: async () => ({ default: {} }),
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '*',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'failing-module',
|
||||
version: '0.0.0',
|
||||
lib: async () => {
|
||||
throw mockError;
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '*',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(loadModuleFederationHostShared()).rejects.toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
'Failed to preload module federation shared dependency',
|
||||
),
|
||||
cause: mockError,
|
||||
});
|
||||
});
|
||||
|
||||
it('should report multiple errors via onError', async () => {
|
||||
const mockError1 = new Error('First module failed');
|
||||
const mockError2 = new Error('Second module failed');
|
||||
|
||||
mockGlobal([
|
||||
{
|
||||
name: 'failing-module-1',
|
||||
version: '0.0.0',
|
||||
lib: async () => {
|
||||
throw mockError1;
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '*',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'failing-module-2',
|
||||
version: '0.0.0',
|
||||
lib: async () => {
|
||||
throw mockError2;
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: true,
|
||||
requiredVersion: '*',
|
||||
eager: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const errors: Error[] = [];
|
||||
const shared = await loadModuleFederationHostShared({
|
||||
onError: err => errors.push(err),
|
||||
});
|
||||
|
||||
expect(errors).toHaveLength(2);
|
||||
expect(errors[0]).toBeInstanceOf(ForwardedError);
|
||||
expect(errors[0]).toHaveProperty('cause', mockError1);
|
||||
expect(errors[1]).toBeInstanceOf(ForwardedError);
|
||||
expect(errors[1]).toHaveProperty('cause', mockError2);
|
||||
|
||||
expect(shared).toEqual({});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 {
|
||||
BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL,
|
||||
LoadedRuntimeSharedDependency,
|
||||
RuntimeSharedDependenciesGlobal,
|
||||
} from './types';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
|
||||
/**
|
||||
* Preloads and builds the list of shared dependencies provided to the module federation runtime.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export async function loadModuleFederationHostShared(options?: {
|
||||
onError?: (error: Error) => void;
|
||||
}): Promise<Record<string, LoadedRuntimeSharedDependency>> {
|
||||
const { items = [], version } =
|
||||
(
|
||||
window as {
|
||||
[BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL]?: RuntimeSharedDependenciesGlobal;
|
||||
}
|
||||
)[BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL] ?? {};
|
||||
if (version !== 'v1') {
|
||||
throw new Error(
|
||||
`Unsupported version of the runtime shared dependencies: ${version}`,
|
||||
);
|
||||
}
|
||||
|
||||
const results = await Promise.allSettled(items.map(item => item.lib()));
|
||||
|
||||
const shared: Record<string, LoadedRuntimeSharedDependency> = {};
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const result = results[i];
|
||||
if (result.status === 'fulfilled') {
|
||||
const mod = result.value;
|
||||
const item = items[i];
|
||||
shared[item.name] = {
|
||||
version: item.version,
|
||||
lib: () => mod,
|
||||
shareConfig: item.shareConfig,
|
||||
};
|
||||
} else {
|
||||
const error = new ForwardedError(
|
||||
'Failed to preload module federation shared dependency',
|
||||
result.reason,
|
||||
);
|
||||
if (options?.onError) {
|
||||
options.onError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shared;
|
||||
}
|
||||
@@ -1,523 +0,0 @@
|
||||
/*
|
||||
* 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({});
|
||||
});
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* 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 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 }>,
|
||||
) {
|
||||
type StringModule = Omit<Runtime, 'module'> & { module: string };
|
||||
const runtimeSharedDependencies: SharedDependencies<Host & StringModule> =
|
||||
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 } : {}),
|
||||
module: `() => import('${name}')`,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
return `window['${BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL}'] = ${JSON.stringify(
|
||||
runtimeSharedDependencies,
|
||||
null,
|
||||
2,
|
||||
).replace(
|
||||
/(^\s+"module":\s*)"([^"]+)"$/gm,
|
||||
(_, start, unquoted) => `${start}${unquoted}`,
|
||||
)};`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 };
|
||||
}
|
||||
@@ -15,55 +15,79 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* The name of the global object used to pass shared dependencies from the CLI to the module federation runtime.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SharedDependencies<ContextFields> = {
|
||||
export const BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL =
|
||||
'__@backstage/module_federation_shared_dependencies__';
|
||||
|
||||
/**
|
||||
* Shared dependencies configuration in module federation remote modules.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type RemoteSharedDependencies = {
|
||||
[name: string]: {
|
||||
/** Whether this dependency should be a singleton */
|
||||
singleton?: boolean;
|
||||
} & ContextFields;
|
||||
/** Whether to share this dependency as a singleton */
|
||||
singleton: boolean;
|
||||
/** Whether to load this dependency eagerly */
|
||||
eager: boolean;
|
||||
/** Provided module for fallback. Set to false to not provide a fallback, or a custom import path. */
|
||||
import?: false | string;
|
||||
/** Required version range. */
|
||||
requiredVersion: string;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Specific fields for shared dependencies configured in module federation remote modules.
|
||||
* Shared dependencies configuration in module federation host 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;
|
||||
export type HostSharedDependencies = {
|
||||
[name: string]: {
|
||||
/** Whether to share this dependency as a singleton */
|
||||
singleton: boolean;
|
||||
/** Whether to load this dependency eagerly */
|
||||
eager: boolean;
|
||||
/** Required version range. */
|
||||
requiredVersion: 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 in the module federation host.
|
||||
* A single shared runtime dependency from the CLI that has been loaded and ready to be used in the module federation runtime.
|
||||
*
|
||||
* @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
|
||||
export type LoadedRuntimeSharedDependency = {
|
||||
version: string;
|
||||
module: () => Promise<any>;
|
||||
lib: () => unknown;
|
||||
shareConfig: HostSharedDependencies[string];
|
||||
};
|
||||
|
||||
/**
|
||||
* The global object used to pass shared dependencies from the CLI to the module federation runtime.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Stored in {@link BACKSTAGE_RUNTIME_SHARED_DEPENDENCIES_GLOBAL}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type RuntimeSharedDependenciesGlobal = {
|
||||
items: Array<{
|
||||
name: string;
|
||||
version: string;
|
||||
lib: () => Promise<unknown>;
|
||||
shareConfig: {
|
||||
singleton: boolean;
|
||||
requiredVersion: string;
|
||||
eager: boolean;
|
||||
};
|
||||
}>;
|
||||
version: 'v1';
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user