关于前端:字节跳动是如何落地微前端的

10次阅读

共计 14526 个字符,预计需要花费 37 分钟才能阅读完成。

本文内提及的 Garfish 微前端解决方案已开源:https://github.com/modern-js-…(目前的 Garfish 作为字节跳动各部门利用最宽泛的微前端解决方案曾经服务超过 100+ 前端团队,400+ 我的项目),另外字节跳动的古代 Web 工程体系行将开源(Modern.js),深度集成 Garfish 提供了对微前端的原生反对,提供更开箱即用的能力,敬请期待!

微前端的呈现的背景和意义

微前端是什么:微前端是一种相似于微服务的架构,是一种由独立交付的多个前端利用组成整体的架构格调,将前端利用分解成一些更小、更简略的可能独立开发、测试、部署的利用,而在用户看来依然是内聚的单个产品。

微前端诞生在两个大的背景下,在提倡拥抱变动的前端社区能够看到新的框架、技术、概念层出不穷,并且随着 Web 规范的演进,前端利用曾经具备更好的性能、更快的开发效率。但随着而来的是利用的复杂程度更高、波及的团队规模更广、更高的性能要求,利用复杂度曾经成为阻塞业务倒退的重要瓶颈。

微前端就是诞生于 Web 利用日益复杂化的场景中,因为随着网络速度、计算机硬件程度的晋升和 Web 规范的演进,过来 Web 利用用户体验远不如传统的应用软件时代已逐步远去,两者之间在用户体验上的差距一直缩减,并且因为 Web 利用开发速度快、用完即走等个性,导致的一个最终后果就是「能用 Web 技术实现的利用,最终都会通过 Web 来实现」。在近几年涌现了一大批之前只能在传统 PC 软件中能力看到的优良产品,例如:Photoshop、Web Office、Web IDE。只管随着 Web 规范的演进,前端工程化也在一直演变,从模块化到组件化在到当初的工程化,但在面对跨团队大规模开发、跨团队企业级利用合作,现有的分治设计模式依然显得有心无力。

大规模 Web 利用的困局

只管 Web 利用的复杂度和参加人数以爆炸式的增长速度,但却没有一种新的架构模式来解决现有的窘境,并同时兼顾 DX(developer experience)和 UX(user experience)。

以字节跳动内「研发中台」举例,在研发日常工作中须要应用十分多的研发零碎,例如:代码治理、代码构建、域名治理、利用公布、CDN 资源管理、对象存储等。站在整个公司研发的角度思考,最好的产品状态就是将所有的研发零碎都搁置同一个产品内,用户是无奈感知他在应用不同的产品,对于用户而言就是单个产品不存割裂感,也不须要去学习多个平台,仅仅须要学习和理解字节跳动内的「研发中台」即可。

在字节跳动内这一类利用随处可见,因为字节跳动内存在大量业务线,每一条业务线都会诞生大量的中台零碎,并且还在指数增长,以字节跳动内电商业务举例,对于电商经营的日常工作来说,其实与研发日常工作一样,围绕在:商品、商家、品牌、风控、营销等工作上,那么对于电商经营来说怎么样才最高效的电商经营零碎呢,因为整个零碎波及范畴较广,在理论的研发过程中必然会以性能或业务需要垂直的切分成更小的子系统,切分成各种小零碎后只管因为分治的设计理念晋升了开发者体验,然而肯定水平上升高了用户体验。那是否以一种新的架构模式,既保开发者体验,又能晋升用户体验呢。

传统 Web 利用的利与弊

这里简略剖析一下传统 Web 利用在开发大规模利用和波及多研发团队合作时遇到的一些窘境,以下面案例中的「电商经营平台」举例,对于电商经营而言商品、商家、品牌等都是电商经营平台能力的一部分,而不是独立之间的孤岛。若以传统的前端研发模式进行开发,那么此时有两种我的项目设计策略:

  1. 将平台内多个零碎搁置同一个代码仓库保护,采纳 SPA(Single-page Application)单页利用模式
  2. 将零碎分为多个仓库保护,在首页聚合所有平台的入口,采纳 MPA(Multi-page Application)多页利用模式

若采纳多个零碎搁置同一个我的项目内保护:

  • 劣势:
    • 更好的性能
    • 具备部分更新,无缝的用户体验
    • 提前预加载用户下一页的内容
    • 对立的权限管控、对立的 Open API 开发能力
    • 更好的代码复用,根底库复用
    • 对立的经营治理能力
    • 不同零碎能够很好的通信
    • SPA 利用特有劣势:
  • 劣势:
    • 代码权限管控问题
    • 我的项目构建工夫长
    • 需要公布互相阻塞
    • 代码 commit 凌乱、分支凌乱
    • 技术体系要求对立
    • 无奈同时灰度多条产品性能
    • 代码回滚相互影响
    • 谬误监控无奈细粒度拆分

