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

Dynamic import only work when direct passed named #1323

Closed
schirrel opened this issue Jan 1, 2022 · 28 comments
Closed

Dynamic import only work when direct passed named #1323

schirrel opened this issue Jan 1, 2022 · 28 comments
Labels
wontfix This will not be worked on

Comments

@schirrel
Copy link
Contributor

schirrel commented Jan 1, 2022

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?

@Amirko28
Copy link
Contributor

Amirko28 commented Jan 8, 2022

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

@schirrel
Copy link
Contributor Author

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.

@AlonMeytalVia
Copy link

AlonMeytalVia commented Jan 10, 2022

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)

@ScriptedAlchemy
Copy link
Member

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
Copy link
Member

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.

@AlonMeytalVia
Copy link

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

@ScriptedAlchemy ScriptedAlchemy added the wontfix This will not be worked on label Jan 14, 2022
@ScriptedAlchemy
Copy link
Member

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

@schirrel
Copy link
Contributor Author

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?

@ScriptedAlchemy
Copy link
Member

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
Copy link
Member

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.

@schirrel
Copy link
Contributor Author

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.

@ScriptedAlchemy
Copy link
Member

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
Copy link
Member

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
Copy link
Member

@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?

@schirrel
Copy link
Contributor Author

schirrel commented Mar 2, 2022

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
Copy link
Contributor Author

schirrel commented Mar 2, 2022

@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
Copy link
Contributor Author

schirrel commented Mar 9, 2022

Hey @ScriptedAlchemy what you think?

@ScriptedAlchemy
Copy link
Member

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

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

@schirrel
Copy link
Contributor Author

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

@ScriptedAlchemy
Copy link
Member

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

@schirrel
Copy link
Contributor Author

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

@ScriptedAlchemy
Copy link
Member

Find another slot on my calendar :D

@schirrel
Copy link
Contributor Author

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

@alessioerosferri
Copy link

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

@schirrel
Copy link
Contributor Author

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

@ScriptedAlchemy
Copy link
Member

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.

@schirrel
Copy link
Contributor Author

@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.

@aditya-agarwal
Copy link

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

6 participants