前言
本文将使用微前端架构和模块联邦构建可扩展和高效的Web应用程序的概述,并提供了一个简易教程,教你如何使用React中的模块联邦构建微前端。一步一步搭建一个ikun之家。
什么是微前端?
微前端(Micro Frontends)是一种前端架构模式,类似于微服务架构,旨在将大型前端应用拆分成更小、独立的模块,以便不同的团队可以独立开发、测试、部署和扩展各自的模块。每个模块都代表应用的一个功能或页面,它们可以独立开发和部署,同时能够在运行时无缝地集成在一起,形成一个完整的用户界面。
微前端的核心思想是将前端应用拆分成更小的部分,这些部分可以独立构建、部署和维护。每个部分被称为微前端,它可以是一个独立的代码库,使用不同的技术栈、框架甚至语言来开发。每个微前端可以有自己的开发团队,有自己的开发流程和部署策略。
什么是模块联邦?
模块联邦(Module Federation)是一种软件架构模式,旨在解决在复杂的分布式系统中,不同模块(或微服务)之间的依赖管理和共享问题。它是由Webpack社区推动和支持的。
在传统的前端应用中,通常使用Webpack等构建工具将整个应用打包成一个或多个bundle文件,然后在浏览器中加载。但是随着应用规模的增大和团队的不断扩展,将整个应用打包成单一的bundle可能会导致文件过大、加载时间长、更新和部署不便等问题。模块联邦的目标就是将这些大型应用拆分成多个独立的模块,使得不同的模块可以独立开发、构建和部署,并且能够在运行时动态地在浏览器中加载和共享这些模块。
它是如何工作的?
模块联邦是一种系统,它允许您在另一个应用程序中使用应用程序的部分,而无需导入整个应用程序。这意味着您可以将应用程序拆分为较小的模块,从而更容易管理和更新。
模块联邦的好处
模块联邦作为一种前端架构模式,带来了许多好处,特别是在处理大型、复杂的分布式系统中。以下是模块联邦的一些主要优势:
-
模块化开发: 模块联邦鼓励将应用拆分成小块模块,每个模块负责一个特定的功能或特性。这样的模块化开发风格有助于提高代码的可维护性、可测试性和可重用性。
-
独立开发和部署: 不同的模块可以由不同的团队独立开发和部署,从而减少了团队之间的依赖和协调成本。每个模块的更新和发布都不会影响其他模块,实现了更灵活的开发流程。
-
减少打包体积: 传统的单一bundle打包方式可能导致文件过大,增加加载时间。模块联邦可以将共享的依赖项从模块中提取出来,在运行时动态加载,从而减少每个模块的打包体积。
-
共享依赖: 模块联邦确保共享的依赖只会被加载和初始化一次,这样可以减少代码冗余和资源浪费,提高应用的性能。
-
团队协作: 模块联邦使得不同团队能够专注于各自的领域,而不必担心与其他团队的冲突。每个团队可以独立开发和维护自己的模块,降低了团队之间的合作难度。
-
远程模块: 可以将模块部署在不同的服务中,甚至由不同的团队维护。这种灵活性有助于更好地组织代码库,划分边界和责任。
-
动态加载: 模块联邦支持在运行时动态加载模块,这意味着只有在需要时才会加载模块,从而减少初始加载时间。
-
适用于复杂应用: 模块联邦适用于各种规模的应用,从小型应用到大型、复杂的分布式系统,都可以受益于模块联邦的架构模式。
-
可维护性增强: 将应用拆分成模块可以降低代码库的复杂性,使得问题排查和维护更加容易。
总的来说,模块联邦通过解决依赖管理、共享依赖、独立开发等问题,为构建现代前端应用提供了一种强大的架构模式。随着前端应用的不断发展,模块联邦的优势将继续显现,并在应对复杂性和可维护性方面发挥重要作用。
实践
这是一个使用模块联邦构建微前端的栗子。我们将构建两个应用程序:主页应用和头部应用,共同组成一个ikun之家
你可以按照以下步骤进行操作:
1.创建应用程序
使用 create-react-app
或你喜欢的方法分别创建两个新的React应用程序:
cmd
npx create-react-app ikun-home
npx create-react-app ikun-header
2.安装 Webpack 5
在每个项目程序中,安装webpack 5及其相关依赖项,你可以使用npm
或yarn
。
cmd
#NPM
npm install --save-dev webpack webpack-cli html-webpack-plugin webpack-dev-server babel-loader css-loader
cmd
#Yarn
yarn add -D webpack webpack-cli html-webpack-plugin webpack-dev-server babel-loader css-loader
3.自定义首页
自定义你的ikun-home,例如,在ikun-home/App.js中:
jsx
import React from 'react'; // Must be imported for webpack to work
import './App.css';
function App() {
return (
<div className="App">
<img className='App-logo' src="https://sns-img-hw.xhscdn.com/47eb6e4a-0b8e-a0e7-9778-7c199931ef12?imageView2/2/w/1920/format/webp|imageMogr2/strip" alt="img"/>
</div>
);
}
export default App;
运行你的程序,不出意外的话你应该会看到下面这个页面:
jsx
cd ./ikun-home && yarn && yarn start
4.自定义ikun-header项目
自定义你的ikun-header项目例如在ikun-header/App.js中:
jsx
import React from 'react'; // Must be imported for webpack to work
import './App.css';
function App() {
return (
<div className="HeaderApp">
<span>可乐要加冰,爱坤要用心。</span>
</div>
);
}
export default App;
运行你的程序,不出意外应该长这样:
js
cd ./ikun-header && yarn && yarn start
5.Webpack 配置
在ikun-header/和ikun-home/的根目录下分别创建webpack.config.js
文件。
jsx
//ikun-home/webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index",
mode: "development",
devServer: {
port: 3000, // port 3001 for ikun-header
},
module: {
rules: [
{
test: /\.(js|jsx)?$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
],
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
],
resolve: {
extensions: [".js", ".jsx"],
},
target: "web",
};
分别修改/src/index.js
文件:
jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
分别修改两个项目public/index.html
文件:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ikun之家,小黑子退!退!退!</title>
</head>
<body>
<div id="app"></div>
<script src="main.js"></script>
</body>
</html>
分别将package.json
中的启动脚本更改为我们的webpack配置:
json
"scripts": {
"start": "webpack serve",
"build": "webpack --mode production",
},
分别运行我们的两个应用:
cmd
cd ikun-home && yarn start
cd ikun-header && yarn start
5.模块联邦配置
首先,我们需要添加一个名为entry.js的文件作为我们每个应用程序的入口。
我们需要这个额外的间接层,因为它允许Webpack加载渲染远程应用所需的所有导入项。
在两个项目下创建 src/entry.js
文件:
js
//entry.js
import('./index.js')
再次修改我们两个项目中的webpack.config.js
js
module.exports = {
entry: "./src/entry.js",
//...
}
6.为模块联邦暴露头部
现在我们需要暴露Header供ikun-home使用,在我们的ikun-header/webpack.config.js中:
js
// ikun-header/webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
// import ModuleFederationPlugin from webpack
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
// import dependencies from package.json, which includes react and react-dom
const { dependencies } = require("./package.json");
js
module.exports = {
//...
plugins: [
//...
new ModuleFederationPlugin({
name: "HeaderApp", // This application named 'HeaderApp'
filename: "remoteEntry.js", // output a js file
exposes: { // which exposes
"./Header": "./src/App", // a module 'Header' from './src/App'
},
shared: { // and shared
...dependencies, // some other dependencies
react: { // react
singleton: true,
requiredVersion: dependencies["react"],
},
"react-dom": { // react-dom
singleton: true,
requiredVersion: dependencies["react-dom"],
},
},
}),
],
};
再次启动ikun-header,导航到http://localhost:3001/remoteEntry.js。这是暴露的所有模块的内容。
7.在主页项目中添加模块联邦功能
现在我们需要将ModuleFederationPlugin添加到ikun-home/webpack.config.js中:
js
// ikun-home/webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
// import ModuleFederationPlugin from webpack
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
// import dependencies from package.json, which includes react and react-dom
const { dependencies } = require("./package.json");
module.exports = {
entry: "./src/entry.js", //注意这里
mode: "development",
devServer: {
port: 3000,
},
module: {
rules: [
{
test: /\.(js|jsx)?$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
},
},
],
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
new ModuleFederationPlugin({
name: "HomeApp", // This application named 'HomeApp'
// This is where we define the federated modules that we want to consume in this app.
// Note that we specify "Header" as the internal name
// so that we can load the components using import("Header/").
// We also define the location where the remote's module definition is hosted:
// Header@[http://localhost:3001/remoteEntry.js].
// This URL provides three important pieces of information: the module's name is "Header", it is hosted on "localhost:3001",
// and its module definition is "remoteEntry.js".
remotes: {
"HeaderApp": "HeaderApp@http://localhost:3001/remoteEntry.js",
},
shared: { // and shared
...dependencies, // other dependencies
react: { // react
singleton: true,
requiredVersion: dependencies["react"],
},
"react-dom": { // react-dom
singleton: true,
requiredVersion: dependencies["react-dom"],
},
},
}),
],
resolve: {
extensions: [".js", ".jsx"],
},
target: "web",
};
修改ikun-home/src/App.js,使用远程程序中的Header组件。
jsx
import React, { lazy, Suspense } from 'react'; // Must be imported for webpack to work
import './App.css';
const Header = lazy(() => import('HeaderApp/Header'));
function App() {
return (
<div className="App">
<Suspense fallback={<div>Loading Header...</div>}>
<Header />
</Suspense>
<img className='App-logo' src="https://sns-img-hw.xhscdn.com/47eb6e4a-0b8e-a0e7-9778-7c199931ef12?imageView2/2/w/1920/format/webp|imageMogr2/strip" alt="img"/>
</div>
);
}
export default App;
分别启动ikun-home 和ikun-deader 项目:
cmd
cd ikun-home && yarn start
cd ikun-header && yarn start
完成!
打开http://localhost:3000, 不出意外的话你看到的应该和我下面的一样!
恭喜你!成功地创建了一个带有标题的模块联合应用程序。
结论
微前端架构和模块联邦是构建可扩展和高效的Web应用程序的强大工具。通过将庞大的单体前端拆分为更小、更易管理的部分,团队可以更高效、更独立地工作。而模块联邦则允许开发人员在应用程序之间共享代码,促进协作,减少重复劳动。通过按照本文中概述的步骤,可以在React中构建自己的微前端并利用这些强大的工具。