模块联邦是什么
模块联邦时webpack5中推出的一项特性,他典型的一个表现是一个主应用拖着一群子应用,子应用可以根据实际情况,通过网络的形式复用主应用的代码,这些代码可以是普通的函数,也可以是react,vue写的组件。
应用场景
一般来说一个微前端应用本身就不可能轻量,所以可以使用qiankun。模块联邦个人感觉可以有以下应用场景
- 在减少应用集群打包时间,打包体积上有用武之地。
- 动态更新页面或组件:比如子应用a 导入了主应用的一个组件群,或者一个命名空间的库函数,只需要在主应用更新,所有的子应用都能得到更新。比如一个由electron 生成的客户端应用,是不是就可以实现某种程度上的”热跟新“?
实践
// app2 webpack.config.js
const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const deps = require('./package.json').dependencies;
const ModuleFederationPlugin = webpack.container.ModuleFederationPlugin;
module.exports = {
entry: "./index.js",
mode: "development",
devServer: {
hot: true,
static: {
directory: path.join(__dirname, 'dist'),
},
port: 3001,
// headers: { "Access-Control-Allow-Origin": "*" },
},
output: {
hashFunction: 'xxhash64',
path: path.resolve("./dist"),
filename: 'js/[name].js',
publicPath: '/',
chunkFilename: 'js/[name].js',
publicPath: 'auto'
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react'],
},
},
],
},
// 其他配置...
plugins: [
new ModuleFederationPlugin({
name: "app2",
filename: "remoteEntry.js",
exposes: {
// 暴露的模块
"./Module1": "./src/Module1",
},
// shared: ["lodash"], // 共享的模块列表,需要和主应用程序一致
shared: {
react: {
requiredVersion: deps.react,
import: 'react', // the "react" package will be used a provided and fallback module
shareKey: 'react', // under this name the shared module will be placed in the share scope
shareScope: 'default', // share scope with this name will be used
singleton: true, // only a single version of the shared module is allowed
},
'react-dom': {
requiredVersion: deps['react-dom'],
singleton: true, // only a single version of the shared module is allowed
},
}
}),
new HtmlWebpackPlugin({
template: "./index.html",
title:"wcx-html"
})
],
};
// app3 webpack.config.js
const webpack = require("webpack")
const ModuleFederationPlugin = webpack.container.ModuleFederationPlugin;
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: "./index.js",
mode: "development",
devServer: {
hot: true,
static: {
directory: path.join(__dirname, 'dist'),
},
port: 3002,
headers: { "Access-Control-Allow-Origin": "*" },
},
output: {
hashFunction: 'xxhash64',
path: path.resolve("./dist"),
filename: 'js/[name].js',
publicPath: '/',
chunkFilename: 'js/[name].js'
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react'],
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'app3',
// remotes: {
// app2: 'app2@http://localhost:3001/remoteEntry.js',
// },
shared: {
react: {
import: 'react', // the "react" package will be used a provided and fallback module
shareKey: 'react', // under this name the shared module will be placed in the share scope
shareScope: 'default', // share scope with this name will be used
singleton: true, // only a single version of the shared module is allowed
},
'react-dom': {
singleton: true, // only a single version of the shared module is allowed
},
}
}),
new HtmlWebpackPlugin({
template: "./index.html",
title:"wcx-html"
})
]
}
app3 目录结构
// index.js
import ("./src/bootstrap")
// bootstrap js
import App from "../src/AppIndex"
import React from "react"
import {createRoot} from "react-dom/client"
const rootNode = document.querySelector("#root")
const root = createRoot(rootNode)
root.render(<App/>)
// AppIndex.js
import { init,loadRemote } from '@module-federation/runtime'
import React,{useEffect, useState} from "react"
init({
name: 'app3',
remotes: [
{
name:'app2',
entry: 'http://localhost:3001/remoteEntry.js'
},
]
})
// async function test() {
// const rlt = await loadRemote(`app2/Module1`);
// const target = rlt.default
// target()
// }
// test()
export default function App() {
const [Component, setComponent] = useState(null)
const [isReady, setIsReady] = useState(false)
const loadComponent = async() => {
const { default: component} = await loadRemote("app2/Module1")
console.log('component', component)
setComponent(() => component)
setIsReady(true)
}
useEffect(() => {
loadComponent()
})
return (
<div>
this is App component from app3
{isReady ? <Component/> : null}
{/* <Component /> */}
</div>
)
}
app2 目录结构
// Module1.js
import React from "react"
export default function() {
return (
<div>this is a component from app2</div>
)
}
通过webpack dev-sever起服务后查看效果
在UMI4.1.10中使用模块联邦
消费模块的配置
//.umirc.ts
import { defineConfig } from '@umijs/max';
const remoteMFName = 'templateLib';
export default defineConfig({
antd: {},
access: {},
model: {},
initialState: {},
request: {},
layout: {
title: '@umijs/max',
},
routes: [
{
path: '/',
redirect: '/home',
},
{
name: '首页',
path: '/home',
component: './Home',
},
{
name: '权限演示',
path: '/access',
component: './Access',
},
{
name: ' CRUD 示例',
path: '/table',
component: './Table',
},
],
mf: {
name: remoteMFName,
library: {
type: "window",
name: remoteMFName
},
remoteHash: false
},
publicPath: 'http://127.0.0.1:8002/',
npmClient: 'pnpm',
});
生产模块配置
// .umirc.ts
import { defineConfig } from '@umijs/max';
export default defineConfig({
antd: {},
access: {},
model: {},
initialState: {},
request: {},
layout: {
title: '@umijs/max',
},
routes: [
{
path: '/',
redirect: '/home',
},
{
name: '首页',
path: '/home',
component: './Home',
},
{
name: '权限演示',
path: '/access',
component: './Access',
},
{
name: ' CRUD 示例',
path: '/table',
component: './Table',
},
],
// 已经内置 Module Federation 插件, 直接开启配置即可
mf: {
remotes: [
{
// 可选,未配置则使用当前 remotes[].name 字段
aliasName: 'templateLib',
name: 'templateLib',
entry: 'http://localhost:8002/remote.js',
},
],
// 配置 MF 共享的模块
shared:{},
},
npmClient: 'pnpm',
});
实际页面使用的方式
const HomePage: React.FC = () => {
const RemoteComponent = React.lazy(() => {
return rawMfImport({
entry: "http://localhost:8002/remote.js",
// entry: "http://localhost:3001/remote.js",
moduleName: "Module1",
remoteName: "templateLib"
// remoteName: "app2"
})
})
return (
<Suspense fallback="loading">
<RemoteComponent />
</Suspense>
);
};
export default HomePage;
补充
webpack5 内部帮你解决了跨域的问题,所以你不需要关心跨域相关的问题.