Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: prevent circular dependency via asset file imports in shared mod…#891

Merged
gioboa merged 5 commits into
module-federation:mainfrom
Sddoo:fix/prevent_assets_circular_dependency
Jul 10, 2026
Merged

fix: prevent circular dependency via asset file imports in shared mod…#891
gioboa merged 5 commits into
module-federation:mainfrom
Sddoo:fix/prevent_assets_circular_dependency

Conversation

@Sddoo

@Sddoo Sddoo commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Problem

When a shared dependency contains static import statements for asset files (SVG, PNG, CSS/SASS, fonts, etc.) in its bundled entry point, the Vite dependency optimizer generates a dep file that inlines those imports. During Module Federation's resolve hook processing, these asset import requests are incorrectly intercepted by the loadShare proxy and resolved back into the loadShare virtual module path.

This creates a circular dependency chain:

loadShare_pkg → prebuild_pkg → dep_pkg.js → (SVG/asset imports) → loadShare_pkg

The result is a runtime TDZ (Temporal Dead Zone) error:

Uncaught (in promise) ReferenceError: Failed to read the 'default' property from 'Module':
Cannot access 'default' before initialization

Because the loadShare module tries to re-enter itself before its own default export is ready.

Root cause

The plugin has a guard in three resolve hooks that skips .css imports from being proxied through loadShare:

if (/\.css$/.test(source)) return;

However, this guard only covers .css. All other common web asset extensions — SVG, PNG, JPEG, GIF, WebP, AVIF, ICO, WOFF/WOFF2, TTF, EOT, OTF, MP4, WebM, SCSS, SASS, LESS, STYL — pass through the filter and get intercepted, triggering the circular dependency.

The identical single-extension check exists at all three locations:

Location Line Purpose
src/plugins/pluginProxySharedModule_preBuild.ts 340 Main Vite resolveId hook
src/index.ts (Rolldown optimizeDeps resolver) 352 Rolldown dep optimizer
src/index.ts (esbuild optimizeDeps resolver) 396 esbuild dep optimizer

Fix

Expanded the regex in all three locations from /\.css$/ to cover all common web asset extensions:

- if (/\.css$/.test(source)) return;
+ if (/\.(?:css|scss|sass|less|styl|stylus|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|eot|otf|mp4|webm)$/i.test(source)) return;

This ensures that any import of an asset file extension bypasses the loadShare proxy entirely. Vite's normal asset handling pipeline resolves and serves these files directly, breaking any potential circular dependency.

File changes

  1. src/plugins/pluginProxySharedModule_preBuild.ts:340 — Vite plugin resolveId hook
  2. src/index.ts:352 — Rolldown optimizeDeps.rolldownOptions.plugins resolver
  3. src/index.ts:396 — esbuild optimizeDeps.esbuildOptions.plugins resolver

Tests

src/plugins/__tests__/pluginProxySharedModule_preBuild.test.ts

A new describe('asset extension exclusion') block with 14 test cases verifying that asset imports matching a wildcard shared key (@ui-lib/) return undefined (not proxied), plus a positive control verifying that non-asset imports under the same wildcard key ARE proxied:

Test name Source Expected
does not proxy 'ext' asset imports (×14) @ui-lib/assets/file.{svg,png,jpg,gif,webp,avif,ico,woff2,ttf,mp4,webm,scss,less} undefined
still proxies non-asset imports that match a wildcard shared key @ui-lib/button { id: '...' }

Each test registers a @ui-lib/ wildcard shared key so that findSharedKeyForSource matches, then verifies the asset extension regex returns undefined (skip proxying). The positive control ensures the wildcard matching works for regular JS module imports.

src/__tests__/index.test.ts

Rolldown resolver test (proxies shared deps during Rolldown optimizeDeps resolution):

