查看原文
其他

微前端概述

京东物流 冯其帅 京东技术 2022-06-25

Tech


导读

文由浅到深地对微前端进行了概括性介绍,读者可以了解到微前端的概念、微前端的特点与价值、微前端的实现方案、一个微前端框架应具备的功能,以及微前端的适用场景。读者可以多关注下本文提到的各个开源的优秀的微前端实现方案,通过对比及借鉴来实现一套适合自身业务的微前端方案。




01微前端是什么


传统的分而治之的策略已经无法应对现代 Web 应用的复杂性,因此衍生出了微前端这样一种新的架构模式,与后端微服务相同,它同样是延续了分而治之的设计模式,不过却以全新的方法来实现。微前端是一种由独立交付的多个前端应用组成整体的架构风格,将前端应用分解成一些更小、更简单的,能够独立开发、测试、部署的应用,并且在用户看来仍然是内聚的单个产品。

:extending the microservice idea to frontenddevelopment; Techniques, strategies and recipes for building a modern web appwith multiple teams that can ship features independently.



02微前端的特点与价值


2.1 独立与自治

独立开发、独立部署、独立自治代码库,从而可提升构建效率、改善交付效率。


2.2 技术栈无限定


技术栈自主性有利于多个不同技术栈的团队协同合作,技术栈的可平滑迁移也对旧有业务的不断迭代和技术升级带来较大的便利性。


2.3 颗粒化解耦与可组合


在大型的前端工程中,对于颗粒化解耦有很高的要求,常基于不同维度划分,例如业务类型颗粒化、技术服务类型颗粒化等等。各个微前端颗粒的可组合性又让多个可交付系列产品有很好的颗粒一致性和整体定制差异化,并能极大减少业务重复开发的资源浪费。



03微前端实现方案


3.1 iframe

从浏览器原生的方案来说,iframe 不从体验角度上来看几乎是最可靠的微前端方案了,主应用通过iframe 来加载子应用,iframe 自带的样式、环境隔离机制使得它具备天然的沙盒机制,但也是由于它的隔离性导致其并不适合作为加载子应用的加载器,iframe 的特性不仅会导致用户体验的下降,也会在研发在日常工作中造成较多困扰,以下总结了 iframe 作为子应用的一些劣势:

  • 使用iframe 会大幅增加内存和计算资源,因为 iframe 内所承载的页面需要一个全新并且完整的文档环境;

  • 由于iframe 与上层应用并非同一个文档上下文,所以:


    事件冒泡不穿透到主文档树上,焦点在子应用时,事件无法传递上一个文档流;
    iframe内元素会被限制在文档树中,视窗宽高限制问题、弹窗类的功能只能在对应的窗口内展示;

    iframe应用加载失败,内容发生错误主应用无法感知。
  •    无法预加载缓存 iframe 内容;

  •    无法共享基础库进一步减少包体积;

  •    事件通信繁琐且限制多。

3.2 Module Federation/EMP(构建时确定依赖关系)


3.2.1 Module Federation:

多个独立的构建可以形成一个应用程序。这些独立的构建不会相互依赖,因此可以单独开发和部署。Multiple separate builds should form asingle application. These separate builds should not have dependencies betweeneach other, so they can be developed and deployed individually. This is oftenknown as Micro-Frontends, but is not limited to that.


提供了能在当前应用中远程加载其它服务器上应用的能力,基于此可以实现一个去中心化的应用部署群,每个应用是单独部署在各自的服务器,每个应用都可以引用其它应用,也能被其它应用所引用。通过new ModuleFederationPlugin配置被远程引用时的路径(exposes)、远程引用的应用、与其它应用之间可以共享的第三方依赖(shared)等。


由上可知,通过shared以及exposes可以将多个应用引入同一应用中进行管理,从而实现微前端架构。


3.2.2 EMP

是一个基于Webpack5Module Federation搭建的微前端解决方案。通过cdn加载微应用,可以动态更新微应用,微应用只需要部署一次便可以提供给任何基于Module Federation的应用使用。每个微应用间都可以引入其它的微应用,无中心应用的概念。可以选择只加载微应用中需要的部分。每一个应用都可以进行状态共享。


3.3  single-spa/qiankun/icestark/garfish(运行时确定依赖关系)


1.:Single-spa是一个用于前端微服务化的JavaScript前端解决方案,实现了路由劫持和应用加载(通过监听url change事件,在路由变化时匹配到子应用并进行渲染),其本身没有处理样式隔离和js执行隔离。其实,single-spa是一个子应用加载器与状态机的结合体。推荐阅读


2.:qiankun是一个基于single-spa的微前端实现库。HTML entry接入方式。在主应用中注册微应用,当微应用信息注册完之后,一旦浏览器的url发生变化,便会自动触发qiankun的匹配逻辑,所有activeRule规则匹配上的微应用就会被插入到指定的container中,同时依次调用微应用暴露出的生命周期钩子(bootstrap、mount、unmount)。如果微应用不是直接跟路由关联的,也可以使用手动加载微应用的方式。


3.:icestark通过微应用入口字段的配置进行应用的渲染,支持多种入口配置形式:url、entry(即html url)、entryContent(即html内容,用于解决html url不支持前端跨域访问的情况)、render/component(仅支持使用React的主应用)。为了能够让icestark响应页面路由的变化并对相应的微应用进行加载,icestark对两类路由事件进行了劫持,即history API的popstate和hashChange,以及window的路由事件pushState和replaceState。支持微模块,一种没有路由、粒度更小的挂件,通常是一个模块或页面,跟页面路由无关,可以随处挂载。

4.:当执行注册子应用相关信息并执行Garfish.run后,此时Garfish框架将会启动路由劫持能力,当浏览器的地址发生变化时,Garfish框架内部便会立即触发匹配逻辑,当应用符合匹配逻辑时将会自动将应用挂载至页面中(也可以手动通过某些事件触发来挂载),并依次触发子应用加载、渲染过程中的生命周期。支持HTML entry 和 JS entry


3.4 Fronts(渐进式)


:Fronts 是一个基于 Webpack 的 Module Federation API 设计的渐进式微前端框架。它强调颗粒间的去中心化依赖管理,并支持多种运行模式来满足不同的微前端架构需求。这个渐进式是指,如果前端工程的各个颗粒并不都支持Module Federation,它依然能以微前端的形式良好地运行,也能自由选择所需要的运行模式;并随着项目升级,可以逐一升级到支持 Module Federation,并最终根据实际需要建立和开启微前端版本控制系统。


3.5 Web Components

浏览器的原生组件,由三项主要技术组成,可以创建可重用的定制元素,不必担心代码冲突。

  • Custom elements(自定义元素):一组JavaScript API,允许定义custom elements及其行为,然后可以在页面中使用。

  • Shadow DOM(影子DOM):一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其它部分发生冲突

  • HTML templates(HTML模板):使用<template>和<slot>元素可以编写不在呈现页面中显示的标记模板,然后它们可以作为自定义元素结构的基础被多次重用。


可以看出来,Web Components是有能力以组件加载的方式将微应用整合在一起实现微前端架构的一种手段:


  • 技术栈无关:Web Components是浏览器原生组件,即可以在任何JS框架中使用

  • 独立开发:使用Web Components开发的应用无需依赖其它应用。

  • 应用间隔离:Shadow DOM的特性,使得引入的微应用间可以达到相互隔离的效果。

    当然,Web Components也有浏览器兼容性的限制。


    :借鉴了Web Components的思想,通过Custom elements结合自定义的Shadow DOM,将微前端框架封装成一个类Web Component组件,从而实现基座应用对子应用的组件化渲染


    《前端架构从入门到微前端》一书中,将微前端的实现方案分为六种:路由分发、前端微服务化、微应用、微件化、iframe、Web Components。





    04微前端框架需要实现的功能


    4.1应用加载


    根据注册的子应用,通过给定的url,加载约定格式的子应用入口,并挂载到给定位置。入口方式通常有两种:HTML和JS,JS做入口更纯粹,HTML做入口更易于旧项目改造。此外,提供预加载功能也很有必要,预加载是指在应用尚未渲染时提前加载资源并缓存,从而提升首屏渲染速度。


    4.2生命周期


    加载、挂载、更新、卸载等。


    4.3路由同步


    子应用的路由切换时,同步更新url;url改变时,同步更新子应用。


    4.4应用通信


    各个应用间可以便捷通信,局部通信、全局通信。


    4.5沙箱隔离


    1.JS隔离:

    • Snapshot。子应用挂载时,先对全局window变量打个快照放闭包里,再把全局window交给子应用,在子应用卸载时通过快照恢复全局window变量。这种方式实际上并没有形成“隔离”,只是防止多个子应用互相“污染”,并且多个子应用之间不能共存,因为全局window只有一个,快照只有一个。

    • Sandbox

      • Wasm VM。重新编译一个Wasm()的JS解释器放在浏览器中,把子应用直接放进这个VM中执行,这种方式隔离非常严格,通信开销非常大。with()+new Function(code)+Proxy。with语法用于改变作用域链,当访问全局变量时进行拦截,不对window进行查找。new Function()将一段字符串解析成一段JS脚本并执行,只能访问全局作用域。Proxy提供的是with和new Function闭包中用到的充当window作用域的对象,通过白名单属性限制能访问真正window上的部分元素,同时对document、history、location进行劫持限制等,从而组成沙箱环境。

    2.CSS隔离:

    命名约定、自动Scope、Shadow DOM、自动卸载、弹窗遮罩。


    4.6异常处理


    对各种异常进行统一处理。




    05微前端的适用场景


    概括来讲,微前端主要解决了两个问题:


    1.应用随着迭代变得越来越大,最终难以维护。

    2.相对独立的几个应用需要融合为一个应用。


    任何一个方案都有优缺点,也不可能适用于所有场景,是否适合的评判标准是收益要大于损失



    推荐阅读搜索中常见数据结构与算法探究(一)
    一起聊聊工作中的功能安全测试
    Deco 智能代码体验版正式上线啦,快来体验设计稿一键生成代码~
    Dubbo负载均衡策略之 一致性哈希

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存