You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Remix集成@module-federation/vite失败,SSR构建报错求助

Remix + @module-federation/vite SSR构建失败问题排查

问题背景

我正在构建一个微前端联邦应用PoC,其中:

  • host是使用@module-federation/vite配置联邦的Remix应用
  • remote是仅通过Vite和@module-federation/vite生成的待消费微前端(MFE)

核心需求是Remix应用在访问特定路由(测试环境为/mfe)时加载该MFE。MFE侧可正常生成文件,但Remix主机添加federation插件后构建失败,且需要保持ssr: true(Remix默认配置)。

报错信息

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'
}

配置代码

主机应用vite.config.ts

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",
  },
});

远程MFE的vite.config.ts

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,
  },
});

主机应用package.json依赖部分

"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"
  },

远程MFE的package.json依赖部分

"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"
  }

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 />
    </>
  );
}

已尝试调整方案

  • 移除所有Remix future标志
  • 在federation配置中添加/移除entryGlobalName: 'remote'和shareScope: 'default'
  • 在federation配置中添加/移除manifest: true
  • 调整插件列表顺序

问题与解答

1. 是否存在我未了解到的Remix与@module-federation/vite兼容限制?

确实存在兼容性限制。Remix的SSR构建依赖虚拟模块virtual:remix/server-build,但@module-federation/vite当前版本(v1.1.5)无法正确处理Vite虚拟模块,会将其当作物理文件尝试读取,直接触发报错。

2. 该限制是否来自@module-federation/vite而非Remix?

是的。问题根源在@module-federation/vite的add-entry插件逻辑:它使用fs.readFileSync读取入口路径,但没有处理虚拟模块的特殊情况,而Remix的SSR构建流程完全符合Vite规范。

3. 报错发生在server-build阶段,是否可仅在客户端构建中启用联邦?若可以,如何实现?

可以仅在客户端构建中启用联邦,具体实现分两步:

步骤1:条件注册联邦插件

修改主机应用的vite.config.ts,通过Vite的isServer参数判断,仅在客户端构建时加载联邦插件:

export default defineConfig(({ isServer }) => ({
  plugins: [
    // 仅客户端构建启用模块联邦
    !isServer && 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(),
  ].filter(Boolean), // 过滤空值,避免注册无效插件
  build: {
    target: "chrome89",
  },
}));

步骤2:确保MFE仅在客户端加载

修改test-mfe.tsx,用Remix的useIsServer钩子避免服务器端渲染远程组件:

/* eslint-disable import/no-unresolved */
import { lazy, Suspense } from "react";
import { useIsServer } from "@remix-run/react";

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

export default function TestMFE() {
  const isServer = useIsServer();

  if (isServer) {
    // 服务器端返回占位内容
    return <p>Loading MFE...</p>;
  }

  return (
    <>
      <p>I AM A MFE</p>
      <Suspense fallback={<p>Loading button...</p>}>
        <RemoteMfeButton />
      </Suspense>
    </>
  );
}

此方案既避免了SSR构建时的虚拟模块读取错误,又符合模块联邦仅在浏览器端运行的逻辑。

内容的提问来源于stack exchange,提问作者Gabriele Buffolino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 06:07:03