Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions packages/bridge/bridge-react/src/__tests__/types.test.ts
Original file line number Diff line number Diff line change
@@ -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<WidgetProps> = () => 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<WidgetProps> = 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<WidgetProps> = 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<WidgetProps>
>;
type TestBridgeInstanceType = ReturnType<TestBridgeComponentType>;

// Verify that the bridge instance has all required properties
type RequiredProperties = keyof BridgeComponent<WidgetProps>;
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) => {};
1 change: 1 addition & 0 deletions packages/bridge/bridge-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type {
RenderFnParams,
RemoteComponentProps,
RemoteModule,
BridgeComponent,
} from './types';
export type {
DataFetchParams,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
DestroyParams,
RenderParams,
CreateRootOptions,
BridgeComponent,
} from '../../types';
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
import { RouterContext } from '../context';
Expand All @@ -21,7 +22,7 @@ export function createBaseBridgeComponent<T>({
defaultRootOptions,
...bridgeInfo
}: ProviderFnParams<T>) {
return () => {
return (): BridgeComponent<T> => {
const rootMap = new Map<any, RootType>();
const instance = federationRuntime.instance;
LoggerInstance.debug(
Expand Down Expand Up @@ -122,6 +123,13 @@ export function createBaseBridgeComponent<T>({
}
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
},
};
};
}
10 changes: 10 additions & 0 deletions packages/bridge/bridge-react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ export interface RemoteComponentParams<
props?: T;
}

/**
* Interface for a bridge component instance
*/
export interface BridgeComponent<T = any> {
render: (info: RenderFnParams & { [key: string]: unknown }) => Promise<void>;
destroy: (info: DestroyParams) => void;
rawComponent: React.ComponentType<T>;
__BRIDGE_FN__: (args: T) => void;
}

/**
* Interface for a remote module provider
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/bridge/bridge-react/src/v18.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ export type {
RootType,
DestroyParams,
RenderParams,
RenderFnParams,
RemoteComponentParams,
RemoteComponentProps,
RemoteModule,
BridgeComponent,
} from './types';
5 changes: 5 additions & 0 deletions packages/bridge/bridge-react/src/v19.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ export type {
RootType,
DestroyParams,
RenderParams,
RenderFnParams,
RemoteComponentParams,
RemoteComponentProps,
RemoteModule,
BridgeComponent,
} from './types';
9 changes: 9 additions & 0 deletions packages/bridge/bridge-react/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down