采纳计划一的劣势非常明显,在日常开发中研发:代码构建半小时以上、公布需要时被需要阻塞、无奈部分灰度部分降级、我的项目遇到问题时回滚影响其余业务、无奈疾速引进新的技术体系进步生产力,我的项目的迭代和保护对于研发同学而言无疑是噩梦。

只管升高了开发体验,如果对我的项目整体的代码拆分,懒加载管制切当,其实对于应用平台的用户而言体验却是晋升的,这所有都归咎于 SPA 利用带来的劣势,SPA 利用跳转页面时无需刷新整个页面,路由变动时仅更新部分,不必让用户产生在 MPA 利用切换时整个页面刷新带来的抖动感而升高体验,并且因为页面不刷新的个性能够极大水平的复用页面间的资源,升高切换页面时带来的性能损耗,用户也不会感知他在应用不同平台。

若采纳拆分成多个仓库保护

  • 劣势
    • 能够以我的项目维度拆分代码,解决权限管控问题
    • 仅构建本我的项目代码,构建速度快
    • 能够应用不同的技术体系
    • 不存在同一个仓库保护时的 commit 凌乱和分支凌乱等问题
    • 性能灰度互不影响
  • 劣势
    • 用户在应用时体验割裂,会在不同平台间跳转,无奈达到 SPA 利用带来的用户体验
    • 只能以页面维度拆分,无奈拆分至区块局部,只能以业务为维度划分
    • 多零碎同灰度策略艰难
    • 公共包根底库反复加载
    • 不同零碎间不能够间接通信
    • 公共局部只能每个零碎独立实现,同一运维告诉艰难
    • 产品权限无奈进行对立收敛

采纳计划二在肯定水平上晋升了开发体验,但却升高了用户体验,研发在日常开发工作中须要应用大量的平台,然而却须要跳转到不同的平台上进行日常的研发工作,整体应用体验较差。体验较差的起因在于将因为通过我的项目维度拆分了整体「研发中台」这样的一个产品,使各个产品之间是独立的孤岛,零碎间互相跳转都是传统意义上的 MPA,跳转须要从新加载整个页面的资源,除了性能是远不如 SPA 利用的并且利用间是没法间接通信,这就进一步加强了用户在应用产品时的割裂感。

背景和意义总结

通过以上两个场景案例,其实能够发现因为 Web 利用在逐渐取代传统的 PC 软件时,大规模 Web 利用在面对高复杂度和波及团队成员广下无奈同时保障 DX 和 UX 的窘境。传统的分而治之的策略曾经无奈应答古代 Web 利用的复杂性,因而衍生出了微前端这样一种新的架构模式,与后端微服务雷同,它同样是连续了分而治之的设计模式,不过却以全新的办法来实现。

微前端解决方案

上一节总结了微前端呈现的背景和意义,并且理解了两种传统 Web 利用的研发模式:SPA(Single-page Application)、MPA(Multi-page Application)在波及人员广和我的项目复杂度高的场景下带来的劣势,那么冀望能有一种新的架构能同时具备 SPA 和 MPA 两种架构劣势,并同时晋升  DX(developer experience)和 UX(user experience)呢?

那在现实的状况下,冀望能达到,将一个简单的单体利用以性能或业务需要垂直的切分成更小的子系统,并且可能达到以下能力:

  • 子系统间的开发、公布从空间上实现隔离
  • 子系统能够应用不同的技术体系
  • 子系统间能够实现根底库的代码复用
  • 子系统间能够疾速实现通信
  • 子系统间需要迭代互不阻塞
  • 子利用能够增量降级
  • 子系统能够走向同一个灰度版本控制
  • 提供集中子系统权限管控
  • 用户应用体验整个零碎是一个繁多的产品,而不是彼此的孤岛
  • 我的项目的监控能够细化到到子系统

那么基于下面现实状况,如何从零设计一套全新的架构用于解决古代 Web 利用在面对企业级零碎遇到的窘境呢。

微前端的整体架构

那么如何提供一套既具备 SPA 的用户体验,又具备 MPA 利用带来的灵活性,并且能够实现利用间同灰度,监控也能够细化到子系统的解决方案呢?目前在字节跳动内利用的微前端解决方案「Garfish」就是这样的一套计划,该解决方案次要分为三层:部署侧、框架运行时、调试工具,采纳的是 SPA 的架构。

解决方案整体架构

微前端部署平台

部署平台作为微前端研发流程中重要的一环,次要提供了:微前端的服务发现、服务注册、子利用版本控制、多个子利用间同灰度、增量降级子利用、下发子利用信息列表,剖析子利用依赖信息提取公共根底库升高不同利用的依赖反复加载。

