module-federation / core

Module Federation is a concept that allows developers to share code and resources across multiple JavaScript applications
https://module-federation.io/
MIT License
1.54k stars 238 forks source link

Next.js: Dynamic Remotes Singleton sharing not working #1750

Closed MarcusNotheis closed 9 months ago

MarcusNotheis commented 11 months ago

Describe the bug

When using dynamic remotes in the Next.js module federation plugin, it looks like singletons are not loaded (or loaded too late) which is causing components to crash. In our use case, we're e.g. sharing @tanstack/react-query and only set the QueryClientProvider in the host application.

When using "static" remotes in the NextFederationPlugin and the remote components are imported with a regular dynamic import, the sharing is working fine.

When using module federation with dynamic remotes, the singletons are not (yet) loaded and the component is crashing.

I tried to find the root cause for that, but the only thing I found out is that __webpack_share_scopes__.default is missing the loaded flag on the singleton remotes:

{
  "react": {
    "0": {
      "from": "nextjs-react_remote-app",
      "eager": false
    },
    "18.2.0": {
      "loaded": 1,
      "from": "roothost"
    }
  },
  "next/router": {
    "13.5.6": {
      "loaded": 1,
      "from": "roothost"
    }
  },
  "react-dom": {
    "0": {
      "from": "nextjs-react_remote-app",
      "eager": false
    },
    "18.2.0": {
      "loaded": 1,
      "from": "roothost"
    }
  },
  "@tanstack/react-query": {
    "5.13.4": {
      "from": "nextjs-react_remote-app",
      "eager": false,
-     "loaded": 1
    }
  }
}

How to reproduce

  1. Checkout the reproduction repo and install dependencies
  2. pnpm start and open the http://localhost:3000 This page is using the standard dynamic import and is loading fine ✅
  3. Now open http://localhost:3000/dynamic-remote
  4. Restart the dev-server (otherwise the singletons are somehow cached?)
  5. Reload http://localhost:3000/dynamic-remote --> see the error 💥

Reproduction

https://github.com/MarcusNotheis/module-federation-dynamic-nextjs-repro

Used Package Manager

pnpm

System Info

System:
    OS: macOS 14.1.1
    CPU: (12) arm64 Apple M2 Pro
    Memory: 398.00 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.10.0 - ~/.nvm/versions/node/v20.10.0/bin/node
    Yarn: 1.22.21 - ~/.nvm/versions/node/v20.10.0/bin/yarn
    npm: 10.2.3 - ~/.nvm/versions/node/v20.10.0/bin/npm
    pnpm: 8.10.5 - ~/.nvm/versions/node/v20.10.0/bin/pnpm
  Browsers:
    Chrome: 120.0.6099.71
    Safari: 17.1

Dependencies:
next: 13.5.6
@module-federation/nextjs-mf: 8.1.0-canary.7

Validations

MarcusNotheis commented 11 months ago

Downgrading @module-federation/nextjs-mf to 8.1.0-canary.1 seems to resolve the singleton sharing issue.

ScriptedAlchemy commented 11 months ago

Stay pinned there!! Thanks for letting me know.

MarcusNotheis commented 11 months ago

Just tried out the experimental version 0.0.0-feat-node-support-1702696727086. The development mode works very well (and singleton sharing is fixed, thank you! 🙂). When running the production build I get the following error:

main-2930bf0ea7365e55.js:54 [ Federation Runtime ]: TypeError: ee.j.startsWith is not a function
main-2930bf0ea7365e55.js:54 Uncaught Error: [ Federation Runtime ]: TypeError: ee.j.startsWith is not a function
    at SyncWaterfallHook.error [as onerror] (main-2930bf0ea7365e55.js:54:3027)
    at SyncWaterfallHook.emit (main-2930bf0ea7365e55.js:16:2384)
    at FederationHost.formatOptions (main-2930bf0ea7365e55.js:53:2800)
    at new FederationHost (main-2930bf0ea7365e55.js:54:1407)
    at Object.init (main-2930bf0ea7365e55.js:54:1650)
    at 2515 (main-2930bf0ea7365e55.js:1:378)
    at __webpack_require__ (webpack-58e7c275c1f45a54.js:54:26463)
    at main-2930bf0ea7365e55.js:54:143007
    at webpackJsonpCallback (webpack-58e7c275c1f45a54.js:54:76971)
    at main-2930bf0ea7365e55.js:1:79
