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

基于Webpack5 Module Federation在Vue3中嵌入React18组件失败求助

Webpack5 Module Federation跨框架集成问题:React组件无法嵌入Vue3应用

问题描述

已创建简单的React HelloComponent,尝试通过Webpack5 Module Federation将其嵌入Vue3应用,但未能成功。React应用运行正常,但Vue无法渲染该组件,尝试多种嵌入方式均无效,寻求解决方案或其他基于Webpack5 Module Federation的跨框架集成方案。

相关配置代码

React Webpack配置(webpack.dev.js)

const { ModuleFederationPlugin } = require('webpack').container;
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common');
const path = require('path');
const devConfig = {
    mode: "development",
    entry: './src/index.jsx',
    devServer: {
        port: "5000",
    },
    plugins: [
        new ModuleFederationPlugin({
            name: 'HelloApp',
            filename: "remoteEntry.js",
            exposes: {
                './HelloComponent': './src/components/HelloComponent',
            },
            shared: { react: { singleton: true, eager: true }, "react-dom": { singleton: true, eager: true } },
        })
    ],

}
module.exports = merge(commonConfig, devConfig);

Vue Webpack配置

const HtmlWebPackPlugin = require("html-webpack-plugin");
const { ModuleFederationPlugin } = require('webpack').container;
const { VueLoaderPlugin } = require("vue-loader");
const { TransformAsyncModulesPlugin } = require("transform-async-modules-webpack-plugin");

module.exports = {
    entry: './src/bootstrap.js',
    output: {
        publicPath: "http://localhost:3002/",
    },

    resolve: {
        extensions: [".jsx", ".js", ".json"],
    },

    devServer: {
        port: 5001,
    },
    externals: {
        react: 'react', // This tells Vue to use the React instance from the remote container
        'react-dom': 'react-dom',
    },
    module: {
        rules: [
            {
                test: /\.m?js/,
                type: "javascript/auto",
                resolve: {
                    fullySpecified: false,
                },
            },
            {
                test: /\.vue$/,
                loader: "vue-loader",
            },
            {
                test: /\.css$/i,
                use: ["style-loader", "css-loader"],
            },
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: ['@babel/preset-env'],
                    },
                },

            },
            {
                test: /\.(jpe?g|png|gif|svg)$/i,
                loader: 'file-loader',
                options: {
                    name: 'src/assets/[name].[ext]'
                }
            }
        ],
    },

    plugins: [
        new TransformAsyncModulesPlugin(),
        new VueLoaderPlugin(),
        new ModuleFederationPlugin({
            name: "Vue App",
            filename: "remoteEntry.js",
            remotes: {
                helloApp: 'HelloApp@http://localhost:3000/remoteEntry.js',
            },
            exposes: {},
            shared: { react: { singleton: true, eager: true }, "react-dom": { singleton: true, eager: true } },

        }),
        new HtmlWebPackPlugin({
            template: "./public/index.html",
        }),
    ],
};

App.vue

<template>
    <HelloComponent :name="name" />
</template>

<script>
// Importing HelloComponent from remote React application
const HelloComponent = () => import("HelloApp/HelloComponent");

export default {
  name: "App",
  data() {
    return {
      name: "World",
    };
  },
  components: {
    HelloComponent,
  },
};
</script>

React package.json