用于解决微前端中子利用的独立部署、版本控制和子利用信息管理,通过 Serverless 平台提供的接口或在渲染服务中下发主利用的 HTML 内容中蕴含子利用列表信息,列表中包含了子利用的详细信息例如:利用 id、激活门路、依赖信息、入口资源等信息,并通过对于子利用的公共依赖进行剖析,下发子利用的公共依赖,在运行时获取到子利用的信息后注册给框架,而后在主利用上管制子利用进行渲染和销毁。

微前端运行时

Why not iframe

谈到微前端绕不开的话题就是为什么不实用 iframe 作为承载微前端子利用的容器,其实从浏览器原生的计划来说,iframe 不从体验角度上来看简直是最牢靠的微前端计划了,主利用通过 iframe 来加载子利用,iframe 自带的款式、环境隔离机制使得它具备人造的沙盒机制,但也是因为它的隔离性导致其并不适宜作为加载子利用的加载器,iframe 的个性不仅会导致用户体验的降落,也会在研发在日常工作中造成较多困扰,以下总结了 iframe 作为子利用的一些劣势:

  • 应用 iframe 会大幅减少内存和计算资源,因为 iframe 内所承载的页面须要一个全新并且残缺的文档环境
  • iframe 与下层利用并非同一个文档上下文导致
    • 主利用劫持快捷键操作
    • 事件无奈冒泡顶层,针对整个利用对立解决时效
    • 事件冒泡不穿透到主文档树上,焦点在子利用时,事件无奈传递上一个文档流
    • 跳转门路无奈与下层文档同步,刷新失落路由状态
    • iframe 内元素会被限度在文档树中,视窗宽高限度问题
    • iframe 登录态无奈共享,子利用须要从新登录
    • iframe 在禁用三方 cookie 时,iframe 平台服务不可用
    • iframe 利用加载失败,内容产生谬误主利用无奈感知
    • 难以计算出 iframe 作为页面一部分时的性能状况
  • 无奈预加载缓存 iframe 内容
  • 无奈共享根底库进一步缩小包体积
  • 事件通信繁琐且限度多

基于 SPA 的微前端架构

只管难以将 iframe 作为微前端利用的加载器,然而却能够参考其设计思维,一个传统的 iframe 加载文档的能力能够分为四层:文档的加载能力、HTML 的渲染、执行 JavaScript、隔离款式和 JavaScript 运行环境。那么微前端库的根底能力也能够参考其设计思维。

从设计层面采取的是基座 + 子利用分治的概念,部署平台负责进行服务发现和服务注册,将注册的利用列表信息下发至基座,通过基座来动态控制子系统的渲染和销毁,并提供集中式的模式来实现利用间的通信和利用的公共依赖治理,因而 Garfish 在 Runtime 层面次要提供了以下四个外围能力:

  • 加载器(Loader)
    • HTML 入口类型,拆解 HTML Dom、Script、Style
    • JS 入口类型,提供根底 Dom 容器
    • 负责注册平台侧提供的利用列表
    • 负责加载和解析子利用入口资源
    • 预加载能力
    • 解析子利用导出内容
  • 沙箱隔离(Sandbox)
    • 提供代码执行能力,收集执行代码时存在的副作用
    • 提供销毁收集副作用的能力
    • 反对沙箱多实例,收集不同实例的副作用
  • 路由托管(Router)
    • 解决不同利用间的路由不同步问题
    • 提供路由劫持能力,在主利用上管控子利用路由
    • 提供路由驱动能力来拼装残缺的平台的能力
  • 子利用通信(Store)
    • 建设通信桥梁
    • 提供共享机制

利用生命周期

整个微前端子利用的生命周期根本能够总结为:

  • 渲染阶段
    • 若入口类型为 HTML 类型,将开始解析和拆解子利用资源
    • 若入口类型为 JS,创立子利用的挂点 DOM
    • 主利用通过路由驱动或手动挂载的形式触发子利用渲染
    • 开始加载利用的资源内容,并初始化子利用的沙箱运行时环境
    • 判断入口类型
    • 将子利用存在”副作用“(对以后页面可能产生影响的内容)交由沙箱解决
    • 开始渲染子利用的 DOM 树
    • 触发子利用的渲染 Hook
  • 销毁阶段
    • 若路由变动来到子利用的激活范畴或被动触发销毁函数,触发利用的销毁
    • 革除利用在渲染时和运行时产生的副作用
    • 移除子利用的 DOM 元素

加载器的设计

