Webpack module federation is not working with eager shared libs

volk picture volk · Feb 9, 2021 · Viewed 21.4k times · Source

I was looking into Webpack 5 Module federation feature, and have some trouble understanding why my code does not work. The idea is pretty similar to what standard module federation examples do:

app1 - is the host app app2 - is a remote exposing the whole app to app1

(app1 renders the header and horizontal line, below which the app2 should be rendered)

Both app1 and app2 declares react and react-dom as their shared, singleton, eager dependencies in the weback.config.js:

// app1 webpack.config.js
module.exports = {
  entry: path.resolve(SRC_DIR, './index.js');,
  ...
  plugins: [
    new ModuleFederationPlugin({
      name: "app1",
      remotes: {
        app2: `app2@//localhost:2002/remoteEntry.js`,
      },
      shared: { react: { singleton: true, eager: true }, "react-dom": { singleton: true, eager: true } },
    }),
    ...
  ],
};
// app2 webpack.config.js
module.exports = {
  entry: path.resolve(SRC_DIR, './index.js');,
  ...
  plugins: [
    new ModuleFederationPlugin({
      name: "app2",
      library: { type: "var", name: "app2" },
      filename: "remoteEntry.js",
      exposes: {
        "./App": "./src/App",
      },
      shared: { react: { singleton: true, eager: true }, "react-dom": { singleton: true, eager: true } },
    }),
    ...
  ],
};

In the App1 index.js I have next code:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";


ReactDOM.render(<App />, document.getElementById("root"));

The App1 App.js component is next:

import React, { Suspense } from 'react';

const RemoteApp2 = React.lazy(() => import("app2/App"));

export default function App() {
  return (
    <div>
      <h1>App 1</h1>
      <p>Below will be some content</p>
      <hr/>
      <Suspense fallback={'Loading App 2'}>
        <RemoteApp2 />
      </Suspense>
    </div>
  );
}

But when I start the application I get the next error:

Uncaught Error: Shared module is not available for eager consumption: webpack/sharing/consume/default/react/react?1bb3
    at Object.__webpack_modules__.<computed> (consumes:133)
    at __webpack_require__ (bootstrap:21)
    at fn (hot module replacement:61)
    at Module../src/index.js (main.bundle.a8d89941f5dd9a37d429.js:239)
    at __webpack_require__ (bootstrap:21)
    at startup:4
    at startup:6

If I extract everything from index.js to bootstrap.js and in index.js will do

import('./bootstrap');

Everything works just fine.

This confuses me as official docs and blog posts from the creator states that you can do either bootstrap.js way OR declare dependency as an eager one.

Would appreciate any help/insights on why it does not work without bootstrap.js pattern.

Here is a link to full GitHub sandbox I was building: https://github.com/vovkvlad/webpack-module-fedaration-sandbox/tree/master/simple

Answer

volk picture volk · Mar 15, 2021

Just to make it clear for those who might miss the comment to the initial answer:

It seems like the main reason of why it failed initially was that remoteEntry.js file was loaded after the code that actually runs the host app.

Both bootstrap.js approach and adding direct script <script src="http://localhost:2002/remoteEntry.js"></script> to the <head></head> tag has exactly the same outcome - they make remoteEntry.js be loaded and parsed BEFORE the main app's code.

In case of bootstrap the order is next:

  1. main_bundle is loaded
  2. as the main code is extracted into bootstrap.js file - remoteEntry.js is loaded
  3. bootstrap.js is loaded which actually runs the main app

enter image description here

with proposed variant by Oleg Vodolazsky events order is next:

  1. remoteEntry.js is loaded first as it is directly added to html file and webpack's main_bundle is being appended to <head></head> after remoteEntry link
  2. main_bundle is loaded and runs the application

enter image description here

and in case of just trying to run app without bootstrap and without hardcoded script in the <head></head> main_bundle is loaded before remoteEntry.js and as main_bundle tries to actually run the app, it fails with an error:

enter image description here