微前端

背景


近几年前端技术发展迅猛,框架组件层出不穷,导致企业中慢慢沉淀了很多不同技术栈的前端应用。同时随着前端业务复杂度的上升,巨石应用开始出现。如何治理庞大的前端应用慢慢成为了企业关注的问题。微前端就是带着这样的使命,慢慢走入大家的视野。今年业界很多大型互联网公司纷纷开始落地实践微前端,以解决自身前端系统臃肿,技术栈混杂的问题。

那么到底什么是微前端,什么情况下该选用微前端,如何实现微前端呢?

下面就带大家一探究竟。

微前端的定义


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的十年陈酿。

能够帮助企业兼容旧系统,并且低成本,增量的方式重构到新技术,才是微前端核心使命。而微前端容器本身,可以用自己喜欢的任意前端框架来实现或者手撸一个。其基础架构如图:

image

微前端容器要解决的问题


  • 路由系统

  • 应用间通信

  • 样式隔离

  • JS沙箱

在讲微前端技术之前不得不提一下iframe,在微前端技术出现前,iframe其实是一个比较好的解决方案。但它的存在的问题很多:

  • 移动端不友好

  • 变量作用域

  • 内外层交互

  • 布局操作复杂

如果抛开上面的问题,iframe是一个近乎完美的微前端容器解决方案,浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但iframe是否就退出历史舞台了,我个人持保留意见,会在下面进行观点说明。我们先看目前微前端的解决方案:

路由系统

以vue-spa为例,路由都是指向工程相对路径或cdn绝对路径的一个js文件。当我们路由切换时,由webpack运行时去处理chunk和module的加载。但在微前端下,模块级别变成了应用级别,这就意味着有一个url注册的要求。基本处理流程如下:

  1. 注册微应用,指明资源地址

  2. 路由切换时,找到注册地址进行请求转发

  3. 加载子应用,这里有两种方式:

  • 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 元素一样。

image
简单来说,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的增强版,值得期待。

你真的需要微前端么?


上面我们讲述了微前端的常用技术以及大体实现流程。在准备使用微前端技术前,我希望使用者问自己几个问题:

  • 你是巨石应用或可能成为巨石应用么?

  • 你是不是有很多陈旧的技术栈需要兼容升级?

  • 你是不是有很多跨组织的团队需要联合工作?

如果不同时满足这三点,建议慎重使用微前端,它未必有你现在简单的架构好。从实现也可以看出,它是有很多性能损耗的。

不要为了使用微前端而使用微前端。如果公司只有一个公众号,一个官网,要去搞微前端,那就有点用牛刀的味道了。


结语


本文做了微前端概念以及基本实现方式的介绍,但里面细节问题还有很多,需要在落地过程中去解决改善。同时,技术的落地一定是储备在前,然后业务驱动,最终落地实践。

参考内容:

  1. https://qiankun.umijs.org/

  2. https://developer.mozilla.org/

  3. https://micro-frontends.org/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342