前端 HTML5 的 Webpack5 模块联邦(Module Federation)最佳学习与实践
微前端背景
概念
微前端概念是从微服务概念扩展而来的,摒弃大型单体方式,将前端整体分解为小而简单的块,这些块可以独立开发、测试和部署,同时仍然聚合为一个产品出现在客户面前。可以理解微前端是一种将多个可独立交付的小型前端应用聚合为一个整体的架构风格。
特点
- 技术栈无关:主应用不限制接入应用的技术栈,子应用具备完全自主权。
- 独立性强:独立开发、独立部署,子应用仓库独立。
- 状态隔离:运行时每个子应用之间状态隔离。
适用场景
- 技术栈切换又不想重构已有业务。
- 历史包袱项目,比如历史项目内部强耦合,但是又运行稳定的。(既要保证旧项目的稳定且不变,又能将新需求聚合为一个产品)
- 旧项目的业务页面在新项目中复用,开发周期短。举例:假设
JQ
写了很多用户信息相关的页面,每个页面的初始化只要传用户ID
就能获取其它数据,再用React
写新项目时要复用这些页面的场景。
实现方案
- 基于
single-spa
的微前端方案qiankun
- 基于
webcomponent
qiankun sandbox
的微前端方案micro-app
- 基于
webcomponent
容器iframe
沙箱的微前端方案wujie
- 基于
webpack5
的微前端方案module federation
ModuleFederation 概述
本文的主角,作为webpack5
内置核心特性之一的module federation
:
从图中可以看到,这个方案是直接将一个应用的包应用于另一个应用,同时具备整体应用一起打包的公共依赖抽取能力。
让应用具备模块化输出能力,其实开辟了一种新的应用形态,即 “中心应用”,这个中心应用用于在线动态分发runtime
子模块,并不直接提供给用户使用:
对微前端而言,这张图就是一个完美的主应用,因为所有子应用都可以利用
runtime
方式复用主应用npm
包和模块,更好的集成到主应用中。
问:什么是
runtime
方式?
答:运行时加载不同联合模块。因为用npm
包的方式需要在本地编译时处理依赖关系,并打到对应的包中。
ModuleFederation 实践
目录结构
Container App
首先创建container
项目,该项目导出其它两个应用程序app1
和app2
。
// container/webpack.config.js
const webpack = require("webpack");
const { ModuleFederationPlugin } = webpack.container;
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const deps = require("./package.json").dependencies;
const buildDate = new Date().toLocaleString();
require("dotenv").config({ path: "./.env" });
module.exports = (env, argv) => {
const isProduction = argv.mode === "production";
return {
entry: "./src/index.ts",
mode: process.env.NODE_ENV || "development",
devServer: {
port: 3000,
open: true,
headers: { "Access-Control-Allow-Origin": "*" },
},
resolve: {
extensions: [".ts", ".tsx", ".js"],
},
module: {
rules: [
{
test: /\.(js|jsx|tsx|ts)$/,
loader: "babel-loader",
exclude: /node_modules/,
options: {
cacheDirectory: true,
babelrc: false,
presets: [
[
"@babel/preset-env",
{ targets: { browsers: "last 2 versions" } },
],
"@babel/preset-typescript",
"@babel/preset-react",
],
plugins: [
"react-hot-loader/babel",
["@babel/plugin-proposal-class-properties", { loose: true }],
[
"@babel/plugin-proposal-private-property-in-object",
{ loose: true },
],
["@babel/plugin-proposal-private-methods", { loose: true }],
],
},
},
],
},
plugins: [
new webpack.EnvironmentPlugin({ BUILD_DATE: buildDate }),
new webpack.DefinePlugin({ "process.env": JSON.stringify(process.env) }),
new ModuleFederationPlugin({
name: "container",
// 将其它项目的 name 映射到当前项目中
remotes: {
app1: isProduction ? process.env.PROD_APP1 : process.env.DEV_APP1,
app2: isProduction ? process.env.PROD_APP2 : process.env.DEV_APP2,
},
// 是非常重要的参数,制定了这个参数,可以让远程加载的模块对应依赖改为使用本地项目的 React 或 ReactDOM。
shared: {
...deps,
react: { singleton: true, eager: true, requiredVersion: deps.react },
"react-dom": {
singleton: true,
eager: true,
requiredVersion: deps["react-dom"],
},
"react-router-dom": {
singleton: true,
eager: true,
requiredVersion: deps["react-router-dom"],
},
},
}),
new HtmlWebpackPlugin({ template: "./public/index.html" }),
new ForkTsCheckerWebpackPlugin(),
],
};
};
应用中必须要配置环境变量,来关联其它应用。
// container/.env.development
DEV_APP1="app1@http://localhost:3001/remoteEntry.js"
DEV_APP2="app2@http://localhost:3002/remoteEntry.js"
// container/.env.production
PROD_APP1="app1@http://ogz-microfrontend-app-1.s3-website.eu-central-1.amazonaws.com/remoteEntry.js"
PROD_APP2="app2@http://ogz-microfrontend-app-2.s3-website.eu-central-1.amazonaws.com/remoteEntry.js"
// container/App.tsx
import React, { lazy } from "react";
import { Routes, Route } from "react-router-dom";
import { ContainerApp } from "./components/ContainerApp";
// 通过 webpack 关联其它应用,然后按需加载
const CounterAppOne = lazy(() => import("app1/CounterAppOne"));
const CounterAppTwo = lazy(() => import("app2/CounterAppTwo"));
const App = () => (
<Routes>
<Route
path="/"
element={
<ContainerApp
CounterAppOne={CounterAppOne}
CounterAppTwo={CounterAppTwo}
/>
}
/>
<Route path="app1/*" element={<CounterAppOne />} />
<Route path="app2/*" element={<CounterAppTwo />} />
</Routes>
);
export default App;
展示效果:
app1
应用和app2
应用下的两个组件已经无缝的应用到了container
中。
App-1
// app1/webpack.config.js
const deps = require("./package.json").dependencies;
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
entry: "./src/index.ts",
mode: "development",
devServer: {
port: 3001,
open: true,
headers: { "Access-Control-Allow-Origin": "*" },
},
resolve: {
extensions: [".ts", ".tsx", ".js"],
},
module: {
rules: [
{
test: /\.(js|jsx|tsx|ts)$/,
loader: "ts-loader",
exclude: /node_modules/,
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "app1",
filename: "remoteEntry.js",
// 表示导出的模块,只有在此申明的模块才可以作为远程依赖被使用。
exposes: {
"./CounterAppOne": "./src/components/CounterAppOne",
},
shared: {
...deps,
react: { singleton: true, eager: true, requiredVersion: deps.react },
"react-dom": {
singleton: true,
eager: true,
requiredVersion: deps["react-dom"],
},
"react-router-dom": {
singleton: true,
eager: true,
requiredVersion: deps["react-router-dom"],
},
},
}),
new HtmlWebpackPlugin({ template: "./public/index.html" }),
],
};
// app1/src/components/CounterAppOne
import React, { useState } from "react";
import { Link, useLocation } from "react-router-dom";
import { Text, Button, Flex } from "@chakra-ui/react";
const Counter = () => {
const location = useLocation();
const [count, setCount] = useState(0);
return (
<Flex color="#000" gap="1rem" direction="column">
<Text>
Add by one each click <strong>APP-1</strong>
</Text>
<Text>Your click count : {count} </Text>
<Button onClick={() => setCount(count 1)}>Click me</Button>
{location.pathname !== "/" && (
<Button as={Link} to="/">
Back to container
</Button>
)}
</Flex>
);
};
export default Counter;
app2
应用与app1
应用的webpack
基础配置基本一致,需要暴露出的组件会存在不同。其次就是根据业务开发相应的内容。
总结
- 对于
module federation
,官方解释就是模块联邦,主要依赖内部webpack
提供的一个插件ModuleFederationPlugin
,将内部的组件共享给其它应用使用。可以说是继externals
后最终的运行时代码复用解决方案。 module federation
解决了什么样的问题,允许一个应用A加载另外一个应用B,并且依赖共享,且两个独立的应用之间互不影响。
本篇文章来至:学新通
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通
- 本文地址: https://www.swvq.com/boutique/detail/tanegka
- 联系方式: luke.wu#vfv.cc
系列文章
更多
同类精品
更多
精彩评论
-
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
excel下拉菜单选择后怎么自动出现相应内容
PHP中文网 06-24 -
excel下划线不显示怎么办
PHP中文网 06-23 -
wphotoshop工具右键快捷工具不见了怎么办
PHP中文网 06-19 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
微信小程序没声音怎么办
PHP中文网 06-15 -
手机怎样打开html文件
PHP中文网 05-20 -
pr编辑工具栏面板不见了怎么办
PHP中文网 05-09 -
excel图片置于文字下方的方法
PHP中文网 06-27