前端工程化实践|微前端是如何在团队落地的(一)?
前言
开篇聊聊我为什么会来写这篇文章。
起因是最近被问到了微前端相关的问题,但是我很明显没有去深入准备相关的知识点,所以回答的很差。本着从哪里进坑,就从哪里爬起来的原则,我花了一点时间总结了部分微前端相关的知识,如果你觉得感兴趣可以继续读下去。
没有总结成体系,会让自己的思维发散受到干扰。
本文的内容我会从以下几个角度来体系化的阐述微前端在团队中的落地。
- 什么是微前端?
- 技术价值
- 实现的原理
什么是微前端
微前端是很新的概念么?其实并不是的。参考ThoughtWorks。
我也是好奇搜索了一下才发现的,也不知道是不是存在拷证的。
毕竟那个时候(2016),我还在上学,压根不知道前端是个什么玩意。
那么回到问题本身,什么是微前端?
下面是这个理解来自Micro Frontends文章的理解。
微前端是一种用于构建多个可以独立发布功能的现代Web应用程序的技术、策略和方法。
总结而言,微前端更多的是一种对现代web前端应用痛点的策略和解决方法的统称。而围绕微前端策略而衍生的技术方案或者说手段,我其实更喜欢称之为生态。
现阶段
qiankun
、single-spa
、无界
非常受社区喜爱,它们都是围绕微前端策略而实施的技术解决方案。
技术价值
从我的理解上来看,微前端本身最大的价值我的理解可以用简单的一句话来讲通:
从技术层面对业务解耦,从用户角度为应用聚合。
从用户使用者来说,很希望对产品的使用不要太复杂。尤其是内部人员使用的系统而言,最大的痛苦就是没有一站式管理的体验,大多数都是通过办公软件的快捷入口,打开多个不同的业务系统来处理自己的任务。一旦涉及到系统间的交割体验就会非常的差。这就是从产品体验上带来的gap了。
如果没有SSO,账号体系没有打通的话绝对是被匿名吐槽的。
在这里我就不多谈一些用户侧的问题了,回到从技术本身出来。微前端到底解决了什么问题?我通过自己的亲身经历来罗列一些痛点:
- 项目随着时间跨度和业务发展,参与人员飞速扩张,模块越来越多,应用体积越来越大。
- 业务组件不规范,不同业务领域组件相互引用,对测试同学不友好,无法全面回归导致背锅。
- 技术负债太多,重构根本无从下手。
- 首次运行和构建缓慢,构建非常bundle需要花费一定时间。
- 全量发布,无法增量升级,有概率出现大风险。
- 等等...
而微前端架构解决的问题在于可以将一个大应用拆分成若干个子应用。且每个子应用都能拥有自己的独立仓库、构建工具、技术栈、CICD等自主选择权,每个微应用之间都独立自主,极大的利好跨团队应用协作。
实现原理
简单的介绍了微前端的一些相关知识,了解了它的动机。那么从技术侧微前端如何来实现呢,换个角度来说一个微前端架构需要具备哪些特定的功能呢?
简单的可以列举如下几点概念知识点,稍后会来细谈。
- 路由劫持
- 全局通信
- 沙箱隔离
- 应用加载
- 生命周期
- 其他...
路由劫持
路由劫持是啥意思呢?在SPA应用中,将除域名外的二级地址称之为路由。不管是vue-router
还是react-router
都是通过路由渲染不同的视图组件来显示页面的内容。
那么路由劫持的目标对象也就很清晰了,分别来做以下几件事情:
- 由于
pushState
和replaceState
会导致路由的变更,但不会触发对应的事件,所以一般都会做一个劫持重写逻辑来主动触发处理。 hashchange
和popstate
的话则可以通过addEventListener
来做事件订阅。
window.addEventListener("hashchange", () => {});
window.addEventListener("popstate", () => {});
主要是明白微前端路由的原理,其实本质上就是sdk需要来接管浏览器的状态变更,然后方便后续的工作。
全局通信
对与子工程而言,虽然做到了独立自主,但是本质上还是一个一体化应用,因此子应用与主工程的通信是避免不了的。毕竟子应用还是要共享主工程的部分用户身份相关内容。
不可能每个子应用都单独来获取一下相关的内容,这样对用户来说是爆炸的。
所以一般微前端架构都会有全局通信这个功能。所做的事情类似于一个Provider
。向子应用传递一个globalValue
和onGlobalValueChange
等一系列配套的钩子方法。
实现方案的话也多种多样,常见的如发布/订阅
,Mount挂载透传
,URL Params
等等。
在这里我就不细说了,有兴趣的同学可以看下qiankun
的实现。传送门
沙箱隔离
沙箱隔离算是微前端的核心技术了。那么沙箱究竟需要隔离哪些东西呢?我们都知道一个网页由html
,js
, css
组成,在这三者中,如果在同一个上下文中运行,我们肯定是需要将js
,css
进行一个环境隔离,不然的话有很大几率会造成样式与逻辑冲突,影响渲染显示效果。
基于现阶段的微前端框架实现方案,我也先来罗列下目前隔离的方式有哪些。
JS隔离
js隔离一般需要将全局window和子应用window进行隔离。避免window对象被无序的更新,划分好对应的领域。
-
在支持
Es6 Proxy API
的浏览器中,可以通过它来对window对象进行修改记录,在子应用卸载的时候会删除修改的记录碎片,当应用在此激活的时候会将记忆碎片注入。从而来达到一个模拟沙箱的作用。 -
当浏览器不支持
Proxy
的时候,则需要做降级处理,通过比对的方式来做记忆碎片的缓存,其原理就是创建一个快照window,在子应用卸载的时候将当前与快照的属性做一次diff比对,挑殓出不一致的属性保存起来。当然,也可以采用vue的策略,Object.defineProperty
似乎也是一个不错的选择,但是你要接受它的缺陷并修补它。
CSS隔离
css的隔离的话就简单很多了,大多数情况下可以使用以下几种方式:
- Shadow DOM:
web-components
的Shadow DOM
与真实DOM之间是完全隔离的,因此也不会存在样式冲突的问题。 - 规则命名:通过BEM约定className的类名可以主动避免样式冲突的问题,也可以使用
css in js
与css module
可以通过工程化的手段来避免。这一类方案都属于规则命名的方式。
生命周期
使用过vue
和react
的同学都知道,每一个组件从加载到销毁都有一个周期过程,这个过程在技术上称之为生命周期
。微前端的子应用也是如此,每一次切换到微前端路由的时候都会重新将应用进行挂载,离开页面的时候同样也会将对应的应用卸载。
以single-spa
为例子,它的生命周期如下:
*bootstrap
:注册引导*mount
:挂载*unmount
:卸载其他
:类似于unload这些。
export async function bootstrap(props) {...}
export async function mount(props) {...}
export async function unmount(props) {...}
总结
本篇文章,主要简述了微前端的一些基本信息,尽可能的简洁的将核心内容点暴露出来。在后面的文章当中,会慢慢的由点到面来仔细做好微前端架构的实践。如果觉得对自己有帮助的话,可以打个点方便后续来看下一集。
本章节或许有些内容没有覆盖到,可以在评论区来回复我,我会在下一章的内容中补充。感谢大家支持。
转载自:https://juejin.cn/post/7202593633332363324