加载器的整体设计理念其实与 React-loadable 十分相似,具备以下能力:

  • 异步加载组件资源
  • 能够预加载资源
  • 能够缓存组件资源
  • 缓存组件实例

与组件不同的是微前端作为一种可能将单体利用拆解成多个子利用的架构模式,不同于组件,这些被拆分进来的子利用最好的研发模式是在开发、测试、部署都与宿主环境拆散,子利用自身应具备自治能力,那么此时就与 iframe 提供的能力十分相似,iframe 通过加载 HTML 文档的模式加载整个子利用的资源,那么子利用自身就可作为一个独立站点,人造具备独立开发、测试的能力。因而 Garfish 的加载器提供了两种利用入口类型:HTML 类型和 JS 入口类型,但须要留神的是 Garfish 并非像 iframe 一样将其分为了另一个文档流,而是将其与主利用作为同一个文档流解决,用以躲避其不再同一个文档流带来的体验感割裂问题。

因为 HTML 入口类型人造具备独立、开发、测试的个性,在微前端整体架构设计中,对于跨团队合作而言,最好的研发模式是能升高其沟通老本,而升高沟通老本的最好形式是不沟通,所以个别我的项目类型都尽可能的举荐用户应用 HTML 的入口类型。

那么针对 HTML 入口类型的加载器须要做一些什么呢,上面是一张浏览器的渲染过程图:

针对浏览器的渲染过程也可将其分为:HTML 文本下载、HTML 拆解为语法树、拆解语法树中具备”副作用的内容“(对以后页面可能产生影响的内容)如 Script、Style、Link 并交由沙箱解决进行后渲染,与个别的子利用不同的是须要子利用提供 provider,provider 中蕴含了子利用渲染和销毁的生命周期,这两个 Hook 能够利用缓存模式中进一步加强利用的渲染速度和性能。

沙箱的设计

为什么须要沙箱

其实在过来的 Web 利用中是很少提及到沙箱这一概念的,因为组件的开发个别都会由研发通过研发标准来尽可能的去防止组件对以后应用环境造成副作用,诸如:组件渲染后增加了定时器、全局变量、滚动事件、全局款式并且在组件销毁后会及时的革除子利用对以后环境产生的副作用。

与组件齐全不同的是微前端是由多个独立运行的利用组成的架构格调,这些零碎可能别离来自不同的技术体系。我的项目的开发、测试从空间和工夫上都是拆散的,因为没有 iframe 一样原生能力的隔离很难利用间不发生冲突,这些抵触可能会导致利用产生异样、报错、甚至不可用等状态。

以 Webpack4 JsonpFunction 为例

在 Webpack5 中提供了一个重要的性能就是 Module Federation,随着 Webpack 5 推出 Module Federation,与 Webpack 4 发生变化的一个重要配置就是 JsonpFunction 属性变为了 chunkLoadingGlobal,并且由原来的默认值 webpackJsonp 变成了默认应用 output.library 名称或者上下文中的 package.json 的 包名称 (package name) 作为惟一值(webpack.js.org/issues/3940)。

为什么会产生这个转变呢,如果理解过 Webpack 构建产物的肯定会晓得 Webpack 通过全局变量存储了分 chunk 后的产物,用于解决分包 chunk 的加载问题。因为 Webpack 5 引入 Module Federation 页面中可能会同时存在两个以上的 Webpack 构建产物,如果还是通过是通过同一个变量存储要加载的 chunk,必然会造成产物之间的相互影响。

通过 Webpack 4 到 Webpack 5 反对 Module Federation 之后能够发现,在一个根底库尚未思考默认兼容多实例的场景下,贸然将其作为多实例应用很可能会造成利用无奈依照预期运行,更为严重的是你认为其失常运行了其实利用曾经产生了重大的内存透露或不可预知的状况,假使将 Webpack 构建产物的利用屡次动静的在页面中运行,将会发现曾经造成重大的内存透露,因为 Webpack 会增量的向全局存储 chunk 的变量上挂载模块以及依赖信息,简略来说就是每次执行 Webpack 构建的子利用代码都会向 webpackJsonp 数组 push 大量的数据,最终造成内存透露,直至页面解体。

沙箱的外围能力

为了保障利用可能稳固的运行且互不影响,须要提供平安的运行环境,可能无效地隔离、收集、革除利用在运行期间所产生的副作用,那利用运行期间次要会产生哪些副作用呢,能够将其分为以下几类:全局变量、全局事件、定时器、网络申请、localStorage、Style 款式、DOM 元素。

