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

Runtime init and loadRemote methods are contain different remote (FederationInstance) when trying load exposed module #1942

Closed
5 tasks done
satheesh66 opened this issue Jan 10, 2024 · 23 comments

Comments

@satheesh66
Copy link

satheesh66 commented Jan 10, 2024

Describe the bug

Hi,
I have using module-federation/runtime ,

I am trying to load the remote and then trying access the exposed module , here my host is main

image

when invoke loadRemote method the FederationInstance is not holding main instead it holds other remote that is previously loaded because of the condition in @module-federation/runtime's init method in line number 1684, since we declared the FederationInstance variable outside of the init and loadRemote method's scope, so it holding previous remote.

image

if i change the condition to if(FederationInstance !== instance) it's working as expected,

Reproduction

Used Package Manager

npm

System Info

Os : Mac os sonoma 14.1.2,
Browser : Brave

Validations

@ScriptedAlchemy
Copy link
Member

@2heal1 any comments about this?

@zhoushaw
Copy link
Collaborator

@satheesh66 Can you describe the problem with reusing a global instance?

@satheesh66
Copy link
Author

satheesh66 commented Feb 2, 2024

@satheesh66 Can you describe the problem with reusing a global instance?

we trying to use runtime plugin as hybrid mode
we are injecting promises(promise based dynamic imports) for some services in build time and loading some services using runtime plugin

in this approach when i trying to invoke init with host for some remote service the federation instance already holding previously loaded remote(via promise) instead host

@zhoushaw

@satheesh66
Copy link
Author

satheesh66 commented Feb 3, 2024

I need some clarification
when i invoke init method from service remote1 the FederationInstance is holding remote1 service options, i am only able to call init from host service , is this expected?

I am triggering init and loadRemote from other services(not from host) in this scenario FederationInstance is not taking up the host instance, it holds remote container instance which is where we triggering init. so that loadRemote unable to return the module.

@zhoushaw @ScriptedAlchemy

@satheesh66
Copy link
Author

any update on this? @zhoushaw @ScriptedAlchemy

@zhoushaw
Copy link
Collaborator

@satheesh66 I'm sorry I didn't understand what you meant. Can you provide a warehouse? How should the normal performance be

@ScriptedAlchemy
Copy link
Member

ScriptedAlchemy commented Mar 5, 2024

Ahh i see, @2heal1 @zhoushaw i think what he means is re-init with new options does not merge the options together

@satheesh66
Copy link
Author

satheesh66 commented Mar 7, 2024

Ahh i see, @2heal1 @zhoushaw i think what he means is re-init with new options does not merge the options together

yes, when i trying to invoke init from remote services i can't get the FederationInstance as host instead i get the remote service's instance which is were i call init from

@2heal1 @zhoushaw @ScriptedAlchemy

@2heal1
Copy link
Member

2heal1 commented Mar 8, 2024

Yes , currently , if you invoke init many times , it will merge all options except remotes and plugins .

remotes and plugins will check whether it has been registered by the identifier name .

@2heal1
Copy link
Member

2heal1 commented Mar 8, 2024

In this case , you want to update the remote entry which has been registered before ?

@zhoushaw @ScriptedAlchemy Should we allow users to merge remote instead of skip ? And how about adding registerRemotes api to help users register remotes quickly instead of calling init again ?

@2heal1
Copy link
Member

2heal1 commented Mar 8, 2024

registerRemotes can allow override existed remote .

registerRemotes: (remotes: Remote[], override?:boolean) => void;

interface Remote {
  name:string;
  entry:string;
  override?: boolean
}

But I'm not sure if we are allowed to control whether a single remote can be override.

Maybe it's enought that we just add override to all remotes.

@ScriptedAlchemy What do you think?

@ScriptedAlchemy
Copy link
Member

ScriptedAlchemy commented Mar 8, 2024

{init,registerRemote} from runtime?

something like that could work, but be warned - sideloading remotes after federation is already initalized the "initial" remotes it had, if you add more remotes later, the already initalized containers from app startup cannot "see" updated share scopes as they are already running and using shared modules.

So i agree we need a way to add more remotes to federation, BUT be warned that there are race conditions that can occur.

For example

You have 3 remotes. A, B, C

