乐趣区

关于前端:推开微前端的门

导读:“微前端”和“微服务”相似,是这两年被频繁提及的名词。web 开发从前后端放在一起的单体利用,演进成前后端拆散的 SPA,这些扭转让前后端实现了开发解耦、独立公布。解耦让开发、调试、公布的过程都更加自在灵便,但随着业务的倒退,中大型的 SPA 逐步成为了“巨石利用”(Monolithic Applications),当初因为前后端拆散带来的“自在”也渐行渐远,模块的拆解越来越被须要。

全文 5574 字,预计浏览工夫 14 分钟

本文次要分享两方面内容:

  1. 思考什么样的零碎或者前端须要微前端
  2. 简述微前端工程中须要关注的一些设计要点

本文仅会从选型和设计上做一些思考总结,「不会」重点介绍以下内容:

  1. 深刻介绍某些开源框架并比照
  2. 如何设计一个十分通用的微前端框架
  3. 针对某个设计点的实现形式十分详尽的介绍

心愿能给正在迟疑是否应用微前端的你一些思路。

一、什么样的零碎或者前端团队须要微前端?

如果你有上面案例的窘境,微前端可能是你的一个抉择。假如一个 SPA 的前端模块,蕴含了 ABCD 四个模块,它们盘根错节地依赖了多个独自部署的后端服务,如下图:

图一

上线当天,你们可能须要梳理一个模块依赖图谱,以确定以后的上线程序。

  • 前端模块 A、B、C、D 均属于一个须要整体公布的 SPA;
  • 模块 C 上线依赖服务 2、3,模块 D 依赖服务 1、4;
  • 因为模块 C 和 D 须要一起上线,和服务 2 毫无关系的模块 D、服务 1、4 都须要期待服务 2 的公布。

这个 SPA 模块将成为整个上线日繁忙的十字路口,上线、验证操作须要排队期待,相干同学不厌其烦。

更可怕的是,当大家含辛茹苦实现深夜上线后,任何发现了问题须要紧急止损的模块,都常会带来雪崩似的回滚操作。如果上线计划筹备不齐备,还须要长期确认影响面,梳理回滚操作的图谱程序。这种消耗掉的止损工夫,在一些零碎、产品中经常是难以承受的,造成回滚反馈链的模块别提压力有多大了。

联合这个例子,有以下几个参考点,如果它们恰好命中了你的痛点,微前端会是你的一个「解决办法」。

  1. 并行开发的模块数量多
    当你有多个性能绝对解耦独立的模块,须要并行开发、公布。
  2. 协同开发的人员数量多
    当单个利用开发同学的人数超过肯定规模(如大于 10 人),且每个人负责的子模块绝对固定,人数增多协同效率往往会指数增长。
  3. 协同开发的团队数量多
    这是一个乏味的点,和 2 的区别,次要在“屁股决定脑袋”的“屁股”上。如果你们因为种种原因,不得不跨团队保护同一个工程,那你们可能面对着开发权限、标准、节奏等很多协同点,这些协同有时候会因为跨团队效率更低。
  4. 公布频率高
    这条须要与 1 和 2 联合起来看,人数多然而公布频率较低的模块,协同沟通等老本就成了伪命题。当然,多人协同开发的利用,大概率是倒退中的业务,须要频繁的迭代。
  5. 须要跨技术栈
    这一点可能是很多团队抉择微前端的重要理由。你们可能保护了一个经营多年、零碎繁冗、技术栈较古老的零碎,你心愿能引入新的技术栈但又没有人力一下子重写零碎。先把零碎微前端化,再渐进式地分子模块重写,会是一个工作节奏可控、品质危险较低的方法。
    然而,针对跨技术栈这个命题,对于独自产品或平台来说,我集体认为有个「“陷阱”」。从团队久远开发效率、平台的性能优化空间、体验统一等多种角度来看,长期不加管控的跨技术栈是有危险的。一些小粒度的形象(组件、业务模块等)难以被高效复用,一些降级难以被间接利用到全局等。这些问题导致的效率降落,可能会覆盖独立开发、公布带来的效率晋升。Martin Fowler 在介绍微前端的收益之一时时也写的是「Incremental upgrades」,渐进式更新不等同于永恒区别。因而,除非你做的只是一个门户,对子模块的一致性没有很高的协同要求,我更倡议跨技术栈是渐进式迁徙的两头态。

二、抉择微前端须要关注的设计点

通过了 checklist,如果一些问题让你决定微前端革新,上面的一些利用设计点兴许能对你有所帮忙。

