<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Mateus Oliveira</title>
    <description>The latest articles on Forem by Mateus Oliveira (@mdolive).</description>
    <link>https://forem.com/mdolive</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3857825%2Fcc6e96dc-4669-40e3-89bd-b81845c3ea7a.png</url>
      <title>Forem: Mateus Oliveira</title>
      <link>https://forem.com/mdolive</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mdolive"/>
    <language>en</language>
    <item>
      <title>Migrating a Webpack-Era Federated Module to Vite Without Breaking the Host Contract</title>
      <dc:creator>Mateus Oliveira</dc:creator>
      <pubDate>Thu, 02 Apr 2026 22:05:00 +0000</pubDate>
      <link>https://forem.com/mdolive/migrating-a-webpack-era-federated-module-to-vite-without-breaking-the-host-contract-31nb</link>
      <guid>https://forem.com/mdolive/migrating-a-webpack-era-federated-module-to-vite-without-breaking-the-host-contract-31nb</guid>
      <description>&lt;p&gt;&lt;em&gt;A practical guide to migrating a federated remote to Vite, based on lessons from a real migration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I was tasked with updating a legacy React application that did not support Module Federation. That integration was added first so the app could run as a remote inside a larger host application. Later, the remote needed to migrate from Create React App (CRA) to Vite. By that point, the host already depended on the remote's loading behavior. The tricky part was not replacing CRA with Vite. It was preserving the &lt;strong&gt;runtime contract&lt;/strong&gt; while only the remote changed bundlers. &lt;/p&gt;

&lt;p&gt;If you own a CRA or webpack-era remote that still has to load cleanly inside an existing host, this post covers the cleanup work beforehand, the core CRA-to-Vite swap, the federation-specific deployment fixes, and a local dev harness for debugging the full host loading sequence without redeploying every change.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Terms for reference&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CRA: Create React App.&lt;/strong&gt; For years it was the default easy on-ramp for React apps before being deprecated in 2025.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CRACO:&lt;/strong&gt; Create React App Configuration Override&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module Federation:&lt;/strong&gt; A way for one application to load code from another at runtime instead of bundling everything together up front.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Host:&lt;/strong&gt; The application that loads another app at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote:&lt;/strong&gt; The application that exposes code for the host to load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime contract:&lt;/strong&gt; The files and exported APIs the host already expects.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Why migrate?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Dependabot alerts. The biggest issue was that the CRA dependency tree had kept accumulating a number of high-risk Dependabot alerts, and patching around them was getting harder to justify.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Slow builds. CRA and webpack took over a minute for a cold-start build.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Too many config layers. CRACO was overriding CRA's webpack config, plus custom build scripts for module federation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stale tooling. ESLint was still on the legacy &lt;code&gt;.eslintrc&lt;/code&gt; format. Jest had its own separate config.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dependency rot. Years of Dependabot patches left dozens of manual resolutions in the dependency manifest that nobody fully understood anymore.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal was not just "swap the build tool." It was to reduce dependency risk, simplify the toolchain, and leave the project in a state that another engineer could pick up. Vite had already earned a strong reputation. What was different now was that there was finally enough maintenance pressure to justify spending sprint time on the migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Remove dead weight
&lt;/h3&gt;

&lt;p&gt;Before touching the build tool, everything that would conflict with Vite or had become dead weight needed to go.&lt;/p&gt;

&lt;h4&gt;
  
  
  Remove webpack and Babel dependencies
&lt;/h4&gt;

&lt;p&gt;Some dependencies werent really "dependencies" so much as assumptions about the old toolchain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Babel macros like &lt;code&gt;preval.macro&lt;/code&gt; that ran at compile time. Vite doesnt run your app through the same pipeline that a CRA stack does.&lt;/li&gt;
&lt;li&gt;CRA-specific packages like &lt;code&gt;react-scripts&lt;/code&gt;, &lt;code&gt;craco&lt;/code&gt;, &lt;code&gt;react-app-rewired&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Packages like &lt;code&gt;jsonwebtoken&lt;/code&gt; that were built for Node.js and rely on polyfills that webpack injected automatically. Vite does not do this, so if anything in the browser code imports Node.js built-ins like &lt;code&gt;crypto&lt;/code&gt; or &lt;code&gt;Buffer&lt;/code&gt;, it will break.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Remove stale deps and manual resolutions
&lt;/h4&gt;

