Skip to content

@module-federation/bridge-react - support layer HOC #4241

@crutch12

Description

@crutch12

Clear and concise description of the problem

I think it would be great to have an option to declare the "bridge" layer exactly via the createLazyComponent call. It would be useful when you need to describe "adapters" for multiple remotes.

e.g. your host uses react-router-dom, but your remote has it's own router and you don't want to install react-router-dom to your remote. And you just want to provide 3 props:

  • location
  • onRouteChange (listen)
  • basename

You can write your host HOC layer specially for that remote, thus your remote is not required to share extra deps (e.g. react-router-dom, react-hook-form, jotai, redux, etc.)

Suggested solution

I see it as withLayer option in createLazyComponent

Host

import { getInstance, loadRemote } from '@module-federation/runtime';

import { useEffect } from 'react'; // host react
import { useLocation, useHistory } from 'react-router-dom' // host react-router dom
import { HostContext } from './context' // host context

const LazyComponent = getInstance()!.createLazyComponent({
  loader: () => loadRemote('remote'),
  loading: 'loading...',
  fallback: ({ error }) => {
    if (error instanceof Error && error.message.includes('not exist')) {
      return <div>fallback - not existed id</div>;
    }
    return <div>fallback</div>;
  },
  // HOC // Allows host to use it's own deps and provide values to remote in convinient form.
  // Thus remote doesn't require to share those deps, it just waits for declared props.
  // e.g. your host uses react-router-dom, but your remote has it's own router and you don't want to install react-router-dom to your remote
  // this logic may be applied to any npm deps or any host variables (e.g. React Context)
  withLayer: (App) => {
  // i'm not sure how to name it properly
  // e.g. bridgeLayer, withLayer, withBridge, withWrapper
    return (props) => {
      const location = useLocation()
      const { listen, createHref } = useHistory()
      const { updateUser } = useContext(HostContext)
      const basename = createHref(props.prefix)

      useEffect(() => {
        // ...
      }, [])

      return <App {...props} location={location} onRouteChange={listen} updateUser={updateUser} basename={basename} />
    }
  },
});

function HostRedPage() {
  // remote App gets props: { prefix, color, location, onRouteChange, updateUser, basename }
  return <LazyComponent prefix="/red" color="red" />
}

function HostGreenPage() {
  return <LazyComponent prefix="/green" color="green" />
}

Remote

import { useEffect } from 'react'; // remote react

export function App({ location, onRouteChange, updateUser, basename ...props }) {
  useEffect(() => {
    return onRouteChange((location, action) => {
      console.log("on host route change");
    })
  }, [onRouteChange])
  
  return <div>
    <div style={{ color: props.color }}>
      Provided basename: {basename}
    </div>
    <button onClick={() => location.push('/home')}>
      Open /home page in host
    </button>
    <button onClick={() => updateUser(null)}>
      Logout
    </button>
  </div>
}

export default createBridgeComponent({
  rootComponent: App,
});

Alternative

I always can do the same thing without withLayer option

function HostApp() {
  const location = useLocation()
  return <LazyComponent color="red" location={location} />
}

Also

Intead of props we could provide LayerContext from remote to host and share data with that

// remote

const BridgeLayerContext = React.createContext()

export default createBridgeComponent({
  rootComponent: App,
  bridgeContext: BridgeLayerContext,
});

function App() {
  const { basename, location, updateUser } = useContext(BridgeLayerContext)
  return '...'
}

// host

import { createContextProvider } from '@module-federation/bridge-react'

const LazyComponent = getInstance()!.createLazyComponent({
  // ...
  withLayer: (App, BridgeLayerContext) => {
    // similar to Context.Provider, but in fact Context.Provider is called inside of remote, not in host
    const BridgeLayerProvider = createContextProvider(BridgeLayerContext)

    return (props) => {
      const location = useLocation()
      const { createHref } = useHistory()
      const { updateUser } = useContext(HostContext)
      const basename = createHref(props.prefix)

      useEffect(() => {
        // ...
      }, [])

      return <BridgeLayerProvider location={location} updateUser={updateUser} basename={basename}>
        <App {...props} />
      </BridgeLayerProvider>
    }
  },
});

I'm not sure if it's possible with suggested implementation, but I think it's possible to implement something similar.

Additional context

I know that @module-federation/bridge-react already supports react-router-dom usage. I used react-router-dom as demonstrative example.

I think current solution is too generic, I want to have much more control over host <-> remote provided props

Validations

  • Read the Contributing Guidelines.
  • Check that there isn't already an issue that request the same feature to avoid creating a duplicate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions