module-federation-examples icon indicating copy to clipboard operation
module-federation-examples copied to clipboard

Dynamic import only work when direct passed named

Open schirrel opened this issue 2 years ago • 26 comments

Hi i am working on a offline approach to use with module federation and to achieve this i must load and cache all the components from my remotes. But when i try to load the component with dynamic import and dynamic string, the webpack cant find it module. I am targeting to do

const modules = ["Cronograma", "Sampler", "Wrapper"]
modules.forEach(module => import(`cronograma/${module}`)) 

I saw it didnt work and try to simplify it in order to test. I've tried a simples dummy test like:

    const moduleName = "cronograma/Cronograma";
    import(moduleName);

At the browser i am getting the error:

src_bootstrap_js.js:2169 Uncaught (in promise) Error: Cannot find module 'cronograma/Cronograma'

According with the Dynamic import specs it accepts a string, so it appears to be something related with module federation :/

Here the full code of my test

// const modules = ["Cronograma", "Sampler", "Wrapper"]
const modules = ["Cronograma"]
setTimeout(() => {
    //modules.forEach(module => import(`cronograma/${module}`)) Does not work either
    console.log("Trying to import from string ")
    const moduleName = "cronograma/Cronograma";
    import(moduleName); //Dont work
    setTimeout(() => {
        console.log("Trying to import the same but directly string ")
        import("cronograma/Cronograma"); // Work
        console.log("Imported")
    }, 1000)
}, 3000)

Above the screencapture of the browser (and the componented rendered using the plain import withtout the string)

image

I am letting something pass by? or should work in a different way with string dynamic imports with module federation?

schirrel avatar Jan 01 '22 18:01 schirrel

Did you try to dynamically load modules using something like https://github.com/module-federation/module-federation-examples/tree/master/advanced-api/dynamic-remotes?

Amirko28 avatar Jan 08 '22 22:01 Amirko28

Thanks @Amirko28 , yeah i was thinking of doing that, in fact i've done and worked, but i wanted to know there was a specific problem with import('') and MFP.

schirrel avatar Jan 10 '22 14:01 schirrel

Sorry to chime in on the same issue, trying to use the example in dynamic-remotes and getting __webpack_init_sharing__ is undefined. what am I doing wrong? could it be a webpack version problem? (using CRA5 with Craco to get the MF going)

AlonMeytalVia avatar Jan 10 '22 17:01 AlonMeytalVia

Yeah Webpack cannot create a factory for unknown modules. When you use a partial import - webpack crawls the directory and bundles all possible modules. So the graph still has full module maps.

In Federation it's not possible for webpack to generate possible references or create factories for modules it doesn't know about. If you need dynamic loading you have to use the low level interface directly.

ScriptedAlchemy avatar Jan 13 '22 06:01 ScriptedAlchemy

If init sharing isn't defined you either don't have federation plugin enabled or are not sharing any modules so the runtime requirement isn't added to webpack since there's nothing for it to do.

ScriptedAlchemy avatar Jan 13 '22 06:01 ScriptedAlchemy

So it just started working. somehow. I've used the code from the example. Maybe some config suddenly took effect. 🤷‍♂️

AlonMeytalVia avatar Jan 13 '22 13:01 AlonMeytalVia

So while i cannot support partial remote import paths, i do think you can create a proxy trap to a fake module (maybe) and possibly catch the requested remote, then use promise new promise to patch it to the low level API

something like remote: promise new Promise(()=>{ const trap { get(module, scope)=> { window.remote.get(module)

} ,init: } resolve(trap) })

Webpack would not be able to initialize it correctly but you could init it manually in the promise and inject the remote yourself. Havent tested with partial remote paths but i think it might be possible

PR an example on this repo and ill see if i can write a trap for you

ScriptedAlchemy avatar Jan 14 '22 01:01 ScriptedAlchemy

Nice @ScriptedAlchemy thanks a lot. Also do you think that is possible to have any type of property to show all the modules availabe at a remote? I've tried to "schema" something like it on here, for local usage and test and it works like i needed. Do you think it can be something integrated at webpack? Maybe could i open a PR?

schirrel avatar Jan 18 '22 23:01 schirrel

Integration into webpack would be tough but possible.

I am working on adding chunk maps to the federation interface to allow SSR and so on to read the used chunks directly off the webpack runtime.

I should be able to easily add the capability you want as well.

Alternatively, you could use DefinePlugin and make process.env.allChunks which just reads the MF config and writes an object - then expose that as a remote. So you could crawl all remotes and they'll return the map of their federated modules.

What im planning to do is extend federation interfaces directly. {get,init,chunkMap}

ScriptedAlchemy avatar Jan 19 '22 03:01 ScriptedAlchemy

Id open a issue about webpack integration before starting a PR - ensure Tobias will sign off on such a modification. Just state you're willing to work on it.

For SSR chunk maps, i was just planning to add another module.export to the top of the remote. Client-side would need to actually alter the runtime requirements.

You could also attach it as a separate runtime requirement. It is trickier to pull maps out of the runtime. I did create the ability to hook into the remotes and run the federation script loading code on demand so i can dynamically figure out what a path needs to load in order to work. Similar to dynamic remotes but you're using the build in federation runtime to load load a remote at runtime, assuming the remote is listed in the federation plugin's remotes to begin with.

ScriptedAlchemy avatar Jan 19 '22 03:01 ScriptedAlchemy

Hi zack, first of all. The dynamic load, i managed to make, withtout the trap. For while i am defining and naming all remotes and its components.

I take a better look at the Post about Dynamic Remotes, from the readme and make a similar approad, and from its example.

  1. get from my env all the Remotes URL
const getAllRemotesURL = async () => {
    const remoteEnvs = Object.keys(process.env).filter(name => name.toUpperCase().indexOf("REMOTE_") != -1)
    await Promise.all(remoteEnvs.map(name => addScript(process.env[name])))
}
  1. In it i call the addScipt to load the remoteEntry files
const addScript = async (src) => {
    return new Promise((resolve) => {
        var s = document.createElement('script');
        s.setAttribute('src', src);
        s.onload = function () {
            resolve()
        };
        document.body.appendChild(s);
    })
}
  1. Then, from my list of modules and components i call the loadComponent function, as on the example:
const loadComponent = function (scope, module) {
    return async () => {
        await __webpack_init_sharing__('default');
        const container = window[scope]; // or get the container somewhere else
        await container.init(__webpack_share_scopes__.default);
        const factory = await window[scope].get(`./${module}`);
        const Module = factory();
        return Module;
    };
}
  1. So my init function is as:
export const initOffline = async () => {
    await getAllRemotesURL()
    const remotes = data.default
    const remoteNames = Object.keys(remotes)
    remoteNames.forEach(remoteName => {
        const components = remotes[remoteName];
        components.forEach(async component => {
            loadComponent(remoteName, component)()
        })
    })
}

I've done this way to be "ready ASAP" even with a little effort of defining the components and remotes names. But i am planning on try and experiment something more like you've said.

schirrel avatar Jan 21 '22 00:01 schirrel

Ahhh okay so using the low level api. So I'm actually working on a solution that exposes Federation and the internal chunk loading directly to you so you can call Remotes.home.get

Check the matchedFederatedPage to see an example. It's on the SSR example under shared in index.js

ScriptedAlchemy avatar Jan 21 '22 10:01 ScriptedAlchemy

I'll be releasing that function for end users. But you can achieve it with my new SSR plugin and just loop over the entries and execute them, they'll return the get init interface for all remotes connected to the network. And you don't have to do any manual script injection since I hooked into webpack Federation loading mechanics

ScriptedAlchemy avatar Jan 21 '22 10:01 ScriptedAlchemy

@schirrel coming back to this. Since webpack will not accept your idea, how'd you feel about us creating a plugin under this organization that extends MF with some augmented extras?

Remotes being one of them. Possibly another could be chunkMap? Any interest?

ScriptedAlchemy avatar Feb 27 '22 08:02 ScriptedAlchemy

Hey @ScriptedAlchemy i've thinking about it, i've started to study and was thinking about something like the AutomaticVendorFederation would be simpler and usefull, what you think?

schirrel avatar Mar 02 '22 11:03 schirrel

@ScriptedAlchemy i've manage to make 3 thinks here exposes moduleMap and remoteMap listing exposed files and available remotes, within the remoteEntry, such: Captura de Tela 2022-03-02 às 11 12 20

and for the chunkMap, because of my knowledge of the webpack "internal work" i've create a file after build listing all the chunks name, that can be fetched or something like it by http://localhost:3006/chunkMap.json, here is the output: Captura de Tela 2022-03-02 às 11 12 48

I didnt knew what name it, but maybe we could use the extended-module-federation-plugin and further in the future add other abilities to it that are not enoughe to be on webpack core codebase.

schirrel avatar Mar 02 '22 14:03 schirrel

Hey @ScriptedAlchemy what you think?

schirrel avatar Mar 09 '22 21:03 schirrel

Book some time and let's talk. I'm thinking ModuleFederationEnhacedPlugin

https://calendly.com/scripted-alchemy/special-pair-programming-session

ScriptedAlchemy avatar Mar 10 '22 02:03 ScriptedAlchemy

Done @ScriptedAlchemy, in fact the name you said is more suitable

schirrel avatar Mar 10 '22 03:03 schirrel

Also how familiar are you with webpack internal apis? I've been looking for some help on my SSR plugin for next but needz the skillz

Funding wise I'm working on private capital so I could comp you. Takes a while to procure but once I've got cash I'll be looking for some hands to help on specific problem

ScriptedAlchemy avatar Mar 10 '22 04:03 ScriptedAlchemy

i have little experience, i am having now to develop this plugin, and about the ssr i need to confess that i had never play around with ssr, even next or nuxt, but i am a hell of a curious person, maybe i can learn around some things

schirrel avatar Mar 10 '22 16:03 schirrel

Find another slot on my calendar :D

ScriptedAlchemy avatar Mar 20 '22 09:03 ScriptedAlchemy

@ScriptedAlchemy i took a while to re-schedule, but now i booked ;)

schirrel avatar Mar 21 '22 13:03 schirrel

I would love to have this access to remotes too for dynamic imports

alessioerosferri avatar Mar 22 '22 19:03 alessioerosferri

@alessioerosferri we are willing to make it possible thru https://github.com/schirrel/extended-module-federation-plugin

schirrel avatar Apr 11 '22 12:04 schirrel

So I may have a way to do this now. It would involve inspecting the runtime module that creates this request, and see if it can take a dynamic expression for it.

ScriptedAlchemy avatar Jul 29 '22 06:07 ScriptedAlchemy

@ScriptedAlchemy as we saw this is not a thing related to MF and yes to JS itself and webpack, i think is right this to be closed.

schirrel avatar Aug 10 '22 16:08 schirrel

I'll be releasing that function for end users. But you can achieve it with my new SSR plugin and just loop over the entries and execute them, they'll return the get init interface for all remotes connected to the network. And you don't have to do any manual script injection since I hooked into webpack Federation loading mechanics

@ScriptedAlchemy Could you share an example of this please. Thanks!

aditya-agarwal avatar Dec 04 '22 20:12 aditya-agarwal