背景
近几年前端技术发展迅猛,框架组件层出不穷,导致企业中慢慢沉淀了很多不同技术栈的前端应用。同时随着前端业务复杂度的上升,巨石应用开始出现。如何治理庞大的前端应用慢慢成为了企业关注的问题。微前端就是带着这样的使命,慢慢走入大家的视野。今年业界很多大型互联网公司纷纷开始落地实践微前端,以解决自身前端系统臃肿,技术栈混杂的问题。
那么到底什么是微前端,什么情况下该选用微前端,如何实现微前端呢?
下面就带大家一探究竟。
微前端的定义
Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略
微前端架构具备以下几个核心价值:
技术栈无关
主框架不限制接入应用的技术栈,微应用具备完全自主权独立开发、独立部署
微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新-
增量升级
在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
独立运行时
每个微应用之间状态隔离,运行时状态不共享
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
而这四个价值中,技术栈无关是重中之重。因为其他三个特性并不是微前端特有,可以用其他技术手段去实现。
通常,拥有巨石应用的企业,不单单有三年或五年陈的项目,还有N年陈的。这些技术栈的宽度不仅仅是从vue到react,甚至会包含php、asp、jsp的十年陈酿。
能够帮助企业兼容旧系统,并且低成本,增量的方式重构到新技术,才是微前端核心使命。而微前端容器本身,可以用自己喜欢的任意前端框架来实现或者手撸一个。其基础架构如图:
微前端容器要解决的问题
路由系统
应用间通信
样式隔离
JS沙箱
在讲微前端技术之前不得不提一下iframe,在微前端技术出现前,iframe其实是一个比较好的解决方案。但它的存在的问题很多:
移动端不友好
变量作用域
内外层交互
布局操作复杂
如果抛开上面的问题,iframe是一个近乎完美的微前端容器解决方案,浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但iframe是否就退出历史舞台了,我个人持保留意见,会在下面进行观点说明。我们先看目前微前端的解决方案:
路由系统
以vue-spa为例,路由都是指向工程相对路径或cdn绝对路径的一个js文件。当我们路由切换时,由webpack运行时去处理chunk和module的加载。但在微前端下,模块级别变成了应用级别,这就意味着有一个url注册的要求。基本处理流程如下:
注册微应用,指明资源地址
路由切换时,找到注册地址进行请求转发
加载子应用,这里有两种方式:
-
Config Entity
路由注册时,要配置应用依赖的js,这种方案优势是减少了运行时的解析消耗。最大的问题是ConfigEntry的方式很难描述出一个子应用真实的应用数据信息。
-
Html Entity
优点是接入应用的信息可以得到完整的保留,接入应用地址只需配一次,子应用的原始开发模式得到完整保留,因为子应用接入只需要告知主应用html在哪,包括在不接入主应用时独立的打开。它的缺点是将解析的消耗留给了运行时。
应用间通信
这块目前业界比较一致,就是围绕window对象,结合Custom Event以及变化监听,或者url去进行应用间的通信,不再累述。
下面两点可以说是微前端容器的核心了。
样式隔离
由于微前端场景下,不同技术栈的子应用会被集成到同一个运行时中,所以我们必须在框架层确保各个子应用之间不会出现样式互相干扰的问题。而在实际实现的时候,更需要解决的是主应用和子应用之间的样式干扰问题。应对方法呢有下面几个:
BEM & CSS Modul
通过约定的方式,来规避样式干扰,比如以子应用为前缀。但该方案问题有:
需要开发人员严格遵守规范,不确定性太多。
难以应对同一组件库不同版本样式的冲突问题。
对遗产项目的接入,需要大幅改造。
Shadow DOM
基于 Web Components 的 Shadow DOM 能力,我们可以将每个子应用包裹到一个 Shadow DOM 中,保证其运行时的样式的绝对隔离。
我们简单看一下Shadow DOM的概念:
Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样。
简单来说,Web Components内的样式的作用范围,只在custom element内有效,可以做到标记结构、样式和行为完全的隔离。这应该是一个很好的解决方案,但不幸的是:
兼容性问题
子应用的组件或脚本在,custom element构建Dom节点,那这些节点就无法应用样式,常见的比如模态框。
对Deeplink以及JsBridge的交互不友好
对遗产项目的接入,需要大幅改造
微前端的核心使命,就是解决遗产项目,需要大幅改造的方案,肯定不能放在首位。
Dynamic Stylesheet
把样式放在子应用内部,跟随子应用的生命周期流转。
<html>
<body>
<div id="microApp">
<link rel="stylesheet" href="XXX.css">
<div>...</div>
</div>
</body>
</html>
当子应用的被卸载,即microApp的innerHTML清空时,样式也就自然而然的被卸载。该方案可以很好的兼容遗产项目,不用做大的改造,但还是无法规避样式冲突问题,因为作用域还是全局。同时css的下载过程会造成页面的晃动。
到底应该如何做呢?一个综合上述方案长处的解决办法:
利用import-html-entry来加载解析子应用的html
合并css到inline模式,避免下载过程中的闪烁
然后对样式动态scoped化,即解析html的样式,并追加子应用标识的前缀
基于MutationObserver,去监听节点变化,循环进行样式scoped化
这样,对于接入的遗产项目,不需要去做样式方面的调整。
整体来讲,样式隔离通过上述几点,可解决大部分的场景,但依然做不到100%。如果是新项目,可以提前定义样式规范来规避问题。
JS沙箱
上面讲了样式污染需要隔离,js也同样需要进行隔离。其处理的主要流程是记录window对象的变化,其他内容有:
- window属性变化
基于Proxy方式:clone一份window,并劫持,子应用的脚本都在该clone对象上执行。大体如下:
//复制window对象
let cloneWindow = clone(window)
//所有子应用的js动作都在cloneWindow上执行
new Proxy(cloneWindow ,{
get: function(obj, prop) {
// 从自定义window对象中获取
return obj[prop];
},
set: function(obj, prop, value) {
// 记录属性变化,放入自定义window对象
return true;
},
defineProperty:function(){
},
...
})
当浏览器不兼容Proxy的时候,可以采用,备份和还原window对象的方式。
细节需要处理的内容还有很多,上述只是一个主体思路。
-
windowListener
可以利用下面方式劫持事件注册和销毁方法
const proxyAddEventListener = window.addEventListener;
const proxyRemoveEventListener = window.removeEventListener;
window.addEventListener = (type, listener, options)=>{
//记录下来
return proxyAddEventListener.call(window,type, listener, options);
}
HistoryListener
Interval
动态Append
这三者和windowListener类似,都是通过类似劫持原生方法的方式,去记录还还原变化,不再累述。
对于样式隔离和js沙箱,我们再回顾一下实现关键点:
样式隔离主要是通过动态css-scoped来实现
js沙箱主要是通过劫持,记录变化,还原来实现
再谈IFrame
通过上述讲解,其实现方式是运行在同一上下文中。但从安全角度来看,接入的三方系统的脚本,不加隔离的去运行,是极具风险的。而IFrame能很好的帮我们解决该问题。所以还要细分:
可信度高的站点接入,可以采用微前端技术。
可信度低的站点接入,还是采用iframe的方式。
所以,iframe在退休前还要发挥点余热。另外WICG有一个关于Portals的提案,目前看,属于iframe的增强版,值得期待。
你真的需要微前端么?
上面我们讲述了微前端的常用技术以及大体实现流程。在准备使用微前端技术前,我希望使用者问自己几个问题:
你是巨石应用或可能成为巨石应用么?
你是不是有很多陈旧的技术栈需要兼容升级?
你是不是有很多跨组织的团队需要联合工作?
如果不同时满足这三点,建议慎重使用微前端,它未必有你现在简单的架构好。从实现也可以看出,它是有很多性能损耗的。
不要为了使用微前端而使用微前端。如果公司只有一个公众号,一个官网,要去搞微前端,那就有点用牛刀的味道了。
结语
本文做了微前端概念以及基本实现方式的介绍,但里面细节问题还有很多,需要在落地过程中去解决改善。同时,技术的落地一定是储备在前,然后业务驱动,最终落地实践。
参考内容: