module-federation / core

Module Federation is a concept that allows developers to share code and resources across multiple JavaScript applications
https://module-federation.io/
MIT License
1.42k stars 212 forks source link

@module-federation/enhanced: shareInfoRes.get is not a function while trying to load shared dependency #2497

Closed fcano-ut closed 4 months ago

fcano-ut commented 4 months ago

Describe the bug

Hi!

I was doing an experiment with enhanced, first of all sorry if this is more like a problem of my mis-understanding of the tool, more than a bug.

I want to achieve the following:

My config looks rougly like this:

// host
new ModuleFederationPlugin({
  name: 'host',
  shared: {
    '@usertesting/ui-toolkit': {
      eager: true,
      requiredVersion: packageJson.dependencies['@usertesting/ui-toolkit']
    }
  }
})

// micro-frontend
new ModuleFederationPlugin({
  name: 'microfrontend',
  shared: {
    '@usertesting/ui-toolkit': {
      requiredVersion: packageJson.dependencies['@usertesting/ui-toolkit']
    }
  }
})

I'm not sure if that combination is supported, or if I should instead use init from @module-federation/enhanced. But assuming that combination is supported, then I'm finding what I believe is a bug.

I'm trying to load all the versions of our UI toolkit from all micro-frontends, and start the host and all the apps with the latest version. That means that if the host has version ^1.1.0 and a micro-frontend ^1.2.0, I want the host to use 1.2.0.

My host entrypoint looks roughly like this:

loadUiToolkit()
  .then(() => {
    import('./bootstrap'); // initialize the app
  });

async function loadUiToolkit() {
  registerRemotes([
    {
      name: 'microfrontend',
      entry: mfManifestUrl,
    }
  ]);

  await preloadRemote([{
    nameOrAlias: 'microfrontend',
    depsRemote: [
      {nameOrAlias: '@usertesting/ui-toolkit'},
    ]
  }]);

  await loadShare('@usertesting/ui-toolkit');
}

To my understanding this should do the trick:

However I have the following error: shareInfoRes.get is not a function while trying to load shared dependency (failing in the loadShare function)

Screenshot 2024-05-16 at 09 06 24

I tried swapping preloadRemote with loadRemote, but even if I load the remote beforehand, the loadShare function fails with the same error

  // ...
  await loadRemote('microfrontend');
  await loadShare('@usertesting/ui-toolkit'); // ❌ shareInfoRes.get is not a function while trying to load shared dependency

I would like to know if (a) What I'm trying to do is possible, and (b) if this is a bug in my setup or an actual bug in the library

Thank you for reading this far.

Reproduction

I can create a reproduction example but I first want to validate if my setup is even supported (using webpack config to register, and enhanced plugin to load)

Used Package Manager

npm

System Info

Validations

ScriptedAlchemy commented 4 months ago

Okay this one took me a moment to understand. Here's what happens. Your hosts have no reference of any remotes existing. So when they start, they have no initialization phase for remotes. You need to use init() instead. If you have one remote we would recognize and add the runtime code. Without it we are not a host, just someone who shares module. If you use init. Just add the remotes. The host will keep existing share scope if empty. This is my theory.

Ideally using a runtime plugin would be a better location but if you need it really dynamic and not just like need url to change for various envs. Then I'm thinking that you need to call init. You could also use a runtime plugin. And use resolveShare hook to control the shared module resolution as you wish. My example repo has a runtime plugin folder with several hooks examples.

ScriptedAlchemy commented 4 months ago

Share info res. I had this happen the other day working on esbuild. It was caused by remote not being loaded when call is made if I remember. I think if you use init + shared at compile time - it'll work. That's usually what I do because it's easier to use the compile time sharing and dynamic remotes.

fcano-ut commented 4 months ago

I was re-reading the docs and it makes sense, I had understood incorrectly before.

If I understand correctly, then I need to use Module Federation plugin, but also init the runtime plugin. I guess I also need to use the same name in both. I tried changing the runtime code to:

init({
  name: 'host',
  remotes: [{
    name,
    entry: mfManifestUrl
  }],
}); // init and register remote
await preloadRemote(...) // preload remote
await loadShare(...)

However I still get the same issue, and now the app doesn't work for some reason 😅 I'll try the approach of using plugins. Seems like it would be more manageable.

ScriptedAlchemy commented 4 months ago

if you can make a repo we can take a look. But this is usually casued by no remote being loaded yet and attempting to call internal modules.

ScriptedAlchemy commented 4 months ago

@2heal1 is this a bug?

fcano-ut commented 4 months ago

I created a minimal reproduction example here: https://github.com/fcano-ut/module-federation-reproduction-example-2497

The reproduction steps are in the README. I hope this makes it easier to understand what I'm trying to achieve and fix the issue (if any). I appreciate a lot if you can take a look

Here's a screenshot of the apps running and the error that appears on the console, and this is the line that throws the error

Screenshot 2024-05-21 at 14 59 21
fcano-ut commented 4 months ago

Quick update, before you waste time trying to reproduce this... Turns out the error stopped popping up as soon as I added this:

Screenshot 2024-05-21 at 16 57 36

Basically there seems to be a difference between simply importing package.json and importing and using a function inside a module. I was using plugins to inspect shared dependencies, and with only the package.json import the module was not being registered as a share dependency at all.

I still cannot use this information to solve my particular use case, that is, I cannot pre-load lodash and use the latest version..., but the reported issue of shareInfoRes.get is not a function seems to be happening due to my weird reproduction example, and doesn't seem to me like it's a bug in the library