vite-plugin-federation icon indicating copy to clipboard operation
vite-plugin-federation copied to clipboard

Importing federated module name via variable

Open iconag-bbasmer opened this issue 1 year ago • 14 comments

Versions

  • vite-plugin-federation: v1.2.2
  • vite: v4.2.0

Reproduction

I trying to lazy load a federated module in my React application. It works if the import link is a string, it doesn't work if the import link is stored in a variable. Example: This works: const MyModule = React.lazy(() => import("FederatedAppName/ComponentName"); This does not work: const appName = "FederatedAppName/ComponentName"; const MyModule = React.lazy(() => import(appName);

Why do I want to do this? I get the information about the modules that should be used as federated module from an API. In the API result I get the component name and the app name and all information that is required to dynamically load the required apps. To simulate this I have created a json file where I put the same information. You can find my example application here: https://github.com/iconag-bbasmer/vite-module-federation-test.git

Check the conainer/scr/main.tsx to see how I try to lazy load the federated components. You can see in line 12 and 13 of the file how things work and how they don't.

iconag-bbasmer avatar Apr 13 '23 15:04 iconag-bbasmer

I have also encountered the same problem as you, and after researching for several days, I only found two methods. I am not sure if they are correct.

Remote Vite configuration:

federation({
      name: './remote-component',
      filename: 'remoteEntry.js',
      // Modules to expose
      exposes: {
          './remote-text.js': './src/components/text/material.vue',
          './remote-image.js': './src/components/image/material.vue',
      },
      shared: ['vue']
    })

references #157

Method 1:

    const remotesMap: any = {};
    remotesMap[ './remote-component'] = {
        url: "http://localhost:4174/assets/remoteEntry.js",
        format: 'esm',
        from: 'vite'
    }
    const url = './remote-image.js'
    const comp = await __federation_method_getRemote('./remote-component',  url);
    return comp.default

Method 2:

    const url = './remote-image.js'
    const remote = await import('http://localhost:4174/assets/remoteEntry.js');
    const comp = await remote.get(url)
    return comp().default

Leen27 avatar Apr 23 '23 03:04 Leen27

I have implemented a mix of both now and it works as long as I don't use any React Hooks in the remotes. As soon as I add a Hook (useEffect or useState for example) the remote breaks. I have updated the repo mentioned above. In vapp1/src/components/AppHeader.tsx I have commented out some test code using Hooks. I this gets activated, the apps get built and run, loading the Vapp1 breaks and it gives this error:

TypeError: U.current is null

Maybe I need to do something different when loading the remotes (see container/src/components/RemoteComponent/index.tsx)?

Btw, when trying Method 1, it tells me that __federation_method_getRomote can't be found and I'm not sure how I could add this.

iconag-bbasmer avatar Apr 26 '23 16:04 iconag-bbasmer

/Method 1 equires at least one static import of a remote module. You can pre-loading a remote module, which can be empty, but cannot be a variable import, and "must" be used, otherwise function '__federation_method_getRemote' will not be included during packaging.

For example, a remote website can expose an empty object:

empty.ts

export default {}

vite.config.js

federation({
      name: './remote-component',
      filename: 'remoteEntry.js',
      // Modules to expose
      exposes: {
          './remote.js': './src/components/index.ts',
          './empty': './src/components/empty.ts'
      },
      shared: ['vue']
    })

Then in the host code:

vite.config.js

federation({
      name: 'host-app',
      remotes: {
          './remote-component': {
            external:`new Promise(resolve=>resolve('http://localhost:4174/assets/remoteEntry.js'))`,
            externalType:"promise"
        },
      },
      shared: ['vue']
    }),

load.js

// here, preload an empty file
import empty from './remote-component/empty'

// must be used
// otherwise function '__federation_method_getRemote' will not be included during packaging.
const EMPTY = empty

async function loadCompConfig(remoteUrl: string) {
    // @ts-ignore
    const remoteEntry = await __federation_method_getRemote("./remote-component", remoteUrl);

   // ... other code
}

This code is a bit hacky, but it works.

For Method 2, there are too many uncertainties.

Leen27 avatar Apr 28 '23 14:04 Leen27

Ok, I updated my test application and got one step further. :-) Now the thing is that I can't add the components that I want to load into the global remotesMap object, used by the federation plugin. I have added my remotes to my local remotesMap object but that doesn't work and I get the error message:

