Skip to content

fix(managers,rspack,enhanced): resolve canonical share keys for relative imports (#5042) - #5065

Open
justonemorenight wants to merge 1 commit into
module-federation:mainfrom
justonemorenight:feat/canonical-shared-resolver
Open

justonemorenight wants to merge 1 commit into
module-federation:mainfrom
justonemorenight:feat/canonical-shared-resolver

Conversation

@justonemorenight

Copy link
Copy Markdown

Description

Problem

In monorepos or multi-package repositories where packages use relative imports internally (e.g. import { Context } from './FeatureTypeContext') while external consumers import via the canonical package specifier (e.g. import { Context } from '@pkg/context-lib/FeatureTypeContext'), Module Federation previously bundled duplicate local instances because sharing was negotiated strictly by the raw import request specifier.

The existing allowNodeModulesSuffixMatch option (extractPathAfterNodeModules) only checks for the substring node_modules in the file path. In modern monorepos (pnpm workspaces, npm workspaces, Lerna), workspace packages live under repository subdirectories (such as packages/*) and do not contain node_modules/ in their path, leaving relative imports unshared and splitting singleton contexts across host and remote compilations.

Solution

This introduces CanonicalSharedPlugin in @module-federation/managers, which is applied automatically by @module-federation/rspack and @module-federation/enhanced when shared options are configured:

  1. Intercepts relative import requests in normalModuleFactory.hooks.beforeResolve.
  2. Locates the nearest package.json for the resolved target path (with memoized pkgCache for build performance).
  3. Matches root packages, package.json exports entries, and relative subpaths against configured sharedKeys.
  4. Rewrites matching relative requests to their canonical shared package specifiers before module factorization.

Result

Relative imports within a shared package/subpath now cleanly delegate to the federated shared scope (webpack/sharing/consume/...) instead of embedding private local duplicates in chunks, resolving singleton fragmentation across host and remote builds.

Related Issue

Fixes #5042
Related: module-federation/vite#329, #4283

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have updated the documentation / changeset.

@changeset-bot

changeset-bot Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5b7bfe8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 48 packages
Name Type
@module-federation/managers Patch
@module-federation/rspack Patch
@module-federation/enhanced Patch
@module-federation/dts-plugin Patch
@module-federation/manifest Patch
@module-federation/modern-js-v3 Patch
@module-federation/modern-js Patch
@module-federation/nextjs-mf Patch
@module-federation/node Patch
@module-federation/rsbuild-plugin Patch
@module-federation/rspress-plugin Patch
@module-federation/rstest Patch
@module-federation/storybook-addon Patch
shared-tree-shaking-no-server-host Patch
shared-tree-shaking-no-server-provider Patch
@module-federation/cli Patch
@module-federation/metro Patch
shared-tree-shaking-with-server-host Patch
shared-tree-shaking-with-server-provider Patch
node-dynamic-remote-new-version Patch
node-dynamic-remote Patch
@module-federation/playground Patch
remote5 Patch
remote6 Patch
website-new Patch
@module-federation/metro-plugin-rnc-cli Patch
@module-federation/metro-plugin-rnef Patch
@module-federation/metro-plugin-rock Patch
@module-federation/devtools Patch
create-module-federation Patch
@module-federation/error-codes Patch
@module-federation/retry-plugin Patch
@module-federation/runtime-core Patch
@module-federation/runtime-tools Patch
@module-federation/runtime Patch
@module-federation/sdk Patch
@module-federation/third-party-dts-extractor Patch
@module-federation/treeshake-frontend Patch
@module-federation/treeshake-server Patch
@module-federation/webpack-bundler-runtime Patch
@module-federation/bridge-react-webpack-plugin Patch
@module-federation/bridge-react Patch
@module-federation/bridge-shared Patch
@module-federation/bridge-vue3 Patch
@module-federation/inject-external-runtime-core-plugin Patch
@module-federation/esbuild Patch
@module-federation/observability-plugin Patch
@module-federation/utilities Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3cb846129c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +57 to +60
} else if (item && typeof item === 'object') {
for (const k of Object.keys(item)) {
keys.add(k);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor request overrides when canonicalizing shared imports

When a valid shared entry uses request to match a canonical import under a different configuration key (for example, { internal: { request: '@scope/pkg/context', import: '@scope/pkg/context' } }), this extracts only internal. ConsumeSharedPlugin matches that configuration using config.request, but the canonical candidate @scope/pkg/context is absent from this set, so relative imports are never rewritten and still create the duplicate local instance this feature is intended to avoid. Include configured request values in the canonical match set.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 5b7bfe8. extractSharedKeys now extracts configured request overrides (e.g. { internal: { request: '@scope/pkg/context' } }) so canonical candidates match the configured share entry.

Comment on lines +182 to +184
for (const c of candidates) {
if (sharedKeys.has(c)) {
return c;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match trailing-slash shared prefixes

For supported prefix sharing such as shared: { '@scope/pkg/': { singleton: true } }, a relative import resolving to @scope/pkg/context produces that full candidate, but this exact membership test cannot match the configured prefix. The sharing implementation treats trailing-slash requests as prefix matches, so the canonicalization layer leaves these internal relative imports local and the singleton-duplication issue persists for prefix-based shared configurations.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 5b7bfe8. Added prefixes array in SharedKeysSet to track trailing-slash prefixes and perform prefix matching via isMatch / isSharedKeyMatch, allowing relative imports to match prefix shared rules like @scope/pkg/.

…tive imports (module-federation#5042)

In monorepos or multi-package repositories where packages use relative
imports internally (e.g. `import { Context } from './FeatureTypeContext'`)
while external consumers import via canonical package specifier (e.g.
`import { Context } from '@pkg/context-lib/FeatureTypeContext'`), Module
Federation previously bundled duplicate local instances because sharing
was negotiated strictly by the raw import request string.

This adds CanonicalSharedPlugin in @module-federation/managers, which is
applied by @module-federation/rspack and @module-federation/enhanced:
1. Intercepts relative requests in normalModuleFactory.hooks.beforeResolve.
2. Locates the nearest package.json for the resolved target path.
3. Matches root packages, exports field, relative subpaths, and
   trailing-slash prefixes against configured shared keys.
4. Honors configured request overrides (e.g. { alias: { request: 'pkg' } }).
5. Rewrites matching relative requests to canonical shared package
   specifiers before factorization so intra-package imports negotiate the
   shared singleton instance.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Singletons bundled in two instances due to relative/absolute import path usage

1 participant