Recoil
Recoil copied to clipboard
Duplicate atom key when using Webpack Module Federation
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?
@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 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.
@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..
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
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.
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.
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 Can you share your solution with us?
@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.
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); }
Hi @anty30
Why is this an issue? What happens when we have local imports of things exposed from a module federated bundle?
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.