expect(resolver.resolveId('./icon.svg', '/repo/node_modules/.vite/deps/shared-lib.js')).toBeUndefined();
expect(resolver.resolveId('./logo.png', '/repo/node_modules/.vite/deps/shared-lib.js')).toBeUndefined();
expect(resolver.resolveId('./style.css', '/repo/node_modules/.vite/deps/shared-lib.js')).toBeUndefined();

esbuild resolver test (does not proxy shared deps when esbuild resolves optimizeDeps entry points):

expect(onResolveHandlers[1]({
  path: './icon.svg',
  importer: '/repo/node_modules/.vite/deps/shared-lib.js',
  kind: 'import-statement',
})).toBeUndefined();
// (same for .png and .css)

Both verify that asset imports originating from a dep file path (the scenario that triggers the circular dependency) bypass the loadShare proxy.

Impact / Scope

  • Fixes: Any project using @module-federation/vite with a shared dependency whose bundled entry contains static imports of asset files (SVG icons, PNG images, fonts, videos, stylesheets, etc.). UI component libraries that bundle their own assets are the most affected.
  • Breaks: Nothing — the change only adds more extensions to an existing skip-list that already excluded .css. All proxied imports must be JavaScript modules with executable exports.
  • Regression risk: Low. Asset imports should never be proxied through loadShare because they are not JavaScript modules with shareable exports.
  • Performance: Slightly positive — eliminating the circular dependency reduces unnecessary work during optimizeDeps and runtime module loading.

Reproduction

Prerequisites: A shared dependency whose bundled entry contains static asset imports.

// host/package.json
{
  "dependencies": {
     "x-data-spreadsheet": "^1.1.9"
  }
}
// host/vite.config.ts
import { dependencies } from './package.json';
export default defineConfig({
  plugins: [
    federation({
      name: 'host',
      filename: 'remoteEntry.js',
      shared: {
        'x-data-spreadsheet': {
          singleton: true,
          requiredVersion: dependencies['x-data-spreadsheet'],
        },
      },
    }),
  ],
});

Where node_modules/x-data-spreadsheet/src/index.js contains e.g.:

import './index.less';
  1. Run npx vite build
  2. Open the built page in a browser
  3. Observe the console error:
    Uncaught (in promise) ReferenceError: Failed to read the 'default' property from 'Module':
    Cannot access 'default' before initialization
    

The error occurs because the dep optimizer creates node_modules/.vite/deps/x-data-spreadsheet.js which inherits the static LESS imports. Each resolves through the loadShare proxy, creating a circular dependency: loadSharedep → LESS → loadShare.

After the fix, asset file imports bypass the loadShare proxy entirely and are handled by Vite's normal asset pipeline.

Error also reproduced with my corporative ui libs, but i can't leave them here.

@dmchoi77

dmchoi77 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Hi! Could this also cover query/hash-suffixed asset specifiers here?

Vite commonly supports asset imports such as:

@ui-lib/assets/icon.svg?url
@ui-lib/assets/logo.png?raw
@ui-lib/assets/style.css?inline

The new regex is anchored with $, so it only seems to match specifiers that end directly with the asset extension.
If one of these query/hash-suffixed specifiers still matches a wildcard shared key like @ui-lib/, I wonder if it could continue into the loadShare proxy path instead of bypassing it as an asset.

@pkg-pr-new

pkg-pr-new Bot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@module-federation/vite@891

commit: 13eecdc

@Sddoo

Sddoo commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@dmchoi77 u are right. I have changed tests and the wildcard:

- /\.(?:css|scss|sass|less|styl|stylus|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|eot|otf|mp4|webm)$/i
+ /\.(?:css|scss|sass|less|styl|stylus|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|eot|otf|mp4|webm)(?:[?#].*)?$/i

@gioboa gioboa left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tests With Different Frameworks

Image

@gioboa gioboa left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tests With Different Frameworks

Image

@gioboa gioboa merged commit fb19eb3 into module-federation:main Jul 10, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants