【问题标题】:Webpack module federation and react-router-domWebpack 模块联合和 react-router-dom
【发布时间】:2022-01-09 08:04:54
【问题描述】:

如何正确设置 ModuleFederation 和 react-router-dom 以便我可以拥有

  • RouterHost app 中定义的路由
  • 远程Header 应用有<Link> 组件指向Host 中定义的路由?

但是,下面的设置失败,出现以下错误:

index.js:15 Uncaught Error: useHref() may be used only in the context of a <Router> component.

设置:

托管 mfe 应用,localhost:3001

...
import { BrowserRouter } from 'react-router-dom'

const Header = lazy(() => import("header/Header"))

const Host = () => {
  return (
    <BrowserRouter>
       <React.Suspense fallback="Loading Header...">
         <Header />
       </React.Suspense>
       <Switch>
         <Route path="/input">
           <InputFormView />
         </Route>
         <Route path="/">
            <ListView />
         </Route>
       </Switch>       
    </BrowserRouter>)
}

...

宿主的 webpack.config.js


...

plugins: [
  new ModuleFederationPlugin({
      name: 'host',
      remotes: {        
        header: 'header@http://localhost:3002/remoteEntry.js'
      },
      exposes: {
      },
      shared: {
        ...deps,
        react: {
          singleton: true,
          requiredVersion: deps.react,
        },
        "react-dom": {
          singleton: true,
          requiredVersion: deps["react-dom"],
        },       
        "react-router-dom": {
          singleton: true,
          requiredVersion: deps["react-router-dom"],
        }
      },
    }),
...

标头 mfe 应用程序,localhost:3002

...

import { Link } from 'react-router-dom'

const Header = () => {
 return (
   <div id="header">
     <h1> Header </h1>
     <Link to="/input"> 
        <button type="button"> Input form </button>        
     </Link>
   </div> 
 )

...

Header 的 webpack.config.js


...

 new ModuleFederationPlugin({
      name: 'header',
      filename: 'remoteEntry.js',
      exposes: {
        './Header': './src/Components/Header'
      },
      remotes: {},      
      shared: {
        ...deps,
        react: {
          singleton: true,
          requiredVersion: deps.react,
        },
        "react-dom": {
          singleton: true,
          requiredVersion: deps["react-dom"],
        },      
        "react-router-dom": {
          singleton: true,
          requiredVersion: deps["react-router-dom"],
        }
      },
    }),
...

但是,如果我也将 Header 包装在 BrowserRouter 中,则会遇到以下错误:

index.js:15 Uncaught Error: You cannot render a &lt;Router&gt; inside another &lt;Router&gt;. You should never have more than one in your app.

【问题讨论】:

    标签: javascript reactjs react-router react-router-dom webpack-module-federation


    【解决方案1】:

    在您的远程应用程序中,您应该将标头组件包装在 BrowserRouter 中,但不应公开包含 BrowserRouter 的组件。在示例中,我使用的是 react-router-dom v6。

    使用另一个组件(在我的示例中是 Test.js,它将在 index.js 中使用,但这不会被模块联合公开,它仅用于远程应用程序的本地开发)。 Header 是您想要向模块联合公开并在其他应用程序中使用的组件(因为您已经在 webpack 配置中拥有它)

    import React from 'react';
    import {
        BrowserRouter,
        Routes,
        Route,
      } from "react-router-dom";
    import Header from './Header';
    
    const localRouter = () => {
      return (
        <BrowserRouter>
            <Routes>
                <Route path="/" element={<div>home<Header></Header></div>}></Route>
                <Route path="input"element={<div>input</div>}/>
            </Routes>
        </BrowserRouter>)
    }
    
    export default localRouter;
    

    然后在您的主机应用程序中,您可以像在远程应用程序中一样使用 Header 组件。

    import React from 'react';
    import {
      BrowserRouter,
      Routes,
      Route,
      Link
    } from "react-router-dom";
    
    const Header = React.lazy(() => import('header/Header'));
    
    const HostApp = () => (
      <>
        <div>Hello, I'm the host app!</div>
        <BrowserRouter>
        <Routes>
            <Route path="/" element={<div>home
                        <React.Suspense fallback="loading...">
                            <Header />
                        </React.Suspense>
                  </div>}></Route>
            <Route path="input"element={<div>input</div>}/>
    
    ... some other routes
    
        </Routes>
      </BrowserRouter>
      </>
    );
    
    export default HostApp;
    

    这个想法是远程应用程序需要标头在 BrowserRouter 内运行以进行本地开发,但是当在主机应用程序中使用时,Header 组件将使用主机应用程序中的 BrowserRouter,因为远程应用程序中的 BrowserRouter 未公开任何地方的模块联合。

    【讨论】: