模块联邦

一、前言

微前端的概念相信大家都不陌生,其本质是一种架构设计思想,将一个完整的项目拆分成若干个小项目,项目的开发和部署互不影响,上线后再聚合成一个完整的项目。webpack5的模块联邦其功能与这个类似,可以用来加载远程模块

二、三个概念

模块联邦有三个重要的概念:

  • webpack构建,一个独立项目通过webpack打包编译而产生资源包
  • remote,一个暴露模块提供其它 webpack构建 消费的 webpack构建
  • host,一个消费其他 remote 模块的 webpack构建

一言以蔽之,一个webpack构建可以是remote–即服务的提供方,也可以是host–即服务的消费方,也可以同时扮演服务提供方和服务消费者,完全看项目的架构。

需要指出的是,任何一个webpack构建既可以作为host消费方,也可以作为remote提供方,区别在于职责和webpack配置的不同

三、项目实战

一共有三个微应用: lib-appcomponent-appmain-app,角色分别是:

  • lib-app as remote,暴露了两个模块 reactreact-dom
  • component-app as remote and host,依赖lib-app,暴露了一些组件供main-app消费
  • main-app as host,依赖lib-app和component-app

lib-app暴露模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//webpack.config.js
module.exports = {
//...省略
plugins: [
new ModuleFederationPlugin({
name: "lib_app",
filename: "remoteEntry.js",
exposes: {
"./react":"react",
"./react-dom":"react-dom"
}
})
],
//...省略
}

component-app的配置

依赖 lib-app,暴露三个模块组件 ButtonDialogLogo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//webpack.config.js
module.exports = {
//...省略
plugins:[
new ModuleFederationPlugin({
name: "component_app",
filename: "remoteEntry.js",
exposes: {
"./Button":"./src/Button.jsx",
"./Dialog":"./src/Dialog.jsx",
"./Logo":"./src/Logo.jsx"
},
remotes:{
"lib-app":"lib_app@http://localhost:3000/remoteEntry.js"
}
}),
]
}
  • 三个暴露的组件
1
2
3
4
5
//Button.jsx
import React from 'lib-app/react';
export default function(){
return <button style={{color: "#fff",backgroundColor: "#409eff",borderColor: "#409eff"}}>按钮组件</button>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//Dialog.jsx
import React from 'lib-app/react';
export default class Dialog extends React.Component {
constructor(props) {
super(props);
}
render() {
if(this.props.visible){
return (
<div style={{position:"fixed",left:0,right:0,top:0,bottom:0,backgroundColor:"rgba(0,0,0,.3)"}}>
<button onClick={()=>this.props.switchVisible(false)} style={{position:"absolute",top:"10px",right:"10px"}}>X</button>
<div style={{ marginTop:"20%",textAlign:"center"}}>
<h1>
What is your name ?
</h1>
<input style={{fontSize:"18px",lineHeight:2}} type="text" />
</div>

</div>
);
}else{
return null;
}

}
}
1
2
3
4
5
6
// Logo.jsx
import React from 'lib-app/react';
import pictureData from './MF.jpeg'
export default function(){
return <img src={pictureData} style={{width:"500px",borderRadius:"10px"}}/>
}

main-app的配置

main-app依赖两个项目 lib-appcomponent-app

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
///webpack.config.js
module.exports = {
//省略...
plugins: [
new ModuleFederationPlugin({
name: "main_app",
remotes:{
"lib-app":"lib_app@http://localhost:3000/remoteEntry.js",
"component-app":"component_app@http://localhost:3001/remoteEntry.js"
},
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
})
]
//省略...
};

使用

main-app 中引入 component-app 暴露出来的模块,即可使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'lib-app/react';
import Button from 'component-app/Button'
import Dialog from 'component-app/Dialog'
import Logo from 'component-app/Logo'
export default class App extends React.Component{
constructor(props) {
super(props)
//省略...
}
//省略...
render(){
return (<div>
//省略...
</div>)
}
}

Comments