{
  "name": "base-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@babel/core": "^7.24.4",
    "@babel/preset-env": "^7.24.4",
    "@babel/preset-react": "^7.24.1",
    "@testing-library/jest-dom": "^5.17.0",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.6.8",
    "babel-loader": "^9.1.3",
    "html-webpack-plugin": "^5.6.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "terser-webpack-plugin": "^5.3.10",
    "web-vitals": "^2.1.4",
    "webpack": "^5.91.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.2",
    "webpack-merge": "^5.10.0"
  },
  "scripts": {
    "start": "webpack serve --config webpack.dev.js",
    "build": "webpack build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

Vue package.json

{
  "name": "vue-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "start": "webpack-dev-server  --open --mode development",
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.8.3",
    "vue": "^3.2.13"
  },
  "devDependencies": {
    "@babel/core": "^7.24.4",
    "@babel/eslint-parser": "^7.12.16",
    "@babel/plugin-transform-runtime": "^7.24.3",
    "@babel/preset-env": "^7.24.4",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "@vue/compiler-sfc": "^3.4.25",
    "babel-loader": "^9.1.3",
    "css-loader": "^7.1.1",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^5.6.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "style-loader": "^4.0.0",
    "transform-async-modules-webpack-plugin": "^1.1.0",
    "vue-loader": "^17.4.2",
    "vue-template-compiler": "^2.7.16",
    "webpack": "^5.91.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^5.0.4"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/vue3-essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "@babel/eslint-parser"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead",
    "not ie 11"
  ]
}

报错现象

Vue应用中出现组件渲染失败错误,无法加载并渲染远程的React HelloComponent,控制台提示组件无法识别或渲染失败。

解决方案

1. 修正Vue Webpack配置错误

  • 修正远程地址:React应用运行在5000端口,将Vue配置中remotes的地址改为helloApp: 'HelloApp@http://localhost:5000/remoteEntry.js'
  • 移除externals配置:已通过shared共享React和ReactDOM,externals会导致模块查找失败,删除以下代码:
    externals: {
        react: 'react',
        'react-dom': 'react-dom',
    },
    
  • 添加React JSX支持:修改babel-loader配置,加入@babel/preset-react(需先在Vue项目执行npm install @babel/preset-react --save-dev):
    {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
            loader: "babel-loader",
            options: {
                presets: ['@babel/preset-env', '@babel/preset-react'],
            },
        },
    }
    
  • 统一publicPath:将output.publicPath改为http://localhost:5001/,与devServer端口一致

2. 封装React组件为Vue可识别组件

React组件不能直接在Vue模板中使用,需封装成Vue组件。创建src/components/ReactWrapper.vue:

<template>
  <div ref="reactRoot"></div>
</template>

<script>
import React from 'react';
import ReactDOM from 'react-dom/client';
// 导入远程React组件
import HelloComponent from 'helloApp/HelloComponent';

export default {
  name: 'ReactWrapper',
  props: ['name'],
  mounted() {
    // 渲染React组件到Vue的DOM节点
    const root = ReactDOM.createRoot(this.$refs.reactRoot);
    root.render(<HelloComponent name={this.name} />);
  },
  // 监听props变化,更新React组件
  watch: {
    name(newVal) {
      const root = ReactDOM.createRoot(this.$refs.reactRoot);
      root.render(<HelloComponent name={newVal} />);
    }
  },
  // 组件卸载时清理React实例
  unmounted() {
    ReactDOM.unmountComponentAtNode(this.$refs.reactRoot);
  }
};
</script>

3. 修改App.vue使用封装后的组件

<template>
  <ReactWrapper :name="name" />
</template>

<script>
import ReactWrapper from './components/ReactWrapper.vue';

export default {
  name: "App",
  data() {
    return {
      name: "World",
    };
  },
  components: {
    ReactWrapper,
  },
};
</script>

4. 统一React版本

Vue项目中React版本为18.3.1,React项目为18.2.0,版本不一致可能导致冲突。将Vue项目的React版本改为18.2.0,修改package.json后执行npm install:

"react": "^18.2.0",
"react-dom": "^18.2.0",

5. 确保React组件默认导出

检查React的HelloComponent是否为默认导出:

const HelloComponent = ({ name }) => {
  return <h1>Hello, {name}!</h1>;
};

export default HelloComponent;

验证步骤

  1. 启动React应用:npm start(端口5000)
  2. 启动Vue应用:npm start(端口5001)
  3. 访问Vue应用,查看是否成功渲染React组件

内容的提问来源于stack exchange,提问作者Magento 2

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 08:05:55