loadable-components icon indicating copy to clipboard operation
loadable-components copied to clipboard

feat: add moduleFederation option

Open brunos3d opened this issue 2 years ago • 20 comments

Summary

This PR was created to add support for Module Federation it is originated from this issue: https://github.com/gregberge/loadable-components/issues/926

It aims to add a new option to the loadabel babel-plugin called moduleFederation, it's a boolean option so we just need to set it to true to enable the support for Module Federation.

{
  "plugins": [
    ["@loadable/babel-plugin", { "moduleFederation": true }],
    // ...
  ]
  // ...
}

This PR also adds some JSDoc typedefs to the plugin, which will help users to understand the plugin better.

Test plan

To test it you can clone the sample project using this branch as base for the test

The branch with our samples fixed https://github.com/brunos3d/loadable-module-federation-react-ssr/tree/demo-loadable-fix

The PR of the fixed branch to check the diff https://github.com/brunos3d/loadable-module-federation-react-ssr/pull/1

brunos3d avatar Dec 14 '22 23:12 brunos3d

Per my understanding, the essence of this change is to disable code splitting for federation, not to "support" federation keeping the current constraints.

The question is how to ensure this configuration is used properly - only for server-side bundle.

theKashey avatar Dec 15 '22 04:12 theKashey

woud we set a DefinePlugin like 'process.isBrowser' and if its false webpack could remove the dead code? Not sure if that would work or not

ScriptedAlchemy avatar Dec 15 '22 17:12 ScriptedAlchemy

@theKashey yeah, your right about using it only on server-side, I will work to make it work as expected, don't have ideas on how we can make it

brunos3d avatar Dec 15 '22 17:12 brunos3d

@theKashey just removed every unrelated change and forced push it to avoid overwriting the git history, I also removed the federated option, now we need to figure out how to ensure that it uses require(ID) only on server-side

brunos3d avatar Dec 15 '22 17:12 brunos3d

👍 @brunos3d, now the change is short and sound.


Define Plugin stuff might now work, as long as in case of process.isBrowser is not defined it will be kept "as it", while we need condition to be removed to hide require from webpack.

What we can do - add a check for browser/clientside/webpack-target and ➡️ throw an error in case of misconfiguration. So not fail the build, but fail the runtime.

That would put customers without tests into a little dangerous situation, so we might also not document new property for now

theKashey avatar Dec 15 '22 23:12 theKashey

@theKashey just added the suggested warnings. As I'm not too used to babel/webpack plugins I will ask you for code suggestions to solve this issue without workarounds :smile:

brunos3d avatar Dec 20 '22 17:12 brunos3d

Hey, folks as we didn't find a proper way to pass the serverSideModuleFederation option from the LoadablePlugin (webpack) to babel, I just decide to inject the value to process using directly, as it will be used only during the compilation time, I can't see any problems on that solution. Excited to see your suggestions about it cc: @theKashey @bwhitty @ScriptedAlchemy

brunos3d avatar Dec 23 '22 20:12 brunos3d

@theKashey any input here - would be great to move this forwards if we can

ScriptedAlchemy avatar Dec 27 '22 22:12 ScriptedAlchemy

Before we processed with the merge, I want to ask one question - Where is the best place to handle this stuff? Having the problem of "federation gap", ie one's inability to simply require yet undefined stuff, there are two options:

  • make a sync require in the loadable (this PR). That would solve problem only for loadable and in a very specific way.
  • preload all known components in the loadable (not which are not currently tracked)
  • preload all federated sources via ModuleFederationPlugin. Ie go via all known remotes and import them before starting a server

theKashey avatar Dec 28 '22 22:12 theKashey

This does module federation for all modules. Should we do it as a option on loadable call?

fivethreeo avatar Jan 07 '23 00:01 fivethreeo

This does module federation for all modules. Should we do it as a option on loadable call?

It does, but should be only used on the server-side, in the past I had put it as an option for the loadable function, it was a option called 'federated'. But the problem is, MF plugin changes the way imports happen even non-federated containers get these __webpack_modules__[moduleId] is not a function errors

brunos3d avatar Jan 09 '23 12:01 brunos3d

So that is the question: if one technology should adapt to support another technology - could there be a third technology to change as well? For example, @fivethreeo is working on React 18 update with a "proper suspense" on the server side - https://github.com/gregberge/loadable-components/pull/923 - which technically can allow us to use plain import without require.resolve

theKashey avatar Jan 09 '23 22:01 theKashey

Proper suspense would work for everything moving forwards, but we would still need a solve for async-node target / lower react versions :P

ScriptedAlchemy avatar Jan 10 '23 18:01 ScriptedAlchemy

@theKashey @ScriptedAlchemy An issue we're facing right now is when we try to consume a container from some remote using loadable and the remote is down, MF tries to generate a fake remote and a fake container, but loadable throws multiple errors saying that the provided module is not a react component (cause it's an empty object), an a bunch of other errors.

image

image

image

image

By now I'm using the following workaround for local linked and patched @loadable/component

// on InnerLoadable component at the top of the "render" function

if (result && typeof result === 'object' && Object.keys(result).length === 0) {
  console.log('probably the fake container')
  result = `<span>failed to load remote container</span>`
}
// ...

It checks if the result (the loaded/resolved module) is not undefined and verifies if its an empty object (the fake container generated by MF) then it replaces it with the warning message

brunos3d avatar Jan 10 '23 20:01 brunos3d

There is a resolveComponent option you can provide to loadable, and the one capable to check imported object and throw and error (to emulate load failure, as it is a failure). Wondering if can call another check just after it and throw an error if component was resolved to nothing. That should trigger "normal" error branches (fallback or ErrorBoundary), not break createElement.

A little different issue, but quite related

theKashey avatar Jan 11 '23 09:01 theKashey

@brunos3d i might be able to help with the fake remote which returns null. We could modify node federation or allow it to throw a rejection. Generally I avoid that since throwing errors in sync imported components can cause webpack runtime to crash

ScriptedAlchemy avatar Jan 13 '23 05:01 ScriptedAlchemy

great work! @gregberge any plan to merge it soon? :)

ranshamay avatar Feb 07 '23 17:02 ranshamay

@ranshamay Probably we will not merge this PR, but I'm working on some stuff:

I will post the updates here soon as I finish everything, but its also good to keep an eye on https://valor-software.com/articles too 🚀

brunos3d avatar Feb 11 '23 16:02 brunos3d

@brunos3d I saw this example that you prepared: https://github.com/module-federation/module-federation-examples/tree/88c252fbc3fdc3262e0d8e3d03a945276bd0a165/loadable-react-16

In my server-side micro frontend application, I will use module federation to combine shared libraries (react, react-dom, axios). Is it problematic to use this? I am eagerly waiting for your answer.

oguzhanaslan avatar May 23 '23 10:05 oguzhanaslan

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Aug 13 '23 01:08 stale[bot]