&lt;p&gt;The package dependencies were audited and around a dozen were removed. Then the pile of old manual resolutions that had accumulated from years of Dependabot fixes was cleared out. Most of those overrides were for transitive deps of packages that were already gone.&lt;/p&gt;

&lt;h4&gt;
  
  
  Check for Sass compatibility
&lt;/h4&gt;

&lt;p&gt;Worth checking early: a shared design system was still using deprecated Sass &lt;code&gt;@import&lt;/code&gt; patterns, and it had to be updated before the new toolchain would build cleanly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: The CRA-to-Vite swap
&lt;/h3&gt;

&lt;p&gt;With the codebase cleaned up, the core migration came down to a few straightforward steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Replace CRA/CRACO config with a single &lt;code&gt;vite.config.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Move &lt;code&gt;index.html&lt;/code&gt; from &lt;code&gt;public/&lt;/code&gt; to the project root and point it at the module entry&lt;/li&gt;
&lt;li&gt;Rename &lt;code&gt;REACT_APP_*&lt;/code&gt; env vars to &lt;code&gt;VITE_*&lt;/code&gt;; in application code, replace &lt;code&gt;process.env&lt;/code&gt; usage with &lt;code&gt;import.meta.env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Update any legacy &lt;code&gt;ReactDOM.render&lt;/code&gt; calls to &lt;code&gt;createRoot&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Modernize surrounding tooling where it made sense, like moving ESLint to flat config&lt;/li&gt;
&lt;li&gt;Update scripts for &lt;code&gt;vite&lt;/code&gt;, &lt;code&gt;vite build&lt;/code&gt;, &lt;code&gt;vite preview&lt;/code&gt;, and &lt;code&gt;vitest&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Replace Jest with Vitest
&lt;/h4&gt;

&lt;p&gt;Once Vite was the build tool, Vitest was the obvious test runner. It shares the same config file, understands the same path aliases, and removed a lot of separate config glue.&lt;/p&gt;

&lt;p&gt;Add the test config directly to &lt;code&gt;vite.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// ...build config above...&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsdom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;setupFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/test/setup.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/**/*.{ts,tsx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No separate &lt;code&gt;jest.config.js&lt;/code&gt;. No &lt;code&gt;babel-jest&lt;/code&gt; transform. No &lt;code&gt;moduleNameMapper&lt;/code&gt; to keep in sync with path aliases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Module Federation with Vite
&lt;/h3&gt;

&lt;p&gt;This is where the migration stopped being a normal bundler swap. The host still ran webpack and expected all of this to keep working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;host -&amp;gt; fetch asset-manifest.json
host -&amp;gt; load remoteEntry.js
host -&amp;gt; init shared scope
host -&amp;gt; get exposed module
host -&amp;gt; call inject(container, props)
host -&amp;gt; later call unmount()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configuring the federation plugin
&lt;/h4&gt;

&lt;p&gt;Install &lt;code&gt;@module-federation/vite&lt;/code&gt; and add it to your Vite config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;federation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@module-federation/vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;federation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remoteApp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remoteEntry.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;exposes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./RemoteModule&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/remote/entry.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exposed entry file should export the lifecycle functions the host expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/remote/entry.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unmount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./RemoteModule&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./RemoteModule&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MemoryRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Root&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Root&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;_props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Guard against duplicate roots if the host mounts twice.&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;unmount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MemoryRouter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;MemoryRouter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unmount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;inject(container, props)&lt;/code&gt; and &lt;code&gt;unmount()&lt;/code&gt; API here is host-specific. &lt;code&gt;MemoryRouter&lt;/code&gt; made sense because the embedded remote needed internal navigation but not deep-linkable standalone URLs. Standalone development used &lt;code&gt;BrowserRouter&lt;/code&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Generating a host-compatible asset manifest
&lt;/h4&gt;

