diff --git a/packages/bridge/bridge-react/src/__tests__/types.test.ts b/packages/bridge/bridge-react/src/__tests__/types.test.ts new file mode 100644 index 00000000000..3962aed8e90 --- /dev/null +++ b/packages/bridge/bridge-react/src/__tests__/types.test.ts @@ -0,0 +1,93 @@ +/** + * Type tests to verify that bridge component types are correctly generated + * These tests ensure that the TypeScript compiler can properly infer types + */ + +import { createBridgeComponent } from '../provider/versions/legacy'; +import { createBridgeComponent as createBridgeComponentV18 } from '../provider/versions/v18'; +import type { BridgeComponent } from '../types'; + +// Test component props interface +interface WidgetProps { + text: string; + number: number; +} + +// Mock component for testing +const MockWidget: React.ComponentType = () => null; + +describe('Bridge Component Types', () => { + it('should have correct return type for legacy createBridgeComponent', () => { + const WidgetBridge = createBridgeComponent({ + rootComponent: MockWidget, + }); + + const bridgeInstance = WidgetBridge(); + + // Type assertions to verify the interface + expect(typeof bridgeInstance.render).toBe('function'); + expect(typeof bridgeInstance.destroy).toBe('function'); + expect(bridgeInstance.rawComponent).toBe(MockWidget); + expect(typeof bridgeInstance.__BRIDGE_FN__).toBe('function'); + + // Verify the bridge instance matches the BridgeComponent interface + const typedInstance: BridgeComponent = bridgeInstance; + expect(typedInstance).toBeDefined(); + }); + + it('should have correct return type for v18 createBridgeComponent', () => { + const WidgetBridge = createBridgeComponentV18({ + rootComponent: MockWidget, + }); + + const bridgeInstance = WidgetBridge(); + + // Type assertions to verify the interface + expect(typeof bridgeInstance.render).toBe('function'); + expect(typeof bridgeInstance.destroy).toBe('function'); + expect(bridgeInstance.rawComponent).toBe(MockWidget); + expect(typeof bridgeInstance.__BRIDGE_FN__).toBe('function'); + + // Verify the bridge instance matches the BridgeComponent interface + const typedInstance: BridgeComponent = bridgeInstance; + expect(typedInstance).toBeDefined(); + }); + + it('should properly infer component props types', () => { + const WidgetBridge = createBridgeComponent({ + rootComponent: MockWidget, + }); + + const bridgeInstance = WidgetBridge(); + + // Test that __BRIDGE_FN__ accepts the correct props type + bridgeInstance.__BRIDGE_FN__({ text: 'test', number: 42 }); + + // This should cause a TypeScript error if types are incorrect: + // bridgeInstance.__BRIDGE_FN__({ text: 'test' }); // missing 'number' + // bridgeInstance.__BRIDGE_FN__({ text: 'test', number: 'invalid' }); // wrong type + }); +}); + +// Type-only tests (these will be checked by TypeScript compiler) +type TestBridgeComponentType = ReturnType< + typeof createBridgeComponent +>; +type TestBridgeInstanceType = ReturnType; + +// Verify that the bridge instance has all required properties +type RequiredProperties = keyof BridgeComponent; +const requiredProps: RequiredProperties[] = [ + 'render', + 'destroy', + 'rawComponent', + '__BRIDGE_FN__', +]; + +// Verify that rawComponent has the correct type +type RawComponentType = TestBridgeInstanceType['rawComponent']; +const _rawComponentTypeCheck: RawComponentType = MockWidget; + +// Verify that __BRIDGE_FN__ has the correct signature +type BridgeFnType = TestBridgeInstanceType['__BRIDGE_FN__']; +const _bridgeFnTypeCheck: BridgeFnType = (args: WidgetProps) => {}; diff --git a/packages/bridge/bridge-react/src/index.ts b/packages/bridge/bridge-react/src/index.ts index 0c30c931c77..f45f5c87aec 100644 --- a/packages/bridge/bridge-react/src/index.ts +++ b/packages/bridge/bridge-react/src/index.ts @@ -39,6 +39,7 @@ export type { RenderFnParams, RemoteComponentProps, RemoteModule, + BridgeComponent, } from './types'; export type { DataFetchParams, diff --git a/packages/bridge/bridge-react/src/provider/versions/bridge-base.tsx b/packages/bridge/bridge-react/src/provider/versions/bridge-base.tsx index 0a7d43a4009..863931db7b2 100644 --- a/packages/bridge/bridge-react/src/provider/versions/bridge-base.tsx +++ b/packages/bridge/bridge-react/src/provider/versions/bridge-base.tsx @@ -10,6 +10,7 @@ import type { DestroyParams, RenderParams, CreateRootOptions, + BridgeComponent, } from '../../types'; import { ErrorBoundary, FallbackProps } from 'react-error-boundary'; import { RouterContext } from '../context'; @@ -21,7 +22,7 @@ export function createBaseBridgeComponent({ defaultRootOptions, ...bridgeInfo }: ProviderFnParams) { - return () => { + return (): BridgeComponent => { const rootMap = new Map(); const instance = federationRuntime.instance; LoggerInstance.debug( @@ -122,6 +123,13 @@ export function createBaseBridgeComponent({ } instance?.bridgeHook?.lifecycle?.afterBridgeDestroy?.emit(info); }, + + rawComponent: bridgeInfo.rootComponent, + __BRIDGE_FN__: (args: T) => { + LoggerInstance.debug('Bridge function called with args:', args); + // This function provides access to the bridge component for type inference + // It's primarily used for TypeScript type checking and development tools + }, }; }; } diff --git a/packages/bridge/bridge-react/src/types.ts b/packages/bridge/bridge-react/src/types.ts index eab85b7be60..bf8a455f149 100644 --- a/packages/bridge/bridge-react/src/types.ts +++ b/packages/bridge/bridge-react/src/types.ts @@ -126,6 +126,16 @@ export interface RemoteComponentParams< props?: T; } +/** + * Interface for a bridge component instance + */ +export interface BridgeComponent { + render: (info: RenderFnParams & { [key: string]: unknown }) => Promise; + destroy: (info: DestroyParams) => void; + rawComponent: React.ComponentType; + __BRIDGE_FN__: (args: T) => void; +} + /** * Interface for a remote module provider */ diff --git a/packages/bridge/bridge-react/src/v18.ts b/packages/bridge/bridge-react/src/v18.ts index 626628602f2..442e312ab4c 100644 --- a/packages/bridge/bridge-react/src/v18.ts +++ b/packages/bridge/bridge-react/src/v18.ts @@ -6,4 +6,9 @@ export type { RootType, DestroyParams, RenderParams, + RenderFnParams, + RemoteComponentParams, + RemoteComponentProps, + RemoteModule, + BridgeComponent, } from './types'; diff --git a/packages/bridge/bridge-react/src/v19.ts b/packages/bridge/bridge-react/src/v19.ts index 1c789ee22d6..3e67cf93d09 100644 --- a/packages/bridge/bridge-react/src/v19.ts +++ b/packages/bridge/bridge-react/src/v19.ts @@ -6,4 +6,9 @@ export type { RootType, DestroyParams, RenderParams, + RenderFnParams, + RemoteComponentParams, + RemoteComponentProps, + RemoteModule, + BridgeComponent, } from './types'; diff --git a/packages/bridge/bridge-react/vite.config.ts b/packages/bridge/bridge-react/vite.config.ts index be2badcdafd..ec129cc77c5 100644 --- a/packages/bridge/bridge-react/vite.config.ts +++ b/packages/bridge/bridge-react/vite.config.ts @@ -14,6 +14,15 @@ export default defineConfig({ '@module-federation/bridge-shared', 'react-error-boundary', ], + insertTypesEntry: true, + copyDtsFiles: false, + include: ['src/**/*'], + exclude: [ + '**/*.spec.ts', + '**/*.test.ts', + '**/*.spec.tsx', + '**/*.test.tsx', + ], }), ], build: {