2

I am trying to create a Proof of Concept of a federated app, that is a microfrontend based app, where:

  • the host is a Remix app, which federation is configured using @module-federation/vite
  • the remote is the mfe to be consumed that I generate with just Vite and @module-federation/vite module.

The core idea is to have a Remix app that loads a mfe when navigating to a particular route, in my test it is /mfe.

While on the mfe side everything is working fine and I can create the mfe file correctly, I cannot make the host Remix app build when I add the federation plugin to its vite.config.ts.

Here is the error, please note that I want to keep ssr: true in host app (which is Remix default value):

vite v5.4.10 building SSR bundle for production...
✓ 0 modules transformed.
x Build failed in 12ms
[add-entry] ENOENT: no such file or directory, open 'C:\DEV\poc_remix\virtual:remix\server-build'
    at Object.readFileSync (node:fs:441:20)
    at Object.buildStart (C:\DEV\poc_remix\node_modules\@module-federation\vite\lib\index.cjs:109:41)
    at file:///C:/DEV/poc_remix/node_modules/rollup/dist/es/shared/node-entry.js:20789:40  
    at async Promise.all (index 8)
    at async PluginDriver.hookParallel (file:///C:/DEV/poc_remix/node_modules/rollup/dist/es/shared/node-entry.js:20717:9)
    at async file:///C:/DEV/poc_remix/node_modules/rollup/dist/es/shared/node-entry.js:21664:13
    at async catchUnfinishedHookActions (file:///C:/DEV/poc_remix/node_modules/rollup/dist/es/shared/node-entry.js:21135:16)
    at async rollupInternal (file:///C:/DEV/poc_remix/node_modules/rollup/dist/es/shared/node-entry.js:21661:5)
    at async Module.build (file:///C:/DEV/poc_remix/node_modules/vite/dist/node/chunks/dep-BWSbWtLw.js:65443:14)
    at async viteBuild (C:\DEV\poc_remix\node_modules\@remix-run\dev\dist\vite\build.js:212:5) {
  errno: -4058,
  code: 'PLUGIN_ERROR',
  syscall: 'open',
  path: 'C:\\DEV\\poc_remix\\virtual:remix\\server-build',
  pluginCode: 'ENOENT',
  plugin: 'add-entry',
  hook: 'buildStart'
}

Inspired by official module federation examples, here are my configuration files.

The host vite.config.ts config file:

export default defineConfig({
  plugins: [
    federation({
      name: "remix_host",
      remotes: {
        mfe: {
          type: "module",
          name: "mfe",
          entry: "http://localhost:3000/mfe.js",
        },
      },
      exposes: {},
      filename: "mfe.js",
      shared: {
        react: { singleton: true },
      },
    }),
    remix({
      future: {
        v3_fetcherPersist: true,
        v3_relativeSplatPath: true,
        v3_throwAbortReason: true,
        v3_singleFetch: true,
        v3_lazyRouteDiscovery: true,
      },
      routes: (defineRoutes) =>
        defineRoutes((route) => {
          route("/", "routes/home.tsx", { index: true });
          route("/mfe", "routes/test-mfe.tsx");
        }),
    }),
    tsconfigPaths(),
  ],
  build: {
    target: "chrome89",
  },
});

The remote mfe vite.config.ts config file:

export default defineConfig({
  plugins: [
    federation({
      name: "mfe",
      filename: "mfe.js",
      exposes: {
        "./mfeButton": "./src/mfeButton",
      },
      remotes: {},
      shared: {
        react: {
          singleton: true,
        },
      },
    }),
    react(),
  ],
  build: {
    target: "chrome89",
  },
  server: {
    port: 3000,
  },
});

The host app package.json deps sections:

"dependencies": {
    "@module-federation/vite": "^1.1.5",
    "@remix-run/node": "^2.13.1",
    "@remix-run/react": "^2.13.1",
    "@remix-run/serve": "^2.13.1",
    "isbot": "^4.1.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@remix-run/dev": "^2.13.1",
    "@types/react": "^18.2.20",
    "@types/react-dom": "^18.2.7",
    "@typescript-eslint/eslint-plugin": "^6.7.4",
    "@typescript-eslint/parser": "^6.7.4",
    "autoprefixer": "^10.4.19",
    "eslint": "^8.38.0",
    "eslint-import-resolver-typescript": "^3.6.1",
    "eslint-plugin-import": "^2.28.1",
    "eslint-plugin-jsx-a11y": "^6.7.1",
    "eslint-plugin-react": "^7.33.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "postcss": "^8.4.38",
    "typescript": "^5.1.6",
    "vite": "^5.1.0",
    "vite-tsconfig-paths": "^4.2.1"
  },

The remote mfe package.json deps section:

"dependencies": {
    "@module-federation/vite": "^1.1.5",
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@eslint/js": "^9.13.0",
    "@types/react": "^18.3.12",
    "@types/react-dom": "^18.3.1",
    "@vitejs/plugin-react": "^4.3.3",
    "eslint": "^9.13.0",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-react-refresh": "^0.4.14",
    "globals": "^15.11.0",
    "typescript": "~5.6.2",
    "typescript-eslint": "^8.11.0",
    "vite": "^5.4.10"
  }

Here is the test-mfe.tsx:

/* eslint-disable import/no-unresolved */
import { lazy } from "react";

const RemoteMfeButton = lazy(
  async () =>
    // @ts-expect-error loading remote
    await import("mfe/mfeButton")
);

export default function TestMFE() {
  return (
    <>
      <p>I AM A MFE</p>
      <RemoteMfeButton />
    </>
  );
}

In the host vite.config.ts file I tried:

  • drop all Remix future flag
  • add/remove entryGlobalName: 'remote' and shareScope: 'default' in federation configuration
  • add/remove manifest: true to federation configuration
  • reordering plugin list within the config

But error did not change. If I remove federation config, then Remix app builds normally.

My questions are:

  1. Is there any Remix limitation to work with @module-federation/vite that I am not aware of?
  2. Perhaps the limitation is on @module-federation/vite side, and not in Remix?
  3. The error happens on the server-build so perhaps I should find a way of isolating federation build step only for the client side build? If so, any idea on how to do that?

I see these possible workarounds, but I would like to avoid them:

  1. having Remix loading an external module built in plain Vite, so that I can leverage @module-federation/vite without mixing it up with Remix. Such module will be the host and will actually just bridge the remote mfe loading
  2. manage the mfe loading directly, basically writing a very custom module federation plugin for host. Something that I would like to avoid.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.