构建聚合
构建聚合通常使用软件工程的方式将拆分的子应用在构建编译阶段重新聚合为一个微前端应用。
monorepo
├── packages
| ├── pkg1
| | ├── package.json
| ├── pkg2
| | ├── package.json
├── package.json
严格来说monorepo只是在工程管理角度对大型前端项目的一种管理方式。它提供了统一的工作流程管理和项目间的Code Sharing,可结合如yarn workspaces等辅助工具来管理整体依赖。
项目结构从主应用引入各子应用,进行打包,并发布为一个整体应用,这并不是微前端的一种方案。
npm package
├── src
├── package.json
| | | | ├── app1:version
| | | | | ├── src
| | | | | ├── package.json
| | | | | ├── webpack.config
该方式将子应用发布成独立的 npm 包,共同作为主应用的依赖项。虽然主应用可以使用lazyLoad等方式拆分bundle,但这种方式存在的问题是任何一个子应用有变更(需更新npm包版本并重新发布子应用),整个应用都需要重新编译发布。这种方式只满足了独立开发,不能满足独立运行,独立部署的要求。
module federation
在webpack5 MF出来之前,有些公司通过开发一套自己的构建工具链,在构建过程中对子应用进行聚合。这种方式可以抽取共有依赖,实现样式复用和子应用的chunk拆分和懒加载,但限制是必须使用同一个框架。
对于ToC业务来说,这种方式可以很好的优化性能和加载速度,又能兼顾微前端的独立开发需求。但在MF兴起以后,完全可以采用MF的方式重新思考和构建架构生态。
先看看MF定义
Module federation allows a JavaScript application to dynamically load code from another application
MF允许JS应用动态的从其他应用加载代码
MF提供了什么
在webpack配置文件中使用MF(ModuleFederationPlugin
)后,打包的子应用会变成以下三种module形式:
-
host
类比为:插座
-
remote
类比为:插头
-
Bidirectional-hosts
类比为:双向插座(既有插座也有插头)
一个应用可以是插座,可以是插头,也可以双向插座。
怎么配置MF
先来个插座
//webpack.config
module.exports = {
...
plugins: [
new ModuleFederationPlugin({
name: "app_one_remote",
remotes: {
app_two: "app_two_remote",
app_three: "app_three_remote@https://localhost:8080/remoteEntry.js"
},
shared: ["react", "react-dom","react-router-dom"]
})
]
}
//App.js
const PageThree = React.lazy(() => import("app_three_remote/PageThree"));
const Main = () => {
return (
<div>
<h1>Page main</h1>
<React.Suspense fallback="Loading PageThree...">
<PageThree />
</React.Suspense>
</div>
);
}
ReactDom.render(<Main />, document.getElementById("root")
);
//index.html
<head>
<script src="http://localhost:3002/app_one_remote.js"></script>
</head>
<body>
<div id="root"></div>
</body>
再来个插头
new ModuleFederationPlugin({
name: "app_three_remote",
library: { type: "var", name: "app_three_remote" },
filename: "remoteEntry.js",
exposes: {
“./PageThree”: "./src/PageThree"
},
shared: ["react", "react-dom"]
}),
这样的话在index.html加载的时候,app_one_remote.js发现有依赖的app_three_remote.js就会动态的去加载它。
这里不展开MF的原理,可以参考Module Federation原理剖析
MF可以干什么
有了插座、插头、双向插座,那么我们就可以使用这三种module形式将我们的应用改造成module依赖树的形式,并且是独立编译的动态加载依赖树。而首次加载的应用,将自动变成root插座。
-
主应用
规划一级路由,作为基座,配置引用的所有依赖和子应用二级路由映射。
-
子应用
导出入口文件,部署地址,模块等。
-
平台化
开发子应用的动态注册平台,将子应用的入口文件,部署地址,路由映射等动态的生成到主应用的webpack.js和Router.js进行重新编译部署更新主应用,并结合CI/CD提高平台的无开发特性。
MF落地微前端
采用MF技术落地微前端的情况下,比较适合于统一架构体系,如都是React或Angular的情况,MF对于路由处理,公用依赖处理都非常的方便。适合新搭建的微前端平台,MF这种形式还是偏向于构建聚合的场景。
如果针对各子应用存在架构体系不统一,特别是ToB场景平台级Web有老旧项目的情况下,由于主应用和子应用无法针对一级路由做子应用加载,MF并不是一个太好的选择。
运行模式
-
首次加载
请求远端路由=>返回主应用资源=>主应用加载完成=>主应用接管路由=>根据路由动态加载子应用
-
路由变更
统一架构下由主应用控制整体路由,表现形式同SPA应用一样。
-
刷新页面
同首次加载逻辑