-
-
Notifications
You must be signed in to change notification settings - Fork 8.8k
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
Federated Modules: Dynamic Remotes #11033
Comments
You can dynamically load remote modules using this: // Initializes the share scope.
// This fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__("default");
// TODO: load the script tag somehow. e. g. with a script loader
const container = window.application_a;
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default);
const module = await container.get("./module"); |
|
Thanks fixed |
In the actual project, services in remote host, and we may be exposed multiple module, can we do this? const { Routes, Button } = await __webpack_init_module_federation_remote('https://localhost/order/remoteEntry.js'); |
@shaodahong It's completely possible to dynamically import modules; we use the following code to load the compiled code into the
Note this doesn't seem to work properly when you have multiple plugins with identical component names running in development mode with HMR. |
yup, it's broken when using dev hmr |
HMR and MF do not work currently. |
|
hi @ScriptedAlchemy and @sokra Just wanted to confirm if it is actually possible to load dynamically federated modules from a dynamically loaded remote. It seems it is possible. I will give it a try to the code @sokra put above, but would be nice to get a confirmation from you that it is actually possible. |
@royriojas check my code above: #11033 (comment). We have this working fine in dev loading federated modules just using a URL. |
You're likely not running remote containers over wds. The issue appears when WDS is used to host containers. I've experienced this issue and it still remains. Though I've not tested with webpack-cli/serve I've not looked into webpack dev server or this issue further. A workaround you could apply is to add startup code and manually define the container to the window. Since WDS adds a module to the end, we can use startup code to add our own module and basically set it ourselves. |
Hi @ScriptedAlchemy based on all that I've found I came with this for dynamic loading modules from remotes (without hardcoding them in the webpack config) https://github.com/royriojas/mfe-webpack-demo/tree/attempt_dynamic But here is something very weird I noticed:
If you comment this line here: https://github.com/royriojas/mfe-webpack-demo/blob/attempt_dynamic/packages/app-01/src/index.jsx#L8 you will see that the app fails to load the For now preloading it solves my issue, but it would be really nice to not need that. The error that I get when I don't preload that given remote is that there are 2 React versions in the page :( |
This is the error, if @sokra or @ScriptedAlchemy or anybody knows why does this error happen.
|
You can't unload react and magically load another copy. Webpack can only negotiate versions sharing of singletons upfront. Only use module-federation-examples for configuring. Your webpack config is old and you're sharing api isn't right for what you're trying to do. Please refer to my repos under the MF organization. One look at your repo, you're not using semver sharing, webpack probably doesn't know if it can / should share. React also needs to be a singleton to function. You can upgrade to react 17 if you need multiple copies of react for whatever reason |
@ScriptedAlchemy I don't want to load two versions of react. I actually want to share the same I'm not trying to share anything yet. I was just trying to understand what was possible to do with module federation. I will take a look a semver sharing. Thank you for your feedback. |
Thanks for your feedback @ScriptedAlchemy. Using a shared singleton fixed all the issues I had. Thank u. 👍 |
Yeeeeee!!! 🥳 Thrilled it resolved the problem. Need to know if I should actually update my articles or not 😂😂 |
@ScriptedAlchemy Old example forked from https://github.com/mizx/mfe-webpack-demo which was using the beta version |
Ahhh yep. He helped me build out some examples, he was the first outside user. Module federation examples was actually based on his initial effort to help us make example projects. Will DM and see if he wants to drop a link or update some examples |
For anyone googling into this, the module federation documentation has been updated and a example added:
So I have issue, I have code loading in modules 'synchronously' so something like: import urls from "remoteModule/constants";
async function getSomething(params) {
// ...Other processing
return fetch(urls.aThing);
} Unless I'm misunderstanding something, the technique used in the example wouldn't work for what I'm trying to do? Or I would have to restructure my code to be something like: async function getSomething(params) {
const { aThing } = await someKindOfDynamicLoading("remoteModule/constants");
// ...Other processing
return fetch(urls.aThing);
} The thing is I really don't want to do that, I guess the argument could be made that it should be explicit whether a module is being loaded externally via a function like Is there a way to modify the example(s) given so that I could load my modules synchronously? Or am I'm using federated modules in a 'unintended'/bad way? Update: Several community members helped and figured out the issue mentioned in this comment, a example is now available under: advanced-api/dynamic-remotes-synchronous-imports |
This issue had no activity for at least three months. It's subject to automatic issue closing if there is no activity in the next 15 days. |
I am able to load remote module dynamically with the above approach. Just curious if I can pass props to the remote component? If yes, refer some sample code please! |
const factory = await container.get(moduleToLoad); // where moduleToLoad is the path to the module (including a `/.` if I recall correctly
const Module = factory();
Module.default(/* pass props here*/); // this is the function, or component or whatever that is being federated |
@royriojas thank you for your reply. I was trying the follow the steps you mentioned. But now luck. In my component I have a property called 'name' with a type of string I was trying to pass the data like Module.default({name:'abcd'}) which did not work Also I tried with Module.default(name='abcd') which is also not working. Can you please help me here. |
How are you exporting your component? is that the default? otherwise you
might not need the default keyword
Roy Ronald Riojas Montenegro
…On Mon, Apr 12, 2021 at 5:04 PM Somnath ***@***.***> wrote:
const factory = await container.get(moduleToLoad); // where moduleToLoad is the path to the module (including a `/.` if I recall correctlyconst Module = factory();
Module.default(/* pass props here*/); // this is the function, or component or whatever that is being federated
@royriojas <https://github.com/royriojas> thank you for your reply. I was
trying the follow the steps you mentioned. But now luck. In my component I
have a property called 'name' with a type of string I was trying to pass
the data like Module.default({name:'abcd'}) which did not work Also I tried
with Module.default(name='abcd') which is also not working. Can you please
help me here.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#11033 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABABWSINU5VQRE6Z2QPYSTTIODCXANCNFSM4N5DZLIA>
.
|
I am exporting as default. Here is the snippet : import * as React from "react"; I am loading the above component from my host app port 3001 and remote component is running in port 3002 |
Could you share your webpack config for remotes?
Roy Ronald Riojas Montenegro
…On Mon, Apr 12, 2021 at 5:22 PM Somnath ***@***.***> wrote:
How are you exporting your component? is that the default? otherwise you
might not need the default keyword Roy Ronald Riojas Montenegro
… <#m_2640954308268869088_>
On Mon, Apr 12, 2021 at 5:04 PM Somnath *@*.**> wrote: const factory =
await container.get(moduleToLoad); // where moduleToLoad is the path to the
module (including a /. if I recall correctlyconst Module = factory();
Module.default(/ pass props here*/); // this is the function, or
component or whatever that is being federated @royriojas
<https://github.com/royriojas> https://github.com/royriojas thank you for
your reply. I was trying the follow the steps you mentioned. But now luck.
In my component I have a property called 'name' with a type of string I was
trying to pass the data like Module.default({name:'abcd'}) which did not
work Also I tried with Module.default(name='abcd') which is also not
working. Can you please help me here. — You are receiving this because you
were mentioned. Reply to this email directly, view it on GitHub <#11033
(comment)
<#11033 (comment)>>,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AABABWSINU5VQRE6Z2QPYSTTIODCXANCNFSM4N5DZLIA
.
I am exporting as default. Here is the snippet :
import * as React from "react";
interface OwnProps {
name: string;
}
const Widget: (React.FC) = props => {
const renderMain = (): JSX.Element => {
return (
<div
// my additional implementation goes here
{props.children}
);
}
return renderMain();
}
export default Widget;
I am loading the above component from my host app port 3001 and remote
component is running in port 3002
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#11033 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABABWRZMCU7N3GFD7RA3ZLTIOFGBANCNFSM4N5DZLIA>
.
|
const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { |
For people not sure how to implement this for React lazy loaded components, assuming you have the following config available (hardcode it, preload it, add using SSR, or whatever other method of choice): /* This is basically the same object you'd have in webpack config */
window.__remotes__ = {
'myapp': 'myapp@http://localhost:9001/remoteEntry.js'
}; do: declare global {
interface Window {
__remotes__: Record<string, string>;
}
const __webpack_init_sharing__: any;
const __webpack_share_scopes__: any;
} async function dynamicImport (path: string) {
const [ remoteName, remoteUrl ] = Object.entries(window.__remotes__).find(([ r ]) => path.startsWith(r));
if (!remoteName) throw new Error(`URL not configured for remote '${path}'.`);
if (remoteUrl.split('@').length !== 2) throw new Error(`URL misconfigured for remote '${path}'`);
const [ moduleName, moduleUrl ] = remoteUrl.split('@');
await __webpack_init_sharing__('default');
await new Promise<void>((resolve, reject) => {
const element = document.createElement('script');
element.src = moduleUrl;
element.type = 'text/javascript';
element.async = true;
element.onload = () => {
element.parentElement.removeChild(element);
resolve();
};
element.onerror = (err) => {
element.parentElement.removeChild(element);
reject(err);
};
document.head.appendChild(element);
});
const container = window[moduleName];
await container.init(__webpack_share_scopes__.default);
const component = `.${path.replace(remoteName, '')}`;
const factory = await container.get(component);
return factory();
} and then just replace const MyComponent = React.lazy(() => dynamicImport('myapp/MyComponent')); |
Btw, @sokra @ScriptedAlchemy - are these |
nope, but we could add that to our own typings... |
@sokra @ScriptedAlchemy I'm able to load the component dynamically, however, it seems like host container cannot share the React Context with the Remote container. Do you have any idea why? I have been stuck at this for some time I tried to combine these 2 examples to enable the remote containers to consume context from the host container, but it doesn't seem to work: |
It should work. Is more than one copy of react getting loaded? If not then I don't see a reason why it wouldn't work. Are you perhaps not sharing something that depends on react context. Like a node module |
Thank you @ScriptedAlchemy for your reply. Really appreciate it!
I'm loading just one copy of react, I'm setting the webpack config for both host and remote containers to be
If I understand correctly,
May I clarify what do you mean by this? EDIT: |
Regarding dynamic remotes. They might be getting initialized too late or early, a remote can only be initialized once so if it depends on something from another remotes share scope as a singleton, the scare scopes cannot be reinitialized with more modules at a later stage. |
I have tried the mentioned by @grzegorzjudas, but I can't get past this error Is there something else I am needing to expose these values? |
That won't work if your consuming app doesn't have anything shared. If you're not planning to share any modules from the consuming app then you don't want to init sharing since there's nothing to share. |
I created a library that abstracts some of the business logic and works for suspense, loadable or others https://github.com/MichaelDurfey/mf-dynamic-remote-component |
@MichaelDurfey nice. Would love to house this under the federation git organization if you’d want to maintain it there. Utilities like this make or break the experience :) |
@ScriptedAlchemy Sounds good to me! |
@ScriptedAlchemy I Want to know how we can dynamically fetch remotes on server-side (in node server) |
The node technology is currently proprietary. There's a package on npm called node-mf, it's the least buggy option. I do plan to release module-federation/node in the near future. Our internal technology is the the most stable and powers many enterprise clients. It's also what's powering SSR support on next.js and is the core tech running module-federation/aegis (hexagonal backend architecture that enables federation for any language on any compute primitive) Also works sync or async |
How do you handle errors from the script tag? |
@tzachbon
I believe the remote is trying to fetch its resources on the host instead of its own server. Webpack is clever enough to resolve this for you. See here |
If you only need dynamic connection code, im working on delegate modules - have just released it for next.js and will work on normal support. This lets you use static imports, but can dynamically resolve the location of the remote container as needed. module-federation/module-federation-examples#2756 |
@tzachbon You could return a custom function that wraps return () => {
try {
factory();
} catch (err) {
// handle the error somehow
}
}; |
I got stuck with Vite for few days originjs/vite-plugin-federation#518 (and I started to be desperate) until I found this issue @MichaelDurfey library. This library was a gem to me 👍 Thanks again! |
Currently, I have a solution to the problem of dynamic remote: The general effect is as follows: // You can pull the remote url configuration here through code or requests
window.remote_urls = {
app: 'http://localhost:3000/',
}
import('./bootstrap') And you can use it as follows: // Suppose./Button is exported in the app
import Button from 'remote:app/Button'
function Demo() {
return <Button />;
} I have currently implemented this pattern in my own scaffolding, and if you are interested, I will take the time to introduce it completely as above. |
Feature request
What is the expected behavior?
It seems that although dynamic imports are supported in federated modules, and it is possible to obtain
remoteEntry.js
on runtime, the remotes are still statically defined in webpack config.Here, application_b is statically defining application_a as a remote.
Is there a way that remotes can be defined dynamically instead?
What is motivation or use case for adding/changing the behavior?
A plugin system that spreads its plugins over many microservices.
How should this be implemented in your opinion?
Similar to lazy loading in code splitting, could dynamic imports (remotes) be preemptively prepared?
Are you willing to work on this yourself?
I am not very well-versed in webpack's internals, unfortunately.
The text was updated successfully, but these errors were encountered: