webpack.js.org icon indicating copy to clipboard operation
webpack.js.org copied to clipboard

ChunkID collisions when using Federated Module Plugin

Open jdthorpe opened this issue 4 years ago • 9 comments

Bug report

What is the current behavior?

I am trying to import two modules using the federated module plugin with a setup like this:

new ModuleFederationPlugin({
    name: "module-a",
    filename: "remoteEntry.js",
    library: { type: "var", name: "module_a" },
    exposes: {
        "./lib": "./bootstrap",
    },
})

and a nearly identical setup for module-b.

However, since both of the federated modules use the same file name at their root module (e.g. bootstrap.ts), both get assigned the same chunkId ("626", which is just the hash of './node_modules/ts-loader/index.js!./bootstrap.ts' using the numberHash function)

In a normal webpack build, the file paths for each chunk would be unique, but since I have multiple federated modules that expose a ./bootstrap.ts, each get the same chunk id (656, in this case). This causes an id collision in the runtime, as both federated modules request the same chunkid, and as result there is a race condition, wherein the first chunk with the id 626 to be requested gets by both federated modules.

I tried setting outputs.chunkFilename: "module-a-[id].js" (and likewise for module-b) but this doesn't seems to change the chunkId, and in the network activity, I can see that only one of the two chunks gets loaded and is imported (erroneously) into both federated modules.

Reading the code, it looks like the chunk IDs get set by the DeterministicChunkIdsPlugin, which doesn't expose a hashSalt option or make use of the options.hashSalt, so there doesn't seem to be a way change the hash to prevent id collisions.

Is there any way to prevent this kind of id collisions with federated modules?

If the current behavior is a bug, please provide the steps to reproduce.

This repo illustrates the race condition. In the example, there are two nearly identical federated modules that expose a src/index.js file which are simple enough:

// module_a/src/index.js
export const message = "Hi from Module A"

and

// module_b/src/index.js
export const message = "Hi from Module B"

The host app simply imports these and displays the results with:

function App() {
    const [a, set_a] = useState()
    const [b, set_b] = useState()

    useEffect(() => {
        import(`module_a/Message`).then((m) => { set_a(m.message) })
        import(`module_b/Message`).then((m) => { set_b(m.message) })
    }, [])

    return (
        <>
            <p>Module A says: {a}</p>
            <p>Module B says: {b}</p>
        </>
    )
}

When the app is run, the messages read either

Module A says: Hi from Module A
Module B says: Hi from Module A

or:

Module A says: Hi from Module B
Module B says: Hi from Module B

Depending on whether module_a/remoteEntry.js or module_b/remoteEntry.js finishes loading first. (The Nginx server in the repo introduces a random delay to help make the switching behavior more obvious)

What is the expected behavior?

When two federated modules are loaded onto the page, the correct module is reliably loaded

Other relevant information: webpack version: 5.68.0 Node.js version: 14.16.0 Operating System: Windows, Linux, OSX Additional tools:

jdthorpe avatar Feb 12 '22 17:02 jdthorpe

Give different names here https://github.com/jdthorpe/federated-module-race-condition/blob/main/module_a/package.json#L2 and here https://github.com/jdthorpe/federated-module-race-condition/blob/main/module_b/package.json#L2, we have this in readme

alexander-akait avatar Feb 12 '22 17:02 alexander-akait

Thanks! That worked, but I don't see it mentioned in the official docs Is there another readme I should be reading?

jdthorpe avatar Feb 12 '22 17:02 jdthorpe

We have this on another page, but yes make sense to improve this on this page too

alexander-akait avatar Feb 12 '22 17:02 alexander-akait

I'm experiencing the same problem, but in my case, I have a shell that uses a the library A and I load multiple federated components that use also the library A (but they might use a different version).

In the shell and federated component, I've setup the module federation plugin to not share the library A. It seems that the module id for the library A is the same for the shell and the federated components. When the shell loads and calls a function in library A, it seems to refer to the wrong library A chunk (it was three shaked differently) and the method that I call is missing, but looking in the other chunks with same module id (loaded later in the network tab), the function is there.

I think this is a bug

Shell -> Library A Federated Page -> Library A Federated Component -> Library A

Library A is not shared cause it contains a context API that we won't share between MFEs

All instance of Library A have 805 as module id

When the Shell initializes and call a hook defined in the Library A, I get an error saying it does not exist, but it is there in another chunk with the same module id.

c-falardeau avatar Apr 17 '23 21:04 c-falardeau

Check you don't have different version of Library-A

alexander-akait avatar Apr 17 '23 21:04 alexander-akait

the shell loads 2.9.1 the page loads 2.9.1 the component loads 2.9.0

This is the shell that fails to load

c-falardeau avatar Apr 17 '23 22:04 c-falardeau

As you can see you have different versions, you can use version (as prefix or suffic) in name to prevent such cases

alexander-akait avatar Apr 17 '23 22:04 alexander-akait

we set splitChuks like this in module A and B ,the remoteEntry.js of them has the same chunk id of vendor, they influence each other.

optimization: { runtimeChunk: false, splitChunks: { chunks: 'async', cacheGroups: { vendors: { test: /[\/]node_modules[\/]/, priority: -10, }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true, }, }, }, }

flyinhigh avatar Jul 25 '23 02:07 flyinhigh