在 Garfish Runtime 中的沙箱次要能力也是围绕在这一块的能力建设上,针对子利用可能产生的副作用类型次要分为两类,一类是:动态副作用、另一类则是:动静副作用。这里动态副作用和动静副作用别离指的是什么呢,动态副作用指的是 HTML 中动态标签内容例如:Script 标签、Style 标签、Link 标签,这些内容属于在 HTML 文档流中就蕴含的,另外一部分副作用属于动静副作用,动静副作用指的是由 JavaScript 动态创建进去的,例如 JavaScript 能够动态创建 Style、动态创建 Script、动态创建 Link、动静执行代码、动静增加 DOM 元素、增加全局变量、增加定时器、网络申请、localStorage 等对以后页面产生副作用的内容。

针对子利用的动态副作用的收集比较简单,Loader 外围模块上曾经提供了子利用入口资源类型的剖析和拆解,能够从子利用 DOM 树中轻松拆解获取副作用内容,那么对于动态副作用曾经能够实现无效的收集、革除,然而尚未具备隔离的能力。动态创建的副作用都是通过 JavaScript 来动态创建的,须要收集到 JavaScript 运行时产生的副作用,并提供副作用的隔离和销毁能力。

沙箱设计的两种思路

在 Garfish 微前端中,如何无效收集、隔离、革除利用的副作用是保障利用可能安稳运行的外围能力之一。沙箱的次要能力也在于可能捕捉动态创建的副作用,对利用的副作用进行隔离和革除。

那么如何可能无效的捕捉到动态创建的副作用、收集、并隔离呢?目前 Garfish 提供了两种设计思路,一种是快照模式,另外一种是 VM 模式。

快照沙箱

顾名思义,在利用运行前通过快照的模式来保留以后执行环境,在利用销毁后复原回利用之前的执行环境,用于实现利用间副作用的隔离和革除。相似于“SL 大法”,通过 save 存储环境,通过 load 加载环境的模式。

代码实现思路

外围设计思维简述:

  1. 针对每一种副作用提供一个 Patch 类,这个类须要提供 save 和 load 两个办法
  2. Save 对应着该副作用的环境快照存储,Load 对应着销毁该副作用的销毁复原环境
  3. 并且针对每一种 Patch 还能够存储其在运行期间产生的变动,在优化场景时并不必所有代码,仅复原执行环境即可

VM 沙箱

通过快照沙箱的最简化的外围实现后能够发现,它的设计理念依赖于整个代码的执行属于线性的过程,即:存储执行环境 => 执行具备副作用的代码 => 复原执行环境,但在理论的场景中对于利用的划分并以页面为维度划分,同一个页面可能存在多个利用,所以它的执行程序并非线性,可能同时存在多个快照沙箱的实例环境,也就是快照沙箱多实例,以上面代码举例:

通过下面的代码能够发现,在同时运行多个快照沙箱实例时,在代码执行程序非线性的场景下,并不能无效的收集和解决利用的副作用,也基于此快照沙箱无奈应用在非线性呢多实例的场景中,因而也进一步推出了 VM(virtual machine)沙箱。

维基百科对于 VM  的解释:在计算机科学中的体系结构里,是指一种非凡的软件,能够在计算机平台和终端用户之间创立一种环境,而终端用户则是基于虚拟机这个软件所创立的环境来操作其它软件。虚拟机(VM)是计算机系统的仿真器,通过软件模仿具备残缺硬件零碎性能的、运行在一个齐全隔离环境中的残缺计算机系统,能提供物理计算机的性能。

在 Node 中也提供了 VM 模块,不过不过不同于传统的 VM,它并不具备虚拟机那么强的隔离性,并没有从模仿残缺的硬件零碎,仅仅将指定代码搁置了特定的上下文中编译并执行代码,所以它无奈用于不可信起源的代码。

参考 Node 中 VM 模块的设计,以及 JavaScript 词法作用域 的个性,能够设计出 VM 沙箱,不过与传统的 VM 差别也同样存在,它并能执行不可信的代码,因为它的隔离能力仅限于将其运行在一个指定的上下文环境中。

从而得出以下设计


隔离环境须要哪些上下文

针对副作用的类型:全局变量、全局事件、定时器、网络申请、localStorage、Style 款式、DOM 元素,别离提供了全新的执行上下文:

  • Window
    • 用于隔离全局环境
  • document
    • 收集 DOM 副作用
    • 收集 Style 副作用,进行解决
    • 收集 Script,持续搁置沙箱解决
    • 用于捕捉动态创建的 DOM 节点、Style、Script
  • timeout、interval
    • 解决定时器
  • localstorage
    • 隔离 localStorage
  • listener
    • 收集全局事件

新的执行上下文哪里来

新的执行上下文有两个起源,

  • 来源于以后环境
  • 来源于 iframe 的执行环境

因为 iframe 创立后须要须要较多的内存资源和计算资源,而微前端中的 VM 沙箱并不需要一个齐全的执行上下文,所以能够基于以后环境。

