一、为什么需要微前端
1、what?什么是微前端?
微前端就是将不同功能按不同维度拆分成多个子应用,通过主应用加载这些子应用。
将前端整体分解,每一块可以独立开发、测试和部署,同时对用户而言仍是一个整体。
2、why?为什么去使用他?
- 目前的趋势是构建单页面应用(SPA)。前端层通常由一个单独的团队开发,随着时间的推移,会变得越来越庞大而难以维护。这就是传说中的前端巨无霸。
- 技术升级或业务原因需要重构,增量升级怎么破?
- 现有应用中的耦合和复杂度让每个开发者互相掣肘,降低了大型团队的开发效率怎么破?
- 不同团队开发同一个应用,部署互相影响怎么破?技术栈不同怎么破?项目中还有老的应用代码怎么破?
我们是不是可以将一个应用拆分成若干个子应用,将子应用打包成一个个lib,当路径切换时加载不同的子应用。这样每个子应用都是独立开发、部署,技术栈就没了限制,从而解决前端协同开发问题。
3、How?怎样落地微前端?
- single-spa, 是一个用于前端微服务化的前端解决方案,实现了路由劫持和应用入口。(开放应用加载;没有样式隔离、js沙箱机制)
- qiankun, 基于 single-spa 提供了更加开箱即用的 API (SingleSpa + sandbox + import-html-entry)。做到了技术栈无关,并且接入简单(像iframe一样简单)
总结:子应用独立构建,运行时动态加载,主子应用完全解耦,技术栈无关,靠的是协议接入(子应用必须导出bootstrap、mount、unmount方法)
二、微前端的优点
1、增量升级
对于许多团队而言,这是开始微前端之旅的首要原因。技术栈阻碍了项目的发展,只能重写。为了避免完全重写的风险,我们更希望 逐个替换旧的模块。
2、简单、解耦的代码库
每个单独的微型前端应用的源代码都将比单个整体前端应用的源代码少得多。这些较小的代码库对于开发人员来说更容易维护。尤其是我们避免了组件间耦合所导致的复杂性。
3、独立部署
就像微服务一样,微前端的独立部署能力是关键。部署范围的减小,带来了风险的降低。每个微前端应用都具有自己的持续交付途径,不停地构建、测试、部署。
4、团队自治
每个团队需要围绕业务功能垂直组建,而不是根据技术能力来组建。这为团队带来了更高的凝聚力。
总结
简而言之,微前端就是将大而恐怖的东西切成更小、更易于管理的部分,然后明确地表明它们之间的依赖性。我们的技术选择,我们的代码库,我们的团队以及我们的发布流程都应该能够彼此独立地操作和发展,无需过多的协调。
三、缺点
- 下载量增多
- 环境差异:不同环境模拟生产环境更耗时
- 治理复杂性:更多的代码库、工具、构建管道、服务器、域名;多个代码库质量、一致性;决策变分散等
四、集成方式
后端模板集成
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Feed me</title>
</head>
<body>
<h1>🍽 Feed me</h1>
<!--# include file="$PAGE.html" -->
</body>
</html>
然后配置 nginx
server {
listen 8080;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
ssi on;
# 将 / 重定向到 /browse
rewrite ^/$ http://localhost:8080/browse redirect;
# 根据路径访问 html
location /browse {
set $PAGE 'browse';
}
location /order {
set $PAGE 'order';
}
location /profile {
set $PAGE 'profile'
}
# 所有其他路径都渲染 /index.html
error_page 404 /index.html;
}
iframe集成
<html>
<head>
<title>Feed me!</title>
</head>
<body>
<h1>Welcome to Feed me!</h1>
<iframe id="micro-frontend-container"></iframe>
<script type="text/javascript">
const microFrontendsByRoute = {
'/': 'https://browse.example.com/index.html',
'/order-food': 'https://order.example.com/index.html',
'/user-profile': 'https://profile.example.com/index.html',
};
const iframe = document.getElementById('micro-frontend-container');
iframe.src = microFrontendsByRoute[window.location.pathname];
</script>
</body>
</html>
package集成(组合式集成)
{
"name": "@feed-me/container",
"version": "1.0.0",
"description": "A food delivery web app",
"dependencies": {
"@feed-me/browse-restaurants": "^1.2.3",
"@feed-me/order-food": "^4.5.6",
"@feed-me/user-profile": "^7.8.9"
}
}
js集成(基座模式)
<html>
<head>
<title>Feed me!</title>
</head>
<body>
<h1>Welcome to Feed me!</h1>
<!-- 这些脚本不会马上渲染应用 -->
<!-- 而是分别暴露全局变量 -->
<script src="https://browse.example.com/bundle.js"></script>
<script src="https://order.example.com/bundle.js"></script>
<script src="https://profile.example.com/bundle.js"></script>
<div id="micro-frontend-root"></div>
<script type="text/javascript">
// 这些全局函数是上面脚本暴露的
const microFrontendsByRoute = {
'/': window.renderBrowseRestaurants,
'/order-food': window.renderOrderFood,
'/user-profile': window.renderUserProfile,
};
const renderFunction = microFrontendsByRoute[window.location.pathname];
// 渲染第一个微应用
renderFunction('micro-frontend-root');
</script>
</body>
</html>
Web Components
基座模式的演变,主要区别在于使用 Web Component 代替全局变量。
五、共享内容
- 组件库
为了解决视觉一致性,应用间共享可重用的 UI
组件库。
与任何共享内部库一样,库的所有权和治理权很难分配。 - 共享内容库
对于这个小型应用而言,组件库会显得过大。因此,我们有一个小
共享内容库,其中包括图像、JSON数据和CSS,这些内容被所有其他微应用共享。 - 依赖库
为避免每个页面重新下载相同的依赖项,如react、react-dom等,在 webpack 配置中将库标记为外部库(
externals),子应用 index.html 以 script 标签形式从共享服务器引入。
六、跨微应用间通信
- 使用自定义事件通信,是降低耦合的一种好方法。
- 可以考虑 React 应用中常见的机制:自上而下传递回调和数据。
- 第三种选择是使用地址栏作为通信桥梁 。
七、后端通讯
- 垂直系统的前后端一体化
- 身份验证和鉴权
八、测试
在测试方面,我们看不到单体式前端和微前端之间的太大差异。
显而易见的差距是容器应用程序对各种微前端的集成测试。
九、原理解析
1、路由问题
2、应用入口
协议入口
3、应用加载
single-spa:开放式,可以通过script、systemjs实现
缺点:
- 必须手动实现应用加载逻辑,挨个罗列子应用需要加载的资源,这在大型项目里是十分困难的
- 只能以js文件为入口,无法直接以html为入口,使得嵌入子应用变得很困难,因此,
single-spa不能直接加载jQuery应用
qiankun:qiankun进行了一次封装,给出了一个更完整的应用加载方案,将其封装成了npm插件import-html-entry
getExternalStyleSheets、getExternalScripts、execScripts
4、js隔离
- 快照沙箱
- Proxy 代理沙箱
5、css隔离
子应用之间样式隔离
- Dynamic Stylesheet 动态样式表,当应用切换时移除老应用样式,添加新应用样式。
主应用和子应用之间的样式隔离
- BEM(Block Element Modifier) 约定项目前缀
- CSS-Modules 打包时生成不冲突的选择器名
- css-in-js
- Shadow Dom 真正意义上的隔离
qiankun实验性方案:experimentalStyleIsolation