&lt;p&gt;The host fetched &lt;code&gt;asset-manifest.json&lt;/code&gt; and expected specific keys for &lt;code&gt;remoteEntry.js&lt;/code&gt; and &lt;code&gt;main.css&lt;/code&gt;. Vite produced a different file (&lt;code&gt;manifest.json&lt;/code&gt;) with a different shape, so even after renaming the file, the host couldnt parse it.&lt;/p&gt;

&lt;p&gt;The fix was a small Vite plugin that generates a compatible manifest after the build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;promises&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Plugin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rewriteHostManifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Plugin&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rewrite-host-manifest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;writeBundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outputDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remoteEntry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remoteEntry.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mainCss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;remoteEntry&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;mainCss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remoteEntry.js not found in bundle output&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;remoteEntry.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;remoteEntry&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mainCss&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asset-manifest.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add it to the plugins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;federation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nf"&gt;rewriteHostManifest&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adapt the manifest shape to whatever the host actually reads. This was specific to this setup.&lt;/p&gt;

&lt;h4&gt;
  
  
  Confirm the base path
&lt;/h4&gt;

&lt;p&gt;If the built assets are served from a CDN or cloud storage bucket, you need to tell Vite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ASSET_BASE_PATH&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, Vite generates root-relative paths like &lt;code&gt;/assets/chunk-abc123.js&lt;/code&gt;. The host resolves those relative to its own origin, which in this case served &lt;code&gt;index.html&lt;/code&gt; instead of the JS file, producing MIME type errors. Setting &lt;code&gt;base&lt;/code&gt; to the bucket or CDN path fixed it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Split fonts from the main bundle (if applicable)
&lt;/h4&gt;

&lt;p&gt;The module bundled custom fonts, but the host already loaded the same fonts globally. The fix was to move the &lt;code&gt;@font-face&lt;/code&gt; declarations into a separate SCSS file and only import it in standalone mode, not in the federated entry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Local federation dev harness
&lt;/h3&gt;

&lt;p&gt;This was the biggest QOL improvement, and probably the most reusable part of the migration. Testing a federated module usually means deploying to a test environment and loading it through the host. That's a slow feedback loop. Instead, a local dev harness was built to replicate the host's loading sequence.&lt;/p&gt;

&lt;p&gt;The harness used &lt;code&gt;vite build --watch&lt;/code&gt; plus &lt;code&gt;vite preview&lt;/code&gt; instead of the normal dev server because the goal was to validate the real emitted artifacts: &lt;code&gt;asset-manifest.json&lt;/code&gt;, &lt;code&gt;remoteEntry.js&lt;/code&gt;, built CSS, and chunk URLs. The standard dev server is great for app development, but it doesnt produce the same output the host will actually fetch in production.&lt;/p&gt;

&lt;p&gt;The harness did the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the module in development mode with &lt;code&gt;vite build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Keep rebuilding with &lt;code&gt;vite build --watch&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Serve the output with &lt;code&gt;vite preview&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use a simple intermediary UI to collect runtime props (locale, auth token, environment details)&lt;/li&gt;
&lt;li&gt;Fetch &lt;code&gt;asset-manifest.json&lt;/code&gt; from the local preview server&lt;/li&gt;
&lt;li&gt;Load &lt;code&gt;remoteEntry.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;container.init()&lt;/code&gt; and &lt;code&gt;container.get()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;inject()&lt;/code&gt; with configurable props and verify &lt;code&gt;unmount()&lt;/code&gt; cleanup&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That made it possible to test the full federation lifecycle locally, including script loading, module init, prop injection, CSS loading, auth handling, and unmount cleanup, without deploying anything.&lt;/p&gt;

