- Sponsor
-
Notifications
You must be signed in to change notification settings - Fork 283
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
fix(react-bridge): optimize bridge router pathname (#2696)
- v8.8.0
- v0.9.1
- v0.9.0
- v0.8.12
- v0.8.10
- v0.8.9
- v0.8.8
- v0.8.7
- v0.8.6
- v0.8.5
- v0.8.4
- v0.8.3
- v0.8.2
- v0.8.1
- v0.8.0
- v0.7.7
- v0.7.6
- v0.7.5
- v0.7.3
- v0.7.2
- v0.7.1
- v0.7.0
- v0.6.16
- v0.6.15
- v0.6.14
- v0.6.13
- v0.6.12
- v0.6.11
- v0.6.10
- v0.6.9
- v0.6.8
- v0.6.7
- v0.6.6
- v0.6.5
- v0.6.3
- v0.6.2
- v0.6.1
- v0.6.0
- v0.5.2
- v0.5.1
- v0.5.0
- v0.3.5
- v0.3.4
- v0.3.3
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.8
- v0.2.7
- v0.2.6
- last-release
- 0.8.11
Showing
3 changed files
with
233 additions
and
186 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
import React, { useContext, useEffect, useRef, useState } from 'react'; | ||
import * as ReactRouterDOM from 'react-router-dom'; | ||
import type { ProviderParams } from '@module-federation/bridge-shared'; | ||
import { LoggerInstance, pathJoin } from '../utils'; | ||
import { dispatchPopstateEnv } from '@module-federation/bridge-shared'; | ||
|
||
declare const __APP_VERSION__: string; | ||
|
||
export interface RenderFnParams extends ProviderParams { | ||
dom?: any; | ||
} | ||
|
||
interface RemoteModule { | ||
provider: () => { | ||
render: ( | ||
info: ProviderParams & { | ||
dom: any; | ||
}, | ||
) => void; | ||
destroy: (info: { dom: any }) => void; | ||
}; | ||
} | ||
|
||
interface RemoteAppParams { | ||
name: string; | ||
providerInfo: NonNullable<RemoteModule['provider']>; | ||
exportName: string | number | symbol; | ||
} | ||
|
||
const RemoteApp = ({ | ||
name, | ||
memoryRoute, | ||
basename, | ||
providerInfo, | ||
...resProps | ||
}: RemoteAppParams & ProviderParams) => { | ||
const rootRef = useRef(null); | ||
const renderDom = useRef(null); | ||
const providerInfoRef = useRef<any>(null); | ||
|
||
useEffect(() => { | ||
const renderTimeout = setTimeout(() => { | ||
const providerReturn = providerInfo(); | ||
providerInfoRef.current = providerReturn; | ||
const renderProps = { | ||
name, | ||
dom: rootRef.current, | ||
basename, | ||
memoryRoute, | ||
...resProps, | ||
}; | ||
renderDom.current = rootRef.current; | ||
LoggerInstance.log( | ||
`createRemoteComponent LazyComponent render >>>`, | ||
renderProps, | ||
); | ||
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 | ||
return <div ref={rootRef}></div>; | ||
}; | ||
|
||
(RemoteApp as any)['__APP_VERSION__'] = __APP_VERSION__; | ||
|
||
interface ExtraDataProps { | ||
basename?: string; | ||
} | ||
|
||
export function withRouterData<P extends Parameters<typeof RemoteApp>[0]>( | ||
WrappedComponent: React.ComponentType<P & ExtraDataProps>, | ||
): React.FC<Omit<P, keyof ExtraDataProps>> { | ||
return (props: any) => { | ||
let enableDispathPopstate = false; | ||
let routerContextVal: any; | ||
try { | ||
ReactRouterDOM.useLocation(); | ||
enableDispathPopstate = true; | ||
} catch { | ||
enableDispathPopstate = false; | ||
} | ||
let basename = '/'; | ||
|
||
if (!props.basename && 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.length > 0 | ||
) { | ||
const matchIndex = routerContextVal.matches.length - 1; | ||
const pathnameBase = | ||
routerContextVal.matches[matchIndex].pathnameBase; | ||
basename = pathJoin(basename, 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 || '/'); | ||
} | ||
} | ||
} | ||
|
||
LoggerInstance.log(`createRemoteComponent withRouterData >>>`, { | ||
...props, | ||
basename, | ||
routerContextVal, | ||
enableDispathPopstate, | ||
}); | ||
|
||
if (enableDispathPopstate) { | ||
const location = ReactRouterDOM.useLocation(); | ||
const [pathname, setPathname] = useState(location.pathname); | ||
|
||
useEffect(() => { | ||
if (pathname !== '' && pathname !== location.pathname) { | ||
LoggerInstance.log(`createRemoteComponent dispatchPopstateEnv >>>`, { | ||
name: props.name, | ||
pathname: location.pathname, | ||
}); | ||
dispatchPopstateEnv(); | ||
} | ||
setPathname(location.pathname); | ||
}, [location]); | ||
} | ||
|
||
return <WrappedComponent {...(props as P)} basename={basename} />; | ||
}; | ||
} | ||
|
||
export default withRouterData(RemoteApp); |