2.1 主模块与子模块

首先你须要一个主模块,它可能是一个 HTML,一个或一组 top app bundle,咱们称它为「APP Shell」或「Nutshell」(https://martinfowler.com/arti…),你的顶层逻辑须要加载一些前置依赖,初始化 entry 内容,依据路由等信息加载渲染子模块。对于运行时加载 sub app 的实现形式有很多,这里简略列举几个,就不一一开展了:

  1. subapp 是一个 iframe,最简略暴力的实现形式,同时有 iframe 实现页面的所有限度。优化空间、顶层控制力无限,集体不举荐。
  2. subapp 是 web component,追随路由切换实例化组件。你须要思考浏览器的兼容性限度。
  3. subapp 是一个独立公布的子 bundle。子 bundle 须要定义一些生命周期 hook,如 register、mount、unmount 等。这个办法应该是比拟遍及应用的。

2.2 单实例 vs 多实例

依据运行时实例的数量,业界有了「单实例」和「多实例」两种 APP。「单实例」是指,在运行时同一时刻仅一个子模块被激活,下图二是一个常见场景,子模块通常追随路由装载 / 卸载。

图二

「多实例」则是运行时同一时刻可能会有多个子模块实例在激活状态。下图三是一个场景,路由和子模块不再是一对一关系,可能成了多对多。

图三

实现计划是由业务和服务拆解来决定的,倡议把性能内聚、保护团队绝对收敛的模块独自拆分,而后再看运行时是否须要在同个路由下同时呈现「多实例」。无论运行时有几个子模块,你都须要一个路由和页面内容的映射关系。这个关系对某些 APP 来说可能是开发编译时能够预判的枚举关系,那么你可能须要保护一个如下的 map:

// 单实例 - 示意 const subAppRoutes = {route1: ‘https://your.static.server.co…’, route2: ‘https://my.static.server.com/…’, route3: ‘https://other.static.server.c…’};

// 多实例 - 示意 const subAppRoutes = {route1: [  {   subApp: ‘https://your.static.server.co…’,   layout: {              // 省去布局形容信息}  },  {subApp: ‘https://my.static.server.com/…’} ], route2: [{   subApp: ‘https://your.static.server.co…’},  {subApp: ‘https://my.static.server.com/…’} ], route3: [{   subApp: ‘https://my.static.server.com/…’} ]}

入口模块会依据路由和相干配置,实现资源的加载,并依据生命周期协定挂载子 APP。后面曾经提到,你须要一个路由。能够通过一些路由前缀束缚规定来防止子模块抵触。对于路由的设计实现文章很多,不是本文重点,暂且按下不表。

如果你的零碎非常灵活、页面类型和数量不可收敛,那么你可能须要一个动静存储的数据结构以及服务来代替浏览器中的 location 路由,通过定义零碎的页面构造 schema,来形容以后页面中每个微前端模块须要搁置的地位和布局(如下面“多实例 - 示意”中的 route1)。schema 的引入让低码(Low-code)开发成为可能,将来你通过简略的数据结构配置,即可用微前端模块拼凑出一个 APP 页。当然,这须要有额定的存储和业务逻辑,零碎的复杂度会减少。

2.3 子模块通信

大多数零碎的子模块还是不能躲避一些相互影响的。假如你有一个功能模块 A,和内容举荐模块 B,B 的内容须要依据 A 的操作连锁反应。有几种常见的做法:

  1. 全局数据共享
    有个全局 store,A 将数据变更写入 store,B 监听 store 的 change 并做出响应,单向数据流的设计能让开发调试变的更加容易,当然你须要躲避分模块对繁多 store 内容的抵触问题,这个和路由抵触的解决方案相似,比方减少一些命名空间。剩下的内容和你接触过的各种繁多 store 的设计都类同,不再赘述。
  2. 事件通信
    提供全局的 EventBus 能力,A 派发事件,B 承受事件并响应。这是个平平无奇的事件通信,但在微前端实现中有一个点须要关注。各子模块之间加载和实例化的过程大多是独立的、异步的,A 公布事件时,B 还没有实例化实现,那么这个音讯可能会被漏掉。通过 1 中共享数据的形式能够解决大部分问题,如果你更喜爱用事件通信来解决,则须要在设计实现 EventBus 的时候思考这个性能,例如缓存事件队列,当 B 在启用事件监听的时刻,回顾一下缓存事件队列中有没有曾经派发且须要被响应的事件。

当然,你可能还有一些思路,比方二次封装。在 A 和 B 之上封装 C,负责两个模块的调度,但这个方法和上述两条不是一个层面,比方 C 的实现也往往须要和 A、B 的通信或者数据共享。同时适度二次封装毛病也很显著,封装的模块数量会随着业务的组合收缩,难以收敛保护。在实际过程中,1 和 2 的能力你应该「都须要建设」。

三、性能优化小贴士

除了素日性能优化的惯例操作,在微前端架构下,有两个小点值得关注。

3.1 多实例按需渲染

如果你的 APP 页体面模块较多,能够思考按需渲染。如下图四的场景,能够依据视口和模块地位决策以后模块是否须要实例化渲染。以此来缩小首屏浏览器 scripting 和资源加载的工夫。当然,随着视窗滚动提早渲染模块的体验,也能够通过闲时预渲染来优化解决。

图四

3.2 反复打包优化

微前端的一个问题是,一些公共能力如何解决。“微”带来了自在,但自在过头了可能就是反复、难以标准要求。比方一些根底的 UI、动静申请能力等,每个模块独自实现打包,都会成为线上运行时的负累。

对于公共能力的抽取也有一些“套路”:

  1. Entry 提供的全局实例。
    你的顶层 APP 能够给每个实例化的子模块注入一个全局能力援用,提供一些简直每个模块都要应用的能力,例如 ajax 申请能力、业务埋点监控能力等。益处是一些底层能力你能够很好地管制起来,毛病是子模块和 entry 之间的耦合会更深一些。如果你有一个子模块须要被利用在不同的 entry APP 中,那针对每个 APP,你可能须要一个适配器层来屏蔽差别。
  2. 抽取公共内容打包。
    公共内容中最常见的就是 polyfill 了,每个模块通过应用 「usage」(https://babeljs.io/docs/en/ba…) 独自打包,益处是开发、线上环境统一,毛病是 polyfill 内容会高度反复,如图五。

图五

优化的思路也不言而喻,将 polyfill 以「entry」形式仅引入一次,如图六。

图六

但如何保障这个公共 polyfill 也是按需优化的呢?有一些方法,例如:

  • 依据运行时环境实时加载 polyfill,如 polyfill.io。比拟重的计划,集体感觉收益不肯定十分划算。
  • webpack@5 的「module federation」。还在 beta 阶段,倡议生产环境谨慎(如果你 webpack 升 5 之后工程还跑得起来的话)。
  • 约定 polyfill 的白名单 / 黑名单。这个是咱们工程中最初应用的计划,理由是轻量、稳当,工程角度感觉“划算”。当然这个形式会有一个问题跃然纸上,独自开发的模块怎么保障应用的语法不会超出白名单?咱们的解决方案是:为了在灵活性上有肯定的标准束缚,开发微服务子模块须要应用一个咱们封装的 dev cli workspace,在开发时实现 App 级别的变量注入、语法校验。公布编译阶段也会有相应的管制,因而「开发环境工具」也是微服务革新的利器。

以上是咱们在微前端工程化技术选型、架构设计中的局部思考和教训,篇幅限度不再开展更多。

3.3 自在 vs 标准

最初,总结一下我对微前端的一些集体认识。灵便、自在可能是很多大型项目、团队设计的冀望。但我认为,一个 APP 在灵便同时,控制力和标准也是十分重要的。你可能会心愿零碎能从数据上标准、交互视觉上高度一致、体验低劣、性能良好。那么,在一开始向微服务革新的时候,就须要提前设计规范管制。否则,微前端热潮之下不久,可能又要开启一片“微前端治理”相干的探讨。毕竟,从“限度”到“自在”容易,从“自在”到“限度”可难了。

参考文献

  • Micro Frontends: https://martinfowler.com/arti…
  • @babel/preset-env · Babel: https://babeljs.io/docs/en/ba…
  • Polyfill.io: http://polyfill.io/
  • Module Federation | webpack: https://webpack.js.org/concep…

嘉宾介绍:

马海娜,百度商业平台研发部前端资深研发工程师,次要负责广告托管业务的前端架构。专一于在疾速迭代的业务中打造高效、可扩大、体验良好的前端业务架构。喜好撸码和撸猫。

举荐浏览:

|百度商业大规模高性能全息日志检索技术揭秘

|疾速剪辑 - 助力度咔智能剪辑提效实际

|短视频个性化 Push 工程精进之路

———- END ———-

百度 Geek 说

百度官网技术公众号上线啦!

技术干货 · 行业资讯 · 线上沙龙 · 行业大会

招聘信息 · 内推信息 · 技术书籍 · 百度周边

欢送各位同学关注

退出移动版