&lt;p&gt;The entry point ended up with three runtime modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/main.tsx&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_USE_FEDERATION_HARNESS&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FederationHarness&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dev/FederationHarness&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FederationHarness&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VITE_EMBEDDED_MODE&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FederatedEntry&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./remote/FederatedEntry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FederatedEntry&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StandaloneEntry&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./standalone/StandaloneEntry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StandaloneEntry&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;start&lt;/code&gt; runs standalone app development&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dev&lt;/code&gt; runs federation development against a local preview server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;build&lt;/code&gt; produces the production remote for the real host&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pitfalls to watch out for
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vite's manifest is not webpack's manifest.&lt;/strong&gt; Dont assume the formats will match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;base&lt;/code&gt; matters for remote hosting.&lt;/strong&gt; Forget it and every chunk import will 404 or return HTML instead of JavaScript.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared dependencies are not automatic wins.&lt;/strong&gt; They are one of the biggest selling points of Module Federation, but cross-bundler setups and older integration contracts can make them risky to use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suppress lint rules temporarily.&lt;/strong&gt; A build tool migration will surface new lint errors from updated configs. Add temporary warn overrides and fix them in separate PRs and keep momentum.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix things at the source.&lt;/strong&gt; For example, dont patch CI when the build config is wrong :)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;These were the checks that mattered more than "the build passed":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standalone development still worked with the app's normal router and env vars&lt;/li&gt;
&lt;li&gt;The local federation harness could fetch &lt;code&gt;asset-manifest.json&lt;/code&gt;, load &lt;code&gt;remoteEntry.js&lt;/code&gt;, and mount the module&lt;/li&gt;
&lt;li&gt;CSS loaded correctly from the built output&lt;/li&gt;
&lt;li&gt;Production hosting used the correct base path and chunk URLs all resolved correctly&lt;/li&gt;
&lt;li&gt;Full regression test of all features&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Resolved all the open dependabot alerts&lt;/li&gt;
&lt;li&gt;Removed &lt;code&gt;.babelrc&lt;/code&gt;, &lt;code&gt;craco.config.js&lt;/code&gt;, &lt;code&gt;jest.config.js&lt;/code&gt;, and custom webpack overrides&lt;/li&gt;
&lt;li&gt;Consolidated build, dev, preview, and test config into &lt;code&gt;vite.config.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Cold-start build time went from &lt;strong&gt;63.4s&lt;/strong&gt; in CRA/webpack to &lt;strong&gt;9.3s&lt;/strong&gt; in Vite&lt;/li&gt;
&lt;li&gt;The lockfile diff had a reduction of &lt;strong&gt;~10k lines&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're maintaining a federated micro frontend on CRA, the path to Vite is worth the effort. Just remember to analyze the host's loading contract and build yourself a local harness that exercises the real federation lifecycle.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on Vite 8:&lt;/strong&gt; Vite 8 &lt;a href="https://vite.dev/blog/announcing-vite8" rel="noopener noreferrer"&gt;shipped recently&lt;/a&gt;, after this migration was already complete. Its release notes mention Module Federation support as one of the capabilities unlocked by the new Rolldown-based architecture, which looks promising. If I were starting today, I would look into this first. &lt;/p&gt;
&lt;/blockquote&gt;




&lt;h4&gt;
  
  
  References
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://react.dev/blog/2025/02/14/sunsetting-create-react-app" rel="noopener noreferrer"&gt;React: Sunsetting Create React App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vite.dev/guide/build.html" rel="noopener noreferrer"&gt;Vite docs: Building for Production&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sass-lang.com/documentation/breaking-changes/import/" rel="noopener noreferrer"&gt;Sass docs: @import deprecation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/module-federation/vite" rel="noopener noreferrer"&gt;Module Federation Vite plugin repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://adhithiravi.medium.com/migrating-from-create-react-app-to-vite-a-modern-approach-76148adb8983" rel="noopener noreferrer"&gt;Migrating from Create React App to Vite: A Modern Approach&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://module-federation.io/guide/build-plugins/plugins-vite" rel="noopener noreferrer"&gt;Module Federation Vite Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vite.dev/blog/announcing-vite8" rel="noopener noreferrer"&gt;Vite 8 announcement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vite</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>webpack</category>
    </item>
  </channel>
</rss>
