Skip to content

[Bug]: Module Federation fails to declare shared dependencies when using a custom resolverΒ #11477

@elbywan

Description

@elbywan

System Info

System:
  OS: macOS 15.6
  CPU: (10) arm64 Apple M1 Max
  Memory: 22.77 GB / 64.00 GB
  Shell: 5.9 - /bin/zsh
Binaries:
  Node: 22.15.0 - ~/.volta/tools/image/node/22.15.0/bin/node
  Yarn: 4.9.1-git.20250411.hash-1908ee79f - ~/.volta/tools/image/yarn/4.9.1/bin/yarn
  npm: 10.9.2 - ~/.volta/tools/image/node/22.15.0/bin/npm
  pnpm: 10.14.0 - ~/.volta/bin/pnpm
Browsers:
  Brave Browser: 137.1.79.126
  Chrome: 139.0.7258.139
  Safari: 18.6
npmPackages:
  @rspack/cli: ^1.4.11 => 1.4.11 
  @rspack/core: ^1.4.11 => 1.4.11 

Details

Hey πŸ‘‹

We are currently facing an issue when trying to upgrading rspack to >= v1.4.7.

When using the Module Federation plugin in conjunction with a custom resolver, the __FEDERATION__.__SHARE__ global variable is empty during runtime - whereas with older versions (<= v1.4.6) it was correctly populated.

// JSON.stringify(__FEDERATION__.__SHARE__)
'{"spa-federated:0.0.2":{"default":{}}}'

Our resolver plugin uses the unplugin library which taps into the normalModuleFactory.hooks.resolve hook - mutating the resolveData.request field.

I bisected the repo and the regression is likely coming from this refactoring of the hooks:

Before this change, crates/rspack_plugin_mf/src/sharing/provide_shared_plugin.rs was able to match the shared modules declared in the config using their name (react, react-dom…) but it is now trying to use the fully qualified path instead - which does not feel right nor user friendly if it means that we have to resolve ahead of time and declare the shared dependencies using the absolute path.

This simple change seems to fix the problem by reverting to the old behaviour:

// crates/rspack_plugin_mf/src/sharing/provide_shared_plugin.rs

+let dependency = data.dependencies[0]
+ .as_module_dependency()
+ .expect("should be module dependency");

-let request = &create_data.raw_request;
+let request = dependency.request();

If that solution seems right to you I created a draft PR πŸ™

Reproduce link

See below ⏬

Reproduce Steps

Copy paste this in your terminal:

# create a new project
mkdir rspack-repro-mf-shared-custom-resolution
cd rspack-repro-mf-shared-custom-resolution

# init and add packages
pnpm init
pnpm add -D @rspack/core @rspack/cli @module-federation/enhanced [email protected]

# create source entry point
mkdir src
cat <<EOF > src/host.js
import react from "react";
console.log("React version:", react.version);
EOF

# create rspack config
cat <<EOF > rspack.config.mjs
import { ModuleFederationPlugin } from "@module-federation/enhanced/rspack";
import { createRequire } from "node:module";

const require = createRequire(import.meta.url);

const config = {
  mode: "development",
  devtool: false,
  entry: {
    main: "./src/host.js",
  },
  output: {
    clean: true,
    publicPath: "auto",
  },
  plugins: [
    // Basic module federation plugin
    new ModuleFederationPlugin({
      name: "host",
      shared: {
        react: {
          version: "18.3.1",
          singleton: true,
        },
      },
    }),
    // A dummy plugin to simulate a custom resolver
    function (compiler) {
      compiler.hooks.thisCompilation.tap(
        "customResolver",
        (compilation, { normalModuleFactory }) => {
          normalModuleFactory.hooks.resolve.tap("customResolver", (data) => {
            if (data.request.startsWith("react")) {
              data.request = require.resolve(data.request);
            }
          });
        }
      );
    },
  ],
};

export default config;
EOF

# build the host
pnpm rspack build

# check which shared modules are declared
grep "__webpack_require__.initializeSharingData = { scopeToSharingDataMapping:" ./dist/main.js

Outcome:

# ❌ With the custom resolver (empty object):
# __webpack_require__.initializeSharingData = { scopeToSharingDataMapping: {  }, uniqueName: "rspack-repro-mf-shared-custom-resolution" };
// Formatted
__webpack_require__.initializeSharingData = {
  scopeToSharingDataMapping: {},
  uniqueName: "rspack-repro-mf-shared-custom-resolution",
};
# βœ… Without it (correct mapping):
# __webpack_require__.initializeSharingData = { scopeToSharingDataMapping: { "default": [{ name: "react", version: "18.3.1", factory: () => (__webpack_require__.e("vendors-node_modules_pnpm_react_18_3_1_node_modules_react_index_js").then(() => (() => (__webpack_require__(/*! ./node_modules/.pnpm/[email protected]/node_modules/react/index.js */ "./node_modules/.pnpm/[email protected]/node_modules/react/index.js"))))), eager: 0, singleton: 1 }] }, uniqueName: "rspack-repro-mf-shared-custom-resolution" };
// Formatted
__webpack_require__.initializeSharingData = {
  scopeToSharingDataMapping: {
    default: [
      {
        name: "react",
        version: "18.3.1",
        factory: () =>
          __webpack_require__
            .e(
              "vendors-node_modules_pnpm_react_18_3_1_node_modules_react_index_js"
            )
            .then(
              () => () =>
                __webpack_require__(
                  /*! ./node_modules/.pnpm/[email protected]/node_modules/react/index.js */ "./node_modules/.pnpm/[email protected]/node_modules/react/index.js"
                )
            ),
        eager: 0,
        singleton: 1,
      },
    ],
  },
  uniqueName: "rspack-repro-mf-shared-custom-resolution",
};

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions