Skip to content

fix(start): skip plugin-injected entries in manifest chunk scanner#7089

Open
rr-jimmy-multani wants to merge 3 commits intoTanStack:mainfrom
rr-jimmy-multani:fix/manifest-multiple-entries-mf
Open

fix(start): skip plugin-injected entries in manifest chunk scanner#7089
rr-jimmy-multani wants to merge 3 commits intoTanStack:mainfrom
rr-jimmy-multani:fix/manifest-multiple-entries-mf

Conversation

@rr-jimmy-multani
Copy link
Copy Markdown

@rr-jimmy-multani rr-jimmy-multani commented Apr 1, 2026

Hey everyone! I'm working on a POC for TanStack Start + Module Federation and hit this issue. This is my first PR here so apologies if I've missed anything from the contributing guide.

The problem

When using @module-federation/vite as a host, vite build fails with:

Error: multiple entries detected: assets/hostInit-xxx.js assets/main-xxx.js

@module-federation/vite emits additional entry chunks into the client bundle — hostInit (via __mf__virtual/...) and remoteEntry (via virtual:mf-...). The scanClientChunks function in manifestBuilder.ts throws as soon as it sees more than one isEntry chunk, which makes it incompatible with any bundler plugin that emits its own entry points.

This is being tracked in #7032.

What I changed

scanClientChunks now uses facadeModuleId to detect plugin-injected entries and skip them. facadeModuleId preserves the original entry module ID before Rolldown resolves it to a real file path, making it a reliable discriminator:

  • Chunks with __mf__virtual in their facadeModuleId → MF hostInit entry, skip
  • Chunks with a virtual:mf- prefix in their facadeModuleId → MF remoteEntry, skip
  • Everything else → treated as the real app entry, original behaviour preserved (including throwing on genuine duplicates)

How I tested it

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Prevented false "multiple entries" build errors by ignoring framework/plugin-injected entry chunks so only actual app entries are considered.
  • Tests

    • Added regression tests to ensure injected entries are skipped and genuine multiple-entry errors still surface.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d257442b-81f9-4625-b73e-3cc70c0bdfa9

📥 Commits

Reviewing files that changed from the base of the PR and between ce55d82 and d1ee1ea.

📒 Files selected for processing (1)
  • packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts

📝 Walkthrough

Walkthrough

Modified chunk-scanning so Rollup “entry” chunks injected by the MF plugin are recognized via facadeModuleId patterns (__mf__virtual or virtual:mf-) and ignored for the single-entry invariant; only non-injected entries are considered for entryChunk selection and multiple-entry errors.

Changes

Cohort / File(s) Summary
Entry chunk scanning logic
packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts
Updated scanClientChunks to derive facadeId from bundleEntry.facadeModuleId for entries and treat entries whose facadeId contains __mf__virtual or starts with virtual:mf- as plugin-injected; injected entries are skipped when enforcing the single-entry invariant and when selecting entryChunk.
Test suite and helpers
packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts
Extended makeChunk helper to accept an optional facadeModuleId and populate OutputChunk.facadeModuleId. Added tests to assert MF plugin-injected entries (paths with /node_modules/__mf__virtual/...__hostAutoInit__... and virtual:mf-...) are ignored, including cases where injected entries appear before the real app entry; retained test for multiple non-injected entries error.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I sniff the chunks, a twitchy sleuth,

virtual masks reveal their truth,
Plugin-made entries hop aside,
One real entry stays bona fide,
Tests nibble crumbs and smile with pride.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: fixing the manifest chunk scanner to skip plugin-injected entries, which directly addresses the build failure from Module Federation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Bundle Size Benchmarks

  • Commit: 5a81726f0a2f
  • Measured at: 2026-04-01T17:24:10.524Z
  • Baseline source: history:796406da66cf
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.48 KiB 0 B (0.00%) 275.76 KiB 75.97 KiB ████▁▁▁▁▁▂▃
react-router.full 90.78 KiB 0 B (0.00%) 286.95 KiB 78.95 KiB ▆▆▆█▁▁▁▁▁▂▂
solid-router.minimal 35.56 KiB 0 B (0.00%) 107.26 KiB 31.94 KiB ████▁▁▁▁▁▂█
solid-router.full 40.03 KiB 0 B (0.00%) 120.79 KiB 35.94 KiB ████▁▁▁▁▁▂▆
vue-router.minimal 53.38 KiB 0 B (0.00%) 153.07 KiB 47.94 KiB ████▁▁▁▁▁▂▄
vue-router.full 58.25 KiB 0 B (0.00%) 168.53 KiB 52.18 KiB ████▁▁▁▁▁▂▄
react-start.minimal 102.01 KiB 0 B (0.00%) 324.00 KiB 88.21 KiB ▅▅▅█▁▂▂▂▂▃▃
react-start.full 105.38 KiB 0 B (0.00%) 334.35 KiB 91.14 KiB ▆▆▆█▁▁▁▂▂▃▃
solid-start.minimal 49.66 KiB 0 B (0.00%) 153.51 KiB 43.84 KiB ▇▇▇▇▁▁▁▂▂▂█
solid-start.full 55.17 KiB 0 B (0.00%) 169.74 KiB 48.46 KiB ▇▇▇▇▁▂▂▂▂▃█

Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better.

@rr-jimmy-multani rr-jimmy-multani marked this pull request as ready for review April 1, 2026 17:11
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts (1)

197-255: Add one bundle-order regression case.

These cases only cover the path where the real app entry is scanned before the synthetic one. Adding a reversed-order fixture would lock in the behavior for the other traversal path too.

💡 Suggested test
+  test('skips plugin-injected entries even when scanned before the app entry', () => {
+    const mfHostInit = makeChunk({
+      fileName: 'hostInit.js',
+      isEntry: true,
+      facadeModuleId:
+        '/project/node_modules/__mf__virtual/host__H_A_I__hostAutoInit__H_A_I__.js',
+    })
+    const appEntry = makeChunk({
+      fileName: 'main.js',
+      isEntry: true,
+      facadeModuleId: '/project/src/client.tsx',
+    })
+
+    const scanned = scanClientChunks({
+      'hostInit.js': mfHostInit,
+      'main.js': appEntry,
+    })
+
+    expect(scanned.entryChunk).toBe(appEntry)
+  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts`
around lines 197 - 255, Add a regression test that mirrors the existing "skips
__mf__virtual plugin-injected entry chunks" and "skips virtual:mf-
plugin-injected entry chunks" cases but supplies the synthetic/plugin-injected
entry chunk before the real app entry in the input object to exercise the
reversed traversal order; use the same helpers (makeChunk) and call
scanClientChunks with the object keys ordered so the plugin chunk comes first
(e.g., {'hostInit.js': mfHostInit, 'main.js': appEntry}) and assert
scanClientChunks().entryChunk is the appEntry to lock in correct behavior for
both traversal orders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts`:
- Around line 197-255: Add a regression test that mirrors the existing "skips
__mf__virtual plugin-injected entry chunks" and "skips virtual:mf-
plugin-injected entry chunks" cases but supplies the synthetic/plugin-injected
entry chunk before the real app entry in the input object to exercise the
reversed traversal order; use the same helpers (makeChunk) and call
scanClientChunks with the object keys ordered so the plugin chunk comes first
(e.g., {'hostInit.js': mfHostInit, 'main.js': appEntry}) and assert
scanClientChunks().entryChunk is the appEntry to lock in correct behavior for
both traversal orders.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2f98581-b186-46bd-874a-edd262648500

📥 Commits

Reviewing files that changed from the base of the PR and between 5a81726 and ce55d82.

📒 Files selected for processing (2)
  • packages/start-plugin-core/src/start-manifest-plugin/manifestBuilder.ts
  • packages/start-plugin-core/tests/start-manifest-plugin/manifestBuilder.test.ts

// emits hostInit and remoteEntry entries). These are not the app entry.
const facadeId = bundleEntry.facadeModuleId ?? ''
const isPluginInjectedEntry =
facadeId.includes('__mf__virtual') || facadeId.startsWith('virtual:mf-')
Copy link
Copy Markdown
Contributor

@schiller-manuel schiller-manuel Apr 2, 2026

Choose a reason for hiding this comment

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

we will not hardcode "arbitrary" strings here. if at all, i could imagine a generic, configurable filter logic (via a callback for example). however we are restructuring plugins right now, so this has to wait

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.

2 participants