快照沙箱和 VM 沙箱能力比照

路由零碎的设计

在于古代 MVC 的设计思维,前端框架的设计思维也始终在产生变更,古代 Web 前端框架提供的最经典的能力莫过于将 MVC 中的 Constroller 变为了 Router,目前简直支流的前端框架都反对路由驱动视图,仅提供一个 Router Map 路由表,无需关注管制任何路由状态即可实现跳转后的路由更新。

通过微前端呈现的背景和意义,能够理解到微前端次要是用于解决:利用增量降级、多技术体系并存、构建大规模企业级 Web 利用而诞生的。那么在基于 SPA 的微前端架构中也能够理解到,目前微前端次要是采纳利用分而治之 + 动静加载 + SPA 利用的模式来解决大规模利用带来的一系列问题。在以组件为颗粒度的 SPA 利用中组件外部是不须要关怀路由的,然而在微前端中次要通过利用维度来拆分,那么拆分的利用也可能是一个独立的 SPA 利用,那么此时主利用与子利用的关系如何编排呢?

微前端利用中现实的路由调度

假如存在一个 Garfish 站点,这个站点它是由主利用 + 三个子利用形成,主利用的 basename 为 /demo,并存在三个 Tab 别离指向跳转至不同的利用,现实的路由成果:

  1. 在点击 vue-app Tab,跳转至 /demo/vue-app 路由后,别离激活 vue-app 下,为 Vue 类型的 A 利用和 B 利用,并激活 A 利用和 B 利用中的 Home 组件
  2. 点击 React-app Tab 进入到 /demo/react-app 路由后,别离激活 react-app 下,为 React 类型的 C 利用,并激活 C 利用的 Home 组件
  3. 在激活 C 利用的根底上,点击 Detail 按钮,跳转至 /demo/react-app/detail,并激活 C 利用的 detail 组件。
  4. 点击浏览器返回按钮展现,跳转 /demo/react-app/detail,并激活 C 利用的 Home 组件,至此实现浏览器的根本路由跳转能力。

不思考任何路由解决的场景

假如存在一个 Garfish 站点,这个站点它是由主利用 + 一个子利用形成。因为 Garfish 采纳的是  SPA 架构,子利用与主利用所处于同一个执行上下文,子利用的路由原样反馈在主利用上。

那么此时别离跳转到:/home/detail路由会发现哪些问题呢?

  • 假设跳转的办法能够同时触发奴才利用路由更新,主利用路由和子利用路由会同时产生抢占状况,后渲染的组件会笼罩先渲染的路由组件
  • 在触发路由跳转方后,只有主利用视图触发刷新、只有子利用视图刷新、或都不刷新
    • 「视图的路由状态保护在框架外部」,通过原生跳转无奈触发视图更新

此时当别离跳转到:/home/detail/test 路由时别离触发对应的组件视图,然而假使子利用路由中也存在 /detail视图呢,因为利用的开发采纳分治的模式,利用的开发从空间和工夫上都是拆散的,无奈保障利用间的路由不产生路由抢占的状况。

「通过 history 路由跳转无奈保障利用可能触发视图更新」,在通过 history api 进行路由跳转时,是无奈触发利用视图更新,假如存在一个 React 利用 A,存在一个组件视图 Test,别离通过 React 提供的路由办法跳转和原生的路由跳转进行察看:

Hash 和 History 路由模式

目前支流的 SPA 前端利用基本上都反对两种路由模式,一种是:hash 模式、另一种则是 History 路由模式,两者的优劣和应用并不在本文的探讨范畴之内,这里仅做在微前端这种分离式开发模式下的介绍,在微前端这种分离式 SPA 利用开发的模式下该抉择哪种路由模式,以及多 SPA 利用下他们的路由应该如何编排:

假如站点地址为:http://garfish.bytedance.net

失常路由状况

  • 主利用 history 模式、子利用 history 模式
    • 主利用(basename: /example):
      • 主利用所有路由基于:http://garfish.bytedance.net/example
      • 例如跳转到:/appA,http://garfish.bytedance.net/example/appA/
    • 子利用(basename: /example/appA):
      • 子利用所有路由基于:http://garfish.bytedance.net/example/appA
      • 跳转到子利用的 /detail 页,http://garfish.bytedance.net/example/appA/detail
    • 特点:
      • 当奴才利用别离为 history 模式时,子利用的路由基于主利用根底路由并带上本人的业务路由
      • 路由同步到主利用路由上,通过 子利用 scope 命名空间隔离(子利用 A,提供 appA 的 scope)主利用和其余利用的路由抵触,并将子利用
      • 门路合乎用户和开发者认知和了解
      • 反对嵌套层级应用,并持续通过 scope 的命名空间保障路由可读
    • \
  • 主利用 history 模式、子利用 hash 模式
    • 主利用(basename: /example):
      • 主利用所有路由基于:http://garfish.bytedance.net/example
      • 例如跳转到:/appA,http://garfish.bytedance.net/example/appA/
    • 子利用(basename: /example/appA):
      • 子利用所有路由基于:http://garfish.bytedance.net/example/appA
      • 从主利用:http://garfish.bytedance.net/example/appA,跳转到子利用的 /detail 页,http://garfish.bytedance.net/example/appA#/detail
    • 特点:
      • 在肯定水平上具备奴才利用都为 history 模式的劣势,不反对嵌套层级应用
      • 目前少数框架都不反对以 hash 值作为 basename
      • 可读性尚可

异样路由状况

  • 主利用 hash 模式、子利用 history 模式
    • 主利用(basename: /example):
      • 主利用所有路由基于:http://garfish.bytedance.net/example
      • 例如跳转到:/detail,http://garfish.bytedance.net/example#/appA
    • 子利用(basename: /example#/appA):
      • 子利用所有路由基于:http://garfish.bytedance.net/example#/appA
      • 跳转到子利用的 /detail 页,http://garfish.bytedance.net/example/detail#/appA
    • 特点:
      • 「路由凌乱」,不合乎用户和开发者直觉
      • 目前少数框架都不反对以 hash 值作为 basename
  • \
  • 主利用 hash 模式、子利用 hash 模式
    • 主利用(basename: /example):
      • 主利用所有路由基于:http://garfish.bytedance.net/example
      • 例如跳转到:/detail,http://garfish.bytedance.net/example#/appA
    • 子利用(basename: /example#/appA):
      • 子利用所有路由基于:http://garfish.bytedance.net/example#/appA
      • 跳转到子利用的 /detail 页,http://garfish.bytedance.net/example#/detail
    • 特点:
      • 「路由凌乱」,不合乎用户和开发者直觉
      • 目前少数框架都不反对以 hash 值作为 basename
      • 可能与主利用或其余子利用产生路由抵触

Garfish  Router 如何解决路由

通过下面现实的路由模式案例发现,微前端利用拆分成子利用后,子利用路由应具备自治能力,能够充沛的利用利用解耦后的开发劣势,但与之对应的是利用间的路由可能会发生冲突、两种路由模式下可能产生用户难以了解的路由状态、无奈激活不同前端框架的下带来的视图无奈更新等问题。

目前 Garfish 次要提供了以下四条策略

  • 提供 Router Map,缩小典型中台利用下的开发者了解老本
  • 为不同子利用提供不同的 basename 用于隔离利用间的路由抢占问题
  • 路由发生变化时能精确激活并触发利用视图更新
Router Map 升高开发者了解老本

在典型的中台利用中,通常能够将利用的构造分为两块,一块是菜单另一块则是内容区域,依靠于古代前端 Web 利用的设计理念的启发,通过提供路由表来自动化实现子利用的调度,将公共局部作为拆离后的子利用渲染区域。


主动计算出子利用所需的 basename

当利用处于激活状态时,依据利用的激活条件主动计算出利用所需的根底门路,并在渲染时通知框架,以便于利用间路由不发生冲突。

如何无效的触发不同利用间的视图更新

目前支流框架实现路由的形式并不是监听路由变动触发组件更新,让开发者通过框架包装后的 API 进行跳转,并外部保护路由状态,在应用框架提供 API 办法产生路由更新时,外部状态产生变更触发组件更新。

因为框架的路由状态别离保护在各自的外部,那么如何保障在路由发生变化时能及时无效的触发利用的视图更新呢,答案是能够的,目前次要有两种实现策略:

  1. 收集框架监听的 popstate 事件
  2. 被动触发 popstate 事件

因为目前反对 SPA 利用的前端框架都会监听浏览器后退事件,在浏览器后退时依据路由状态触发利用视图的更新,那么其实也能够利用这种能力被动触发利用视图的更新,能够通过收集框架的监听事件,也能够触发 popstate 来响应利用的 popstate 事件

基于「古代 Web 框架」的微前端最佳实际

微前端作为一种全新的 Web 利用类型,不同于以往传统的 Web 利用开发,微前端须要采纳奴才利用分治的开发模式后带来了一系列新的挑战,这些挑战包含但不限于:奴才利用开发调试、一般 Web 利用如何疾速变为微前端利用、如何反对微前端利用 SSR、奴才利用数据通信触发视图更新。Modern.js 作为 Garfish 下层的古代 Web 框架,可能很好的解决这些问题,并提供开箱即用的开发体验。

微前端利用的调试开发

因为微前端利用采纳分治的开发策略,利用间的保护和开发可能在工夫和空间上都是拆散的,那么在开发环境时启动整个微前端我的项目的所有奴才利用,是一个并不明智的策略,不仅须要 clone 其余仓库并实现利用的运行,还要保障其代码的时效性。Modern.js 提供了更优的的策略:

  • 某些子利用须要更新时
    • 主利用线上环境
    • 须要开发的子利用线下环境
    • 不须要开发的子利用上线
  • 主利用须要更新时
    • 主利用线下环境
    • 所有子利用线上环境

通过以上更优的调试策略,能够保障开发者仅运行本人的关注的利用即可。那么如何达到这种更优的,能够采纳利用列表的下发模式,框架运行时加载下发的利用列表,在开发主利用时拉取线上的利用列表,在开发某个子利用时代理代理列表中的资源为子利用的列表。

传统 Web 利用反对微前端模式

通过微前端运行时章节能够发现传统 Web 利用与微前端利用间进行切换老本并不高,但须要研发关注利用的路由的调度、利用的生命周期导出、额定的构建配置、利用通信数据触发视图更新,微前端模式利用和传统 Web 利用间如何进行切换都存在肯定的学习和了解老本。

在 Modern.js 中作为下层框架集成了 Garfish,原生反对微前端利用,能够通过简略配置即可实现微前端利用类型的转换,帮忙用户疾速搭建利用根底构造,以升高其学习老本,疾速生成微前端利用。

微前端利用如何反对 SSR

微前端作为一种全新的架构模式,其分治设计模式除了带来的诸多长处外,但与之对应的是引入了新的问题,如何反对传统 Web 利用提供的 SSR 能力,因为微前端采纳了分治的开发模式,利用拆分成了多个子利用,那么须要实现整体利用的 SSR 能力,则须要与具体的 Web 框架相结合,通过制订微前端利用的加载规定,达到微前端利用也能无效的实现 SSR 能力。

Modern.js 作为 Garfish 的下层框架,提供更开箱即用的下层能力,并解决了以上微前端不同于传统 Web 利用开发后带来的弊病,文末有对于 Modern.js 的公布预报,能够理解并关注。

微前端的长处

  • 实用于大规模 Web 利用的开发
  • 更快的开发速度
  • 反对迭代可开发和加强降级
  • 拆解后的局部升高了开发者的了解老本
  • 同时具备 UX 和 DX 的开发模式

微前端的毛病

  • 复杂度从代码转向基础设施
  • 整个利用的稳定性和安全性变得更加不可控
  • 具备肯定的学习和理解老本
  • 须要建设全面的微前端周边设施,能力充分发挥其架构的劣势
    • 调试工具
    • 监控零碎
    • 下层 Web 框架
    • 部署平台

何时应用微前端

  • 大规模企业级 Web 利用开发
  • 跨团队及企业级利用合作开发
  • 长期收益高于短期收益
  • 不同技术选型的我的项目
  • 内聚的单个产品中局部须要独立公布、灰度等能力
  • 微前端的指标并非用于取代 iframe
    • 利用的起源必须可信
    • 用户体验要求更高

总结

微前端概念的呈现是前端倒退的必然阶段,PC 互联网转向挪动互联网时代时,PC 的场景并未齐全被毁灭,反而转向了衍生出了更多沉迷感更高、体验感更强的利用,与之对应的应该是呈现新的架构模式来应答这些利用规模的增长。

微前端也并非银弹,采纳微前端后复杂度并未凭空隐没,而是由代码转向了基础设施,对架构设计带来了更大的挑战,并且在新的架构下须要设计并提供更多的周边工具和生态来助力这一新的研发模式。

本文更多的是从背景和设计层面讲清楚微前端解决方案应具备哪些能力,以及外围模块的设计。每一部分并未蕴含过于具体的细节,如果想要理解「微前端运行时」具体设计,能够通过 https://github.com/modern-js-… 仓库理解细节。

参考

  • 如何设计微前端中的奴才路由调度:https://mp.weixin.qq.com/s/TA…
  • 如何取巧实现一个沙箱:https://mp.weixin.qq.com/s/Mg…
  • 微服务架构及其最重要的 10 个设计模式:https://www.infoq.cn/article/…
  • single-spa:https://github.com/single-spa…

Modern.js 开源预报

Modern.js 和 Garfish 都是字节跳动 Web Infra 发动的「古代 Web 工程体系」开源我的项目,Modern.js 原生反对微前端,在 Garfish 根底上提供了残缺的微前端最佳实际。

Modern.js 打算在 10 月 14 号公布 1.0.0 版和上线文档站,欢送关注和参加。

点击查看 Garfish 仓库。

正文完
 0