Skip to content

Commit

Permalink
fix(react-bridge): optimize bridge router pathname (#2696)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhoushaw authored Jul 10, 2024
1 parent b945d21 commit e38e48d
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 186 deletions.
12 changes: 6 additions & 6 deletions apps/router-demo/router-remote1-2001/rsbuild.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ export default defineConfig({
'./export-app': './src/export-App.tsx',
},
shared: {
react: {
singleton: true,
},
'react-dom': {
singleton: true,
},
// react: {
// singleton: true,
// },
// 'react-dom': {
// singleton: true,
// },
// 'react-router-dom': {
// singleton: true,
// },
Expand Down
242 changes: 62 additions & 180 deletions packages/bridge/bridge-react/src/create.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
import * as ReactRouterDOM from 'react-router-dom';
import React, { forwardRef } from 'react';
import type { ProviderParams } from '@module-federation/bridge-shared';
import { LoggerInstance, pathJoin } from './utils';
import { dispatchPopstateEnv } from '@module-federation/bridge-shared';
import { LoggerInstance } from './utils';
import {
ErrorBoundary,
ErrorBoundaryPropsWithComponent,
} from 'react-error-boundary';

declare const __APP_VERSION__: string;
import RemoteApp from './remote';

export interface RenderFnParams extends ProviderParams {
dom?: any;
Expand All @@ -25,79 +22,67 @@ interface RemoteModule {
};
}

interface RemoteAppParams {
name: string;
providerInfo: NonNullable<RemoteModule['provider']>;
dispathPopstate: boolean;
}

const RemoteApp = ({
name,
memoryRoute,
basename,
providerInfo,
dispathPopstate,
...resProps
}: RemoteAppParams & ProviderParams) => {
const rootRef = useRef(null);
const renderDom = useRef(null);
const providerInfoRef = useRef<any>(null);
if (dispathPopstate) {
const location = ReactRouterDOM.useLocation();
const [pathname, setPathname] = useState(location.pathname);

useEffect(() => {
if (pathname !== '' && pathname !== location.pathname) {
LoggerInstance.log(`createRemoteComponent dispatchPopstateEnv >>>`, {
name,
pathname: location.pathname,
});
dispatchPopstateEnv();
}
setPathname(location.pathname);
}, [location]);
}

useEffect(() => {
const renderTimeout = setTimeout(() => {
const providerReturn = providerInfo();
providerInfoRef.current = providerReturn;
const renderProps = {
name,
dom: rootRef.current,
basename,
memoryRoute,
...resProps,
};
renderDom.current = rootRef.current;
function createLazyRemoteComponent<T, E extends keyof T>(info: {
loader: () => Promise<T>;
loading: React.ReactNode;
fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
export?: E;
}) {
const exportName = info?.export || 'default';
return React.lazy(async () => {
LoggerInstance.log(`createRemoteComponent LazyComponent create >>>`, {
lazyComponent: info.loader,
exportName,
});
try {
const m = (await info.loader()) as RemoteModule;
// @ts-ignore
const moduleName = m && m[Symbol.for('mf_module_id')];
LoggerInstance.log(
`createRemoteComponent LazyComponent render >>>`,
renderProps,
`createRemoteComponent LazyComponent loadRemote info >>>`,
{ name: moduleName, module: m, exportName },
);
providerReturn.render(renderProps);
});

return () => {
clearTimeout(renderTimeout);
setTimeout(() => {
if (providerInfoRef.current?.destroy) {
LoggerInstance.log(
`createRemoteComponent LazyComponent destroy >>>`,
{ name, basename, dom: renderDom.current },
);
providerInfoRef.current?.destroy({
dom: renderDom.current,
});
}
});
};
}, []);
// @ts-ignore
const exportFn = m[exportName] as any;

//@ts-ignore
return <div ref={rootRef}></div>;
};
if (exportName in m && typeof exportFn === 'function') {
const RemoteAppComponent = forwardRef<
HTMLDivElement,
{
basename?: ProviderParams['basename'];
memoryRoute?: ProviderParams['memoryRoute'];
}
>((props, _ref) => {
return (
<RemoteApp
name={moduleName}
providerInfo={exportFn}
exportName={info.export || 'default'}
{...props}
/>
);
});

(RemoteApp as any)['__APP_VERSION__'] = __APP_VERSION__;
return {
default: RemoteAppComponent,
};
} else {
LoggerInstance.log(
`createRemoteComponent LazyComponent module not found >>>`,
{ name: moduleName, module: m, exportName },
);
throw Error(
`Make sure that ${moduleName} has the correct export when export is ${String(
exportName,
)}`,
);
}
} catch (error) {
throw error;
}
});
}

export function createRemoteComponent<T, E extends keyof T>(info: {
loader: () => Promise<T>;
Expand All @@ -114,121 +99,18 @@ export function createRemoteComponent<T, E extends keyof T>(info: {
: {}
: {};

const LazyComponent = createLazyRemoteComponent(info);

return (
props: {
basename?: ProviderParams['basename'];
memoryRoute?: ProviderParams['memoryRoute'];
} & RawComponentType,
) => {
const exportName = info?.export || 'default';
let basename = '/';
let enableDispathPopstate = false;
let routerContextVal: any;
try {
ReactRouterDOM.useLocation();
enableDispathPopstate = true;
} catch {
enableDispathPopstate = false;
}

if (props.basename) {
basename = props.basename;
} else if (enableDispathPopstate) {
const ReactRouterDOMAny: any = ReactRouterDOM;
// Avoid building tools checking references
const useRouteMatch = ReactRouterDOMAny['use' + 'RouteMatch']; //v5
const useHistory = ReactRouterDOMAny['use' + 'History']; //v5
const useHref = ReactRouterDOMAny['use' + 'Href'];
const UNSAFE_RouteContext = ReactRouterDOMAny['UNSAFE_' + 'RouteContext'];

if (UNSAFE_RouteContext /* react-router@6 */) {
if (useHref) {
basename = useHref?.('/');
}
routerContextVal = useContext(UNSAFE_RouteContext);
if (
routerContextVal &&
routerContextVal.matches &&
routerContextVal.matches[0] &&
routerContextVal.matches[0].pathnameBase
) {
basename = pathJoin(
basename,
routerContextVal.matches[0].pathnameBase || '/',
);
}
} /* react-router@5 */ else {
const match = useRouteMatch?.(); // v5
if (useHistory /* react-router@5 */) {
// there is no dynamic switching of the router version in the project
// so hooks can be used in conditional judgment
const history = useHistory?.();
// To be compatible to history@4.10.1 and @5.3.0 we cannot write like this `history.createHref(pathname)`
basename = history?.createHref?.({ pathname: '/' });
}
if (match /* react-router@5 */) {
basename = pathJoin(basename, match?.path || '/');
}
}
}

const LazyComponent = useMemo(() => {
//@ts-ignore
return React.lazy(async () => {
LoggerInstance.log(`createRemoteComponent LazyComponent create >>>`, {
basename,
lazyComponent: info.loader,
exportName,
props,
routerContextVal,
});
try {
const m = (await info.loader()) as RemoteModule;
// @ts-ignore
const moduleName = m && m[Symbol.for('mf_module_id')];
LoggerInstance.log(
`createRemoteComponent LazyComponent loadRemote info >>>`,
{ basename, name: moduleName, module: m, exportName, props },
);

// @ts-ignore
const exportFn = m[exportName] as any;

if (exportName in m && typeof exportFn === 'function') {
return {
default: () => (
<RemoteApp
name={moduleName}
dispathPopstate={enableDispathPopstate}
{...info}
{...props}
providerInfo={exportFn}
basename={basename}
/>
),
};
} else {
LoggerInstance.log(
`createRemoteComponent LazyComponent module not found >>>`,
{ basename, name: moduleName, module: m, exportName, props },
);
throw Error(
`Make sure that ${moduleName} has the correct export when export is ${String(
exportName,
)}`,
);
}
} catch (error) {
throw error;
}
});
}, [exportName, basename, props.memoryRoute]);

//@ts-ignore
return (
<ErrorBoundary FallbackComponent={info.fallback}>
<React.Suspense fallback={info.loading}>
<LazyComponent />
<LazyComponent {...props} />
</React.Suspense>
</ErrorBoundary>
);
Expand Down
Loading

0 comments on commit e38e48d

Please sign in to comment.