我所在的团队使用 React 和 Webpack Module Federation 构建了微前端架构。由于对微前端概念的不理解,我踩了不少坑,甚至导致了一些线上 bug,结果被同事们“diss”了一番。
我困惑于为什么在开发 A 代码库时会影响到 B 代码库。微前端的每个模块都是独立部署的,尽管在本地开发时六个模块都在同一个文件夹中,但却不能使用相对路径 ../../xxx 来引用微前端模块。因为一旦部署到服务器上,一个服务器上可能只有一个微前端模块,这样相对路径就会失效。
为了加深理解,决定手动创建一个基于 React 和 Webpack Module Federation 的微前端项目,从零开始,一步一步搭建,了解各种配置项,不仅为日常需求的顺利进行打基础,也为项目优化、技术项目积累经验。
一、手动启动一个react项目
1.创建一个你项目的文件夹
mkdir main-react
2.进入新创建的目录并初始化一个新的Node.js项目
cd main-react npm init -y
3.安装React和ReactDOM库
npm install react react-dom
4.安装 Webpack 和 Babel 相关依赖。
Webpack 是一个用于打包 JavaScript 应用程序的模块捆绑器,而 Babel 是一个 JavaScript 编译器,用于将 ES6 和 JSX 转换为兼容更多浏览器的 JavaScript 代码。
npm install --save-dev webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env @babel/preset-react html-webpack-plugin style-loader css-loader
5.在项目根目录下创建一个 webpack.config.js 文件,并添加以下代码
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
{
test: /\.css$/, // 匹配所有 .css 文件
use: [
'style-loader', // 将 CSS 注入到 DOM
'css-loader' // 处理 CSS 文件
],
}
]
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public', 'index.html'),
})
],
devServer: {
static: {
directory: path.resolve(__dirname, 'public'),
},
hot: true,
}
};
6.创建 .babelrc 文件,并添加以下代码
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
7.在 package.json 文件的 "scripts" 部分添加以下代码
"scripts": {
"start": "webpack serve",
"build": "webpack"
}
直接替代script部分即可
8.在项目的根目录下创建一个新的 src 目录,并在其中创建 index.js和style.css 文件。并添加以下代码
注意,我安装的React的版本是最新的19.0.0的
import React from 'react';
import { createRoot } from 'react-dom/client';
import './style.css';
function App() {
return (
<div className='main-react-app'>
MAIN-REACT-APP
</div>
);
}
const root = document.getElementById('root');
createRoot(root).render(<App />);
style.css:
.main-react-app {
background-color: pink;
}
9.在 public 文件夹下创建 index.html 文件,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
10.启动
现在,你的项目就已经搭建完成了,就可以使用
npm start来运行你的 React 应用,或者使用npm run build来生成生产环境的构建包。
项目启动成功:
一、在刚刚的文件夹内部,再启动一个react项目
名称为remote1,步骤跟(一)中一样,但是改改文案 和 样式,方便区分
1.创建一个你项目的文件夹
mkdir remote1
2.进入新创建的目录并初始化一个新的Node.js项目
cd remote1 npm init -y
3.安装React和ReactDOM库
npm install react react-dom
4.安装 Webpack 和 Babel 相关依赖。
Webpack 是一个用于打包 JavaScript 应用程序的模块捆绑器,而 Babel 是一个 JavaScript 编译器,用于将 ES6 和 JSX 转换为兼容更多浏览器的 JavaScript 代码。
npm install --save-dev webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env @babel/preset-react html-webpack-plugin style-loader css-loader
5.在项目根目录下创建一个 webpack.config.js 文件,并添加以下代码
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
{
test: /\.css$/, // 匹配所有 .css 文件
use: [
'style-loader', // 将 CSS 注入到 DOM
'css-loader' // 处理 CSS 文件
],
}
]
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public', 'index.html'),
})
],
devServer: {
static: {
directory: path.resolve(__dirname, 'public'),
},
hot: true,
}
};
6.创建 .babelrc 文件,并添加以下代码
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
7.在 package.json 文件的 "scripts" 部分添加以下代码
"scripts": {
"start": "webpack serve",
"build": "webpack"
}
8.在项目的根目录下创建一个新的 src 目录,并在其中创建 index.js和style.css 文件。并添加以下代码
注意,我安装的React的版本是最新的19.0.0的
index.js:
import React from 'react';
import { createRoot } from 'react-dom/client';
import './style.css';
function App() {
return (
<div className='remote-1'>
REMOTE1
</div>
);
}
const root = document.getElementById('root');
createRoot(root).render(<App />);
export default App; // 确保使用 default 导出
style.css:
.remote-1 {
background-color: palegoldenrod;
}
9.在 public 文件夹下创建 index.html 文件,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
10.启动
项目启动成功:
三、配置module federation
main-react/webpack.config.js 和main-react/remote1/webpack.config.js新加了一些配置项,以下是替换代码:
main-react/webpack.config.js:
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
{
test: /\.css$/, // 匹配所有 .css 文件
use: [
'style-loader', // 将 CSS 注入到 DOM
'css-loader' // 处理 CSS 文件
],
}
]
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public', 'index.html'),
}),
new ModuleFederationPlugin({
name: 'main-react',
remotes: {
remote1: 'remote1@http://172.26.95.140:3001/remoteEntry.js', // 指定远程模块的 URL
},
shared: {
react: { singleton: true, eager: true, requiredVersion: '19.x' },
'react-dom': { singleton: true, eager: true, requiredVersion: '19.x' },
},
}),
],
devServer: {
static: {
directory: path.resolve(__dirname, 'public'),
},
hot: true,
port: 3002,
open: true,
}
};
main-react/remote1/webpack.config.js:
const path = require('path');
const { ModuleFederationPlugin } = require('webpack').container;
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
{
test: /\.css$/, // 匹配所有 .css 文件
use: [
'style-loader', // 将 CSS 注入到 DOM
'css-loader' // 处理 CSS 文件
],
}
]
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public', 'index.html'),
}),
new ModuleFederationPlugin({
name: 'remote1',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/index.js',
},
shared: {
react: { singleton: true, eager: true, requiredVersion: '19.x' }, // 设置为 singleton
'react-dom': { singleton: true, eager: true, requiredVersion: '19.x' },
},
})
],
devServer: {
static: {
directory: path.resolve(__dirname, 'public'),
},
hot: true,
port: 3001,
open: true,
}
};
另外main-react/src/index.js中的内容也有所改动:
import React from 'react';
import { createRoot } from 'react-dom/client';
import './style.css';
const loadRemote1 = () => {
return import('remote1/App').then((module) => module.default);
};
// 使用动态加载的组件
loadRemote1().then((Remote1) => {
const App = () => (
<div className='main-react-app'>
MAIN-REACT-APP
<Remote1 />
</div>
);
const root = document.getElementById('root');
createRoot(root).render(<App />);
});
启动
1.先启动remote1中的代码:
cd ./remote1
npm run start
1.再启动main-react中的代码:
注意:此时要保持remote1处于启动状态,另启一个终端来启动main-react中的代码
在main-react目录下
npm run start
启动成功!可以看到main-react项目中 使用module federation引用到了remote1项目中的组件