Recoil icon indicating copy to clipboard operation
Recoil copied to clipboard

Duplicate atom key when using Webpack Module Federation

Open RareSecond opened this issue 3 years ago • 12 comments

We're currently investigating how we could fully use the advantages Webpack's Module Federation can offer.

However, when exposing an atom from one module to another, we get the Duplicate atom key warning.

Is this something that we should be worried about? Is there something we can do?

RareSecond avatar Apr 19 '21 12:04 RareSecond

@JDansercoer i think the warning is related to HMR (Hot Module Replacement)

Duplicate atom key "". This is a FATAL ERROR in
      production. But it is safe to ignore this warning if it occurred because of
      hot module replacement.

when i make a full reload the warning goes away

niklasgrewe avatar May 04 '21 11:05 niklasgrewe

@niklasgrewe I'm pretty sure it is not. I actually built the two projects and had them running via http-server and I was still getting the warning.

RareSecond avatar May 19 '21 09:05 RareSecond

@JDansercoer @niklasgrewe I face that error also, do you have any solution/idea of whats the issue is? and if it's really dangoures?

another q : Is it ok that the host will contain <RecoilRoot> and also the child will contain <RecoilRoot> ? because I want both will manage their own state , it's works but wired..

OriAmir avatar Nov 15 '21 16:11 OriAmir

also met the same issue , recoil may need to think about if declare atom with same key in module Federation , the atom haven't declare in a singleton module it may declare many times while the module will invoke in different project

liuyisnake avatar Dec 06 '21 06:12 liuyisnake

Even when running all the apps in production mode I have the same error "Duplicate atom key ...".

We need some help from Zack Jackson to show us how to correctly setup recoil with Module Federated apps that all use atoms declared inside a common component library.

andreawyss avatar Dec 10 '21 06:12 andreawyss

Is one of your projects imports the atoms with an local path and the other with an remote path? If so, I think the problem will be solve if you import them using the remote module. You need to declare in both plugins the remote entrypoint which contains the atom declaration.

anty30 avatar Mar 23 '22 04:03 anty30

I was able to resolve the issue by having a single common federated bundle containing the app providers with the RecoilRoot and the atoms that need to be shared across federated bundles. All other federated bundles have this common federated bundle as a Remote dependency.

andreawyss avatar Mar 27 '22 02:03 andreawyss

@andreawyss Can you share your solution with us?

ruslan-byondxr avatar May 18 '22 13:05 ruslan-byondxr

@ruslan-byondxr in the host_app and all remotes 'react', 'react-dom' and 'recoil' are marked as singleton dependencies.

shared_remote exposes AppProviders and the atoms that need to be shared. remote_1 uses remotes: shared_remote remote_2 uses remotes: shared_remote host_app uses remotes: shared_remote, remote_1, remote_2

host_app remotes have access and can modify the shared atoms via recoils hooks.

andreawyss avatar May 18 '22 21:05 andreawyss

met the same issue and i use snapshot_UNSTABLE() to avoid redefine

const snap = snapshot_UNSTABLE(); const current = Array.from(snap.getNodes_UNSTABLE()); function singletonAtom<T> (options: AtomOptions<T>) { const previous:RecoilState<T> = current.find((item) => item.key === options.key) as RecoilState<T>; return previous ?? atom(options); }

liuyisnake avatar Jun 15 '22 08:06 liuyisnake

Hi @anty30

Why is this an issue? What happens when we have local imports of things exposed from a module federated bundle?

alioshr avatar May 16 '23 15:05 alioshr

I have found another solution, without workarounds or having an extra infrastructure for a shared_module (although the latter seems cleaner and concise on the long run, as complexity builds up):

Context:

host: BaseApp remote: SubApp

Within BaseApp I have atom1 in src/state/global-state.ts

I needed to set the initial value of atom1 within the initial render of BaseApp, on the RecoilRoot initializeState method. So I have a local import in BaseApp to have this done.

On the other hand I need this atom in remote SubApp.

I was facing the same duplicate atom warning. And I have understood that when we expose things from a ModuleFederation bundle it serves a new instance of that file to whoever is consuming it remotely, and that is why we end up with duplicate atom keys.

Solution:

As I said, I need to import this atom locally on the BaseApp actually within an exposed file as well. But this exposed file named renderer is only consumed in dev by remotes. Anyway:

// renderer.tsx
import { atom1 } from '../state/global-atoms';

<RecoilRoot
  initializeState={({ set }) => {
    set(atom1, { ...initialState });
  }}
>

Within module federation config I have exposed the file and at the same time made it a singleton.

// host BaseApp module federation configuration
module.exports = {
  name: "base",
  exposes: {
    "./renderer": "./src/renderer",
    "./atoms": "./src/state/global-atoms.ts",
  },

// then somewhere in the file make it a singleton to avoid serving a new instance of a remote file. 
shared: {
    './src/state/global-atoms.ts':{
      singleton: true,
    },
 }

Then when I import on the remote app, it is all good:

import { atom1 } from 'base/atoms';

Not sure on what implications this could have as I have never seen sharing local files as singletons before.

I guess that as long as there is an enforced project pattern, and other modules do not try to share atoms as well as singletons with the same file singleton naming, it will be alright.

The benefit here would be to have these things only on the host, exempting us from needing to create a shared module, while enforcing such things to be exposed just by a single source of truth, in this case, the host app.

alioshr avatar May 16 '23 15:05 alioshr