TypeError: remote is undefined

in __federation_method_ensure

I debugged it and indeed, the modules I want to load are not in there, only the ones that should be loaded statically. Also, whenever I add a remotesMap object to my code, the federation plugin renames its own remotesMap object to remotesMap$1 so I can't access it anymore to add my components to it.

In my example repo I have updated /container/src/components/RemoteComponent/index.tsx and added the changes from your post(s) above.

If you have an idea how to add to the remotesMap object created by the federation plugin, please let me know.

Thanks a lot!

iconag-bbasmer avatar May 02 '23 11:05 iconag-bbasmer

Ok. Another step taken and still no step forward. I forked the module federation plugin and added a __federation_method_setRemote function that I found in the discussions of the module federation plugin git repo. After trying a lot of things it works at least sometimes. :-) The issue is that it is not stable. If you refresh the browser and hit the "Load Modules" button and do both multiple times you get different results. Sometimes it loads the App1 and sometimes this one fails and the error message is U.current is null which is the same error message as described above. It comes up because I use React state in this module and it seems that React is not available at the point when the module is loading, at least that's what I suspect. Unfortunately I have no answer to this and I guess I will have to drop using Vite because I need the module federation plugin to be stable and with webpack this just works perfectly out of the box. I have updated my example repo.

iconag-bbasmer avatar May 03 '23 08:05 iconag-bbasmer

There is a discussion about this, with the setRemote methode but no confirmation yet. https://github.com/originjs/vite-plugin-federation/discussions/193#discussioncomment-5241087

T0miii avatar May 08 '23 13:05 T0miii

I have the same problem, dynamic imports not run

oscarlijo avatar Jul 03 '23 01:07 oscarlijo

@iconag-bbasmer Have you been able to achieve your first goal?

const appName = "FederatedAppName/ComponentName"; const MyModule = React.lazy(() => import(appName);

goldyfruit avatar Oct 04 '23 01:10 goldyfruit

I have not and I have stopped trying already a while ago. I dropped working with Vite as a whole and work now with Nx with Webpack to generate my remotes which works perfectly. Nx is a whole different story, I know but for me it actually is the perfect solution for working with my projects. And I think nowadays they even have an integration with Vite also. But I haven't tried it yet together with the module federation.

iconag-bbasmer avatar Oct 04 '23 05:10 iconag-bbasmer

I have not and I have stopped trying already a while ago. I dropped working with Vite as a whole and work now with Nx with Webpack to generate my remotes which works perfectly. Nx is a whole different story, I know but for me it actually is the perfect solution for working with my projects. And I think nowadays they even have an integration with Vite also. But I haven't tried it yet together with the module federation.

Thanks for your answer.

goldyfruit avatar Oct 04 '23 10:10 goldyfruit

I have not and I have stopped trying already a while ago. I dropped working with Vite as a whole and work now with Nx with Webpack to generate my remotes which works perfectly. Nx is a whole different story, I know but for me it actually is the perfect solution for working with my projects. And I think nowadays they even have an integration with Vite also. But I haven't tried it yet together with the module federation.

This is what I'm trying to achieve https://github.com/originjs/vite-plugin-federation/issues/518, in your experience do you think that Nx and Webpack could be a solution for me?

goldyfruit avatar Oct 04 '23 11:10 goldyfruit

Well, that depends heavily on your needs. Nx itself is a beast to understand and work with but once I understood the concepts, I really like it. They have generators for the remotes that you use with your Module Federation and so on. It's very handy and can do a lot of things. But it's also quite complicated to handle. I'd suggest to check it out with a POC to see how it works. The also have generators for Vite but I don't think that the MF is already integrated too as it is with Webpack.

iconag-bbasmer avatar Oct 04 '23 13:10 iconag-bbasmer

Well, that depends heavily on your needs. Nx itself is a beast to understand and work with but once I understood the concepts, I really like it. They have generators for the remotes that you use with your Module Federation and so on. It's very handy and can do a lot of things. But it's also quite complicated to handle. I'd suggest to check it out with a POC to see how it works. The also have generators for Vite but I don't think that the MF is already integrated too as it is with Webpack.

Thanks, I think indeed its quiet heavy for my needs.

goldyfruit avatar Oct 04 '23 21:10 goldyfruit

For reference: https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations

aperkaz avatar Oct 19 '23 08:10 aperkaz