Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upgrading to wp5 beta 20 breaks the application due to change in Module Federation remote logic (Overrides fail) #2

Open
tsukhu opened this issue Jul 8, 2020 · 8 comments

Comments

@tsukhu
Copy link

tsukhu commented Jul 8, 2020

Hi @jherr ,

I really liked this approach where you can dynamically load Module Federation WP5 components into an existing app that currently is using the older version of webpack . But looks like that the internal implementation of Module Federation has changed, due to which the overrides logic does not work anymore and we get this error in the dashboard app

TypeError: window[scope].override is not a function
RemoteReactComponent
C:/workspace/tkdash/wp5-dashboard/dashboard/src/App.js:52
  49 | 
  50 |  if (ready) {
  51 |    const o = global.__webpack_require__ ? global.__webpack_require__.o : {};
> 52 |    window[scope].override(
     | ^  53 |      Object.assign(
  54 |        {
  55 |          recoil: () => Promise.resolve().then(() => () => require("recoil")),

I tried to look at the new remotes that are generated but was not able to figure this one out. It would be great if you can shed some light on this as the mode of dynamically loading Module federation remotes was a great idea 💡

To demonstrate this I have forked your project and upgraded the webpack version to the latest version. Also made some minor tweaks to make it work on a Windows machine :-)

Here is the repo to reproduce the issue
https://github.com/tsukhu/wp5-dashboard

@Xantier
Copy link

Xantier commented Jan 15, 2021

Investigating this a bit. It looks like this PR webpack/webpack#10960 removed the overrides global and replaces it with share scope which requires a different approach to make older webpack share the correct shared modules to remotes. The approach for this would be (apparently) to create a sidecar webpack5 build that does the negotiation of shared modules as done in here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs-sidecar

@Xantier
Copy link

Xantier commented Jan 15, 2021

On closer inspection the above comment isn't fully correct. One can still modify the loading of shared libraries on any app without the need for a sidecar. That can be done by using the init method that is in the remote entry (window[scope].init() in the example code in this repo) and passing an object of dependencies in there with (possibly?) required version information. Like so:

  const Component = React.lazy(() =>
    new Promise((moduleResolve) => {
      const react = require("react");
      const shareScope = {
        react: {
          [react.version]: {
            get: () =>
              new Promise((sharedModuleResolve) => {
                sharedModuleResolve(() => react);
              }),
            loaded: true,
            from: "webpack4",
          },
        },
      };
      new Promise((containerResolve) => {
        containerResolve(window[scope].init(shareScope));
      }).then(() => {
        window[scope].get(module).then((factory) => {
          moduleResolve(factory());
        });
      });
    })
  );

@hb-rapid
Copy link

hb-rapid commented Feb 2, 2021

what is the conclusion ? @jherr @tsukhu ?
I saw it was also asked in the youtube and you mentioned it is possible

@TheNando
Copy link

TheNando commented Mar 9, 2021

I followed @Xantier's pattern and I feel like I'm almost there. I'm still getting some issues. Apparently, the remote entry is expecting a function (that returns the module, I assume?) but it's getting a promise. I tried changing up the get() to not use the promise, but that didn't work either.
https://gist.github.com/TheNando/fb7ac0bb4b781d6c8969bd8840aa1834

image

I'm on the latest version of Webpack 5, and my federated module is sharing a few other things as well, like react-dom, react-router, and styled-components. They are all set to eager in the federated module plugin config. Not sure what else to do here.

@TheNando
Copy link

TheNando commented Mar 9, 2021

I did make a little progress on this. I thoroughly compared my federated modules to the ones in this project and noticed a difference. I was setting eager and singleton in the config. I commented that out:

// In webpack config on federated modules
shared: [
            // {
            //     react: {
            //         eager: true,
            //         singleton: true,
            //     },
            // },
            // {
            //     'react-dom': {
            //         eager: true,
            //         singleton: true,
            //     },
            // },
            'react',
            'react-dom',
            'react-router',
            'react-router-dom',
            'styled-components',
]

Getting a new error now.

remoteEntry.js:41 Uncaught (in promise) Error: Container initialization failed as it has already been initialized with a different share scope
    at Object.init (remoteEntry.js:41)
    at RemoteComponent.jsx:90
    ...

Gonna keep pecking away at it.

@basile-savouret
Copy link

Hello everyone I've been working on this code for a few hours. I finally have a working solution based on @TheNando's one.
My module federation look like this in the shared app:

new ModuleFederationPlugin(
      {
        name: 'Person',
        library: { type: 'var', name: 'Person' },
        filename:
          'remoteEntry.js',
        exposes: {
          Person:'./src/App',
        },
        shared: {
          "react": {
            singleton: true,
            strictVersion: false,
          },
          "react-dom": {
            singleton: true,
            strictVersion: false,
          },
          "@material-ui/core": {
            singleton: true,
            strictVersion: false,
          },
        }
      }
    )

It's important to have singleton: true so that webpack will inject the dependency correctly.
I have made this function to inject the depencies in the module:

const shareModuleDeps = async (scope: string, moduleDeps?: {[dependencyName: string]: Dependency}) => {
    if (!moduleDeps || moduleDeps.length <= 0 ) return
    let sharedScope: {[key: string] : object} = {}  
    for (const dep in moduleDeps) {
        const required = moduleDeps[dep]
        sharedScope[dep] = {
            [required.version]: {
                get: () =>
                    new Promise((resolveShared) => {
                        resolveShared(() => required);
                    }),
                loaded: true,
            }
        }
    }
    //@ts-ignore
    await window[scope].init(sharedScope)
}

Hope it is helpfull for someone !

@ojathelonius
Copy link

ojathelonius commented Jul 5, 2021

Hi @basile-savouret, I might be misunderstanding your solution but aren't you missing a require in the Promise from your last snippet?

new Promise((resolveShared) => {
    resolveShared(() => require(required));
}),

@basile-savouret
Copy link

basile-savouret commented Jul 6, 2021

Hi @ojathelonius, i made a generic function where the required modules are passed outside it like so:

const react = require("react")
const reactDom = require("react-dom")
const mui = require("@material-ui/core")

const moduleDeps = {
        "react": react,
        "react-dom": reactDom,
        "@material-ui/core": mui,
      }

shareModuleDeps("TheScope", moduleDeps)

In my function i loop through the object and get the name of the dependency in the variable dep and the module itself in the variable required (which is already required).

I'm using react based on webpack so i can't require a dependency dynamically like that:

 const deps = [
    "react",
    "react-dom"
  ]

  deps.forEach((dep) => {
    require(dep)
  })

That's why i made this function where the dependencies needs to be required outside.

i suppose that you can do a dynamic require if you are not in a webpack environnement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants