什么是微前端?
微前端 (micro-frontends) 术语在 2016 年在
TECHNOLOGY RADAR
中被提及。
该网站叫做 thoughtworks,有个叫
雷达
(radar) 的技术期刊,用于持续追踪有趣的技术是如何发展的,每种技术被称之为
条目
。
该条目提到了
微服务
中,多服务可
独立部署
并易于
扩展交付
的特性。而随着前端
单页面应用
(SPA) 的流行,构建大型应用需要更为合理的前端架构来管理
多开发人员
和
多团队
开发带来的复杂性。
微前端的含义
微前端应该有两方面的含义:
-
从架构来说,
微前端
采用微服务的一些设计理念,是实现组合多应用的一种技术架构;
-
每个微 (子) 应用,我们可以称之为
微前端
。
优势
-
技术栈无关
主框架不限制接入应用的技术栈,微应用具备完全自主权
-
独立开发、独立部署
与微服务一样,微前端的独立部署能力是关键。这会缩小任何给定部署的范围,从而降低相关风险。无论您的前端代码以何种方式或在哪里托管,每个
微应用
都应该有自己的
持续交付管道
,用于
构建
、
测试
并将其部署到
生产环境
。
-
团队自治
我们的团队将围绕业务功能的垂直切片组建,而不是围绕技术能力。每个微应用都是封装的单个页面,并由单个团队端到端维护,这带来了团队更高的凝聚力。
-
增量升级
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略。
-
独立运行时
每个微应用之间状态隔离,运行时状态不共享。
微前端架构旨在解决
单体应用
在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个
巨石应用
(Frontend Monolith) 后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
为什么不用 iframe
这里
给出了解释,主要有以下几点:
-
URL 状态不同步。iframe 的页面 url 中的状态信息并不能同步到父窗口,无法使用浏览器的前进后退功能。
-
DOM 结构不共享。iframe 的页面布局只针对于 iframe 窗口 (例如:全局弹框无法给出合理布局) 。
-
全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的微应用中实现免登效果。
-
慢。每次微应用进入都是一次浏览器上下文重建、资源重新加载的过程。
集成方式
Iframe 集成
在浏览器中组合应用程序的最简单方法之一是不起眼的 iframe。就其性质而言,iframe 可以轻松地从独立的子页面构建页面。它们还在样式和全局变量方面提供了很好的隔离度,不会相互干扰。
虽然不推荐,但是这里仍然展示其基本使用方式:
<html>
<title>Feed me!</title>
</head>
<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>
Javascript 集成
这种方法可能是最灵活的方法,也是我们看到团队最常采用的方法。每个
微应用
都使用
<script>
标签包含在页面上,并在加载时公开一个
全局函数
作为其
入口
点。然后
容器应用程序
确定应该挂载哪个
微应用
,并调用相关函数来告诉微前端何时何地渲染自己。
下面是一个比较粗糙的实现,不过它演示了基本技术:
<html>
<title>Feed me!</title>
</head>
<h1>Welcome to Feed me!</h1>
<!-- 这些 bundle 不会立即渲染任何东西 -->
<!-- 每一个 boudle 提供一个“入口函数”添加到 window -->
<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];
// 提供一个元素 ID ,作为渲染容器
renderFunction('micro-frontend-root');
</script>
</body>
</html>
Web Components 集成
这是前一种方法的一个
变体
是为每个微前端定义一个 HTML 自定义元素供容器实例化,而不是定义一个全局函数供容器调用。
<html>
<title>Feed me!</title>
</head>
<h1>Welcome to Feed me!</h1>
<!-- 这些 bundle 不会立即渲染任何东西 -->
<!-- 每一个 bundle 都自定义了一个元素类型 -->
<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 webComponentsByRoute = {
'/': 'micro-frontend-browse-restaurants',
'/order-food': 'micro-frontend-order-food',
'/user-profile': 'micro-frontend-user-profile',
// 路由对应组件
const webComponentType = webComponentsByRoute[window.location.pathname];
// 把组件挂载到容器上。
const root = document.getElementById('micro-frontend-root');