ScriptedAlchemy commented 11 months ago

thanks for letting me know, ill address

ScriptedAlchemy commented 11 months ago

published new nightlys

MarcusNotheis commented 11 months ago

I just tried to upgrade to 0.0.0-feat-node-support-1703050615268 and the error is unfortunately still the same. Or should I rather now use the snapshots which are build from the next branch?

PS: I really like these fast iteration cycles with the experimental releases, thanks for publishing them! One suggestion: If I got the pattern correctly, the npm version is currently 0.0.0-<BRANCH_NAME>-<TIMESTAMP>. What about replacing the timestamp with the commit sha? This would make it easier to find the commit where the snapshot was published from.

akifunal commented 11 months ago

We are encountering issues when trying to share react-query as a singleton with dynamic remotes. This problem is preventing us from updating our next and @module-federation/nextjs-mf packages.

Versions Tried

We attempted to use the following versions but encountered the same issue:

Current Versions

Currently, we are using the following versions without this issue:

We would appreciate any guidance or assistance in resolving this issue. Thank you.

ScriptedAlchemy commented 10 months ago

We should release new canary soon.

ScriptedAlchemy commented 10 months ago

Ahh this is dynamic remotes. You will encounter problems doing this. Youll likely need to use the new runtime https://github.com/module-federation/universe/blob/feat_apply_bundler_runtime/packages/runtime/README.md

Dynamic remotes in v1 of MFP cannot guarantee singletons as share scope will tear.

ScriptedAlchemy commented 10 months ago

You can do the following:

try {
//share scope can be mutated
 await container.init(__webpack_share_scopes__.default);
 // share scope is sealed in this container, no additions will be seen after this point. 
} catch() {}

Container cannot be initialized multiple times. After first init() share scope cannot be updated, if it is, whoever is already initialized will not "see" any subsequent additions.

V1.5 apis will allow for more control over where shared modules are picked from, but if you initialize remotes lazily like this, it'll most likely still tear share scope.

ScriptedAlchemy commented 10 months ago

But, i would not suggest implementing dynamic remotes by hand with your own script loaders and so on. use /runtime package along with v8 of nextjs-mf.

Can these remotes not be configured upfront?

akifunal commented 10 months ago

We're planning an architecture with one host and ~50 remote apps, aiming for independent deployments. We're unsure if this can be achieved without upfront remote configuration using the runtime package.

We also aim to follow the 12-factor principles, using the same image across all environments. We're using importRemote from '@module-federation/utilities' to avoid defining remotes upfront.

Is this approach compatible with the runtime package? Any advice on achieving independent deployment and environment consistency with module federation would be appreciated.

ScriptedAlchemy commented 10 months ago

Read here: https://github.com/module-federation/universe/tree/feat_apply_bundler_runtime/packages/runtime

You can dynamically defined containers as needed.

Or you dynamically control what remotes are loaded or from where, you should use the new runtimePlugins capability:

https://github.com/module-federation/universe/blob/feat_apply_bundler_runtime/packages/runtime/src/core.md

https://github.com/module-federation/universe/blob/feat_apply_bundler_runtime/packages/nextjs-mf/src/plugins/container/runtimePlugin.ts

new NextFederationPlugin({
runtimePlugins: [
        require.resolve(path.join(__dirname, '../container/runtimePlugin')),
      ],
})
ScriptedAlchemy commented 10 months ago

Dynamic remotes will cause singleton issue as you tear share scope, all remotes must be known upfront to initialize them. There is no workaround to lazy remote injection, best you can do is register the remotes upfront and lazy load them as needed but they must all inialize at once.

When share scope "seals" no new keys or refs will be added to containers already sealed.

MarcusNotheis commented 9 months ago

Singletons are now working fine for me in 8.1.10. The resolution for me was switching from remote delegates to a runtime plugin. Surprisingly, even the singleton sharing works now with dynamically injected remotes which were not known at startup.

ScriptedAlchemy commented 8 months ago

yes runtime plugin is the best way to go, its the first non-hack solution to this stuff haha. Glad it worked for you!