A is the host
You start on /home, Home (A) import B/button.
You go to /about, A imports C (lazy / dynamic remote injection(, C imports someting else that needs Redux - singleton
You go to /other, which uses B again, and this page also needs redux singleton.

App will fail because B was init() with share scope that existed, A's share scope, B cannot see what new shared modules C added because B was "sealed" already

When you loaded C, it added redux but B cannot see that because it was already inialized with the share scope known at that point in time. So it will load another redux copy.

@ScriptedAlchemy
Copy link
Member

Okay we are adding a new api for addRemote

It should do what you want.

@2heal1
Copy link
Member

2heal1 commented Mar 8, 2024

Okay we are adding a new api for addRemote

It should do what you want.

Yes , the conclude as follows:

  • init can not merge remote , even if the remote not loaded . Because we want to make data track more easier.
  • add new api called registerRemotes , it is used for add new remotes , and it will have a param called force , if users set force: true, it will merge remote(include loaded remote) , and console.error to tell users this action may have risks

I will open a pr for this feature during the following days

cc @zhoushaw

@ScriptedAlchemy
Copy link
Member

Console warn not error.

@2heal1
Copy link
Member

2heal1 commented Mar 8, 2024

Console warn not error.

yes :D

@satheesh66
Copy link
Author

HI @ScriptedAlchemy @2heal1 ,
Thank you for the quick actions, but i am not sure the above solution will solve my problem so i will explain my requirement and help me out how can solve my problem with this solution.

Note:
We are using hybrid mode primary services are injected in build time(using promise based dynamic injection) and other services are injected in runtime (we are planning use runtime methods.).

using - "@module-federation/enhanced": "^0.0.10"

My Scenario :

main service (Host)

widgets service is injected to main in build time using promise based dynamic injection, so that i can directly using that in main, and i am trying init and load primitives service in runtime using a component that resides in widgets service.

import { LazyRemote } from "widgets/lazyremote";

function App(){
   return <LazyRemote module={ "primitives/button" } componentName={ "Button" } />
}

widgets service (Remote)

The init and loadRemote will work with widgets service's FederationHostInstance but i need to add the remotes to main service. i assume using registerRemotes method adds the remote services to the widgets service's instance instead of main(host instance), is this expected or am i missing something

import { init, loadRemote } from "@module-federation/runtime";


async function lazyRemoteModule(module) {
  let [remote] = module.split("/");
  let remoteUrl = `${window.origin}${constructAssetUrl(remote)}`;

  init({
    name: "main",
    remotes: [
      {
        name: remote,  //primitives
        entry: remoteUrl  // https://[bucket url]/primitives
      }
    ]
  });

  return await loadRemote(module);
}

export function LazyRemote(props) {
  let { name, module, componentName, ...restProps } = props;

  let [Component, setComponent] = useState(() => () => {
    console.info("Loading remote module");
    return null;
  });

  useEffect(
    function loadComponent() {
      lazyRemoteModule(module)
        .then((module) => {
          setComponent(() => module[componentName]);
        })
        .catch((err) => {
          setComponent(() => () => (
            <p>
              Failed to load external module
              <details>
                <summary>View more</summary>
                <p>{err.toString()}</p>
              </details>
            </p>
          ));
        });
    },
    [module, componentName]
  );

  return <Component {...restProps} />;
}

primitives service(remote)

export function Button(){
    return <button> click me </button>
}

@2heal1
Copy link
Member

2heal1 commented Mar 10, 2024

So you have declared remote(widgets service) in your build config , and you want to use loadRemote in runtime .
I want to know the detail:

  • Will the remote(widgets service) entry url keep the same ?
    • If yes , you don't need to call init again , you can just use loadRemote directly , no need to init again.
    • If no , you can use registerRemotes([{name,entry}],{force:true}) to override the remote . The specify preview version as follows:
      @module-federation/enhanced@0.0.0-next-20240310052320
      @module-federation/runtime@0.0.0-next-20240310052320

@satheesh66
Copy link
Author

satheesh66 commented Mar 11, 2024

yes, my Widget service's entry file is already in browser but Primitives service is not in the browser, so i need to init(register remote url) and then loadRemote to get module from Primitives service.

now I just register my Primitive service inside widgets service it will registers remotes to widgets service and then i can use loadRemote, as per your suggestion ,am i correct @2heal1

@2heal1
Copy link
Member

2heal1 commented Mar 13, 2024

@satheesh66 yes, if the remote name is the same , you need to use registerRemotes .

If the issue can not be solved , you can give the reproduce repo , and i will take a further look.

@satheesh66
Copy link
Author

Issue solved. Thank you @2heal1 @ScriptedAlchemy. looking forward to registerRemote stable release.

@2heal1
Copy link
Member

2heal1 commented Mar 15, 2024

v0.0.17 release
#2193

@2heal1 2heal1 closed this as completed Mar 15, 2024
@AdarshJais

This comment was marked as resolved.

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

5 participants