我们用一个图片说明一下关键点
TeamA TeamB
React
我们再用一张图说明一下容器
和主机(host)
的关系
容器
远程
的,容器的使用者是主机
远程
可以将模块公开给主机
主机
可以使用此类模块,它们被称为远程模块
remote
, 引用者被称为host
,remote
暴露模块给host
, host
则可以使用这些暴露的模块,这些模块被称为remote
模块我们还有一张图来表示我们的项目之间的关系
我们做一个双向依赖的项目,remote
本地list
组件,依赖host
的from
组件,host
本地from
组件,依赖remote
的list
组件。他们共同依赖react
和react-dom
字段 | 类型 | 含义 |
---|---|---|
name | string | 必传值,即输出的模块名,被远程引用时路径为 n a m e / {name}/ name/{expose} |
library | object | 声明全局变量的方式,name为umd的name |
filename | string | 构建输出的文件名 |
remotes | object | 远程引用的应用名及其别名的映射,使用时以key值作为name |
exposes | object | 被远程引用时可暴露的资源路径及其别名 |
shared | object | 与其他应用之间可以共享的第三方依赖,使你的代码中不用重复加载同一份依赖 |
npm i webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env html-webpack-plugin react react-dom -D
webpack.config.js
let path = require("path");
let webpack = require("webpack");
let HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
publicPath: "http://localhost:3001/",
clean: true,
path: path.join(__dirname, 'dist')
},
devServer: {
port: 3001,
static: {
directory: path.join(__dirname, 'dist'),
},
},
module: {
rules: [
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: ["@babel/preset-react"]
},
},
exclude: /node_modules/,
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
new ModuleFederationPlugin({
filename: "hostEntry.js",
name: "host",
exposes: {
"./From": "./src/From",
},
remotes: {
remote: "remote@http://localhost:3000/remoteEntry.js"
}
})
]
}
我们主要讲一下new ModuleFederationPlugin()
的操作,主要是暴露一个模块./From
指向我们本地的From
组件,同时加载一个远程的模块remote@http://localhost:3000/remoteEntry.js
App.jsx
import React from "react";
import From from './From';
const RemotList = React.lazy(() => import("remote/List"));
const App = () => (
host项目
);
export default App;
import
语法webpack
是会自动转移成自己的webpack__require__
去加载远程模块的。
let path = require("path");
let webpack = require("webpack");
let HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
publicPath: "http://localhost:3000/",
clean: true,
path: path.join(__dirname, 'dist')
},
devServer: {
port: 3000,
static: {
directory: path.join(__dirname, 'dist'),
},
},
module: {
rules: [
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: ["@babel/preset-react"]
},
},
exclude: /node_modules/,
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
new ModuleFederationPlugin({
filename: "remoteEntry.js",
name: "remote",
exposes: {
"./List": "./src/List",
},
remotes: {
remote: "host@http://localhost:3001/hostEntry.js"
}
})
]
}
Remot项目App主入口
import React from "react";
import List from './List';
const HostFrom = React.lazy(() => import("remote/From"));
const App = () => (
Remot项目
)
export default App;
之前我们在为什么需要微前端中讲过目前国内微前端方案大概分为:
SIngle Spa
的偏通用的qiankun方案,也有基于本身团队业务量身定制的方案。Webpack 5 Module Federation
实现的EMP微前端方案,可以实现多个应用彼此共享资源分享。那么基于Webpack 5 Module Federation
的微前端的解决方案,旧的项目的迁移成本就比较高了。对于一个大型的工程来说,需要进行按照项目目录或者功能点的拆分的时候,可能基座模式模式成本更小、更易上手。而去中心模式的优势在于,过多的项目同时有大量的公用性组件或者逻辑时,Module Federation
便能很好的彼此共享资源。
不啰嗦,我们直接上成果图
使用react-base
去加载了reace-project
的hello
组件,这里大家可以直接看EMP微前端方案,但是我看文档不是很全,vue的项目对接方案是缺失的。
下面是配置emp的关键点:
我们对比react-base和react-project项目的配置,看到react-base在emp.config.js配置文件中声明分享了一个Hello组件:
exposes: {
// 别名:组件的路径
'./components/Hello': 'src/components/Hello',
},
react-project在emp.config.js配置文件中声明需要使用react-base项目的Hello组件:
remotes: {
// 远程项目别名:远程引入的项目名
'@emp/react-base': 'empReactBase',
},
并且react-project在项目代码中,引入使用了react-base项目的Hello组件:
import HelloDEMO from '@emp/react-base/components/Demo'
const App = () => (
<>
// ...
// ...
>
)
那么,我们跟着指令分别跑起react-base和react-project项目就可以看到上面的成果图了:
cd react-base && yarn && yarn dev
cd react-project && yarn && yarn dev
single-spa
一样,不支持js沙箱,需要自己实现写在最后:下一篇我们将讲single-spa的玩法《微前端从零到剖析qiankun源码 -- single-spa玩法!》