斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享

Posted 光谷猫友会

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享相关的知识,希望对你有一定的参考价值。

猫友会希望建立更多高质量垂直细分社群,本次是"前端学习交流付费群"的第二期分享。


“前端学习交流付费群”由猫友会联合,石墨前端leader陈萍圆,青云前端开发总监梁波,斗鱼前端总监杜伟发起,希望带动武汉的技术分享氛围, 欢迎大家加入!(文末有入群方式



大家好,我是王俊龙,目前在斗鱼前端FED部门主要从事基础框架搭建和开发体系设计。


首先自我介绍一下,2013年研究生毕业后从事传统企业级软件开发(微软.NET平台)近4年,期间开发过不少C/S,B/S架构的企业软件,企业级的SaaS平台和ESB集成总线,前后端/客户端技术都有所涉猎。2017年4月份加入斗鱼,开始参与前端领域的基础框架、基础服务、性能优化相关的工作。


今天跟大家分享的主题是《斗鱼前端开发体系 - Shark 2.0》,主要和大家分享的是斗鱼主站最新的开发体系Shark2.0,近段时间部门一直在使用Shark2.0对主站进行重构,通过介绍新的开发体系,分享一下这套开发体系涵盖的技术点、解决了哪些之前的开发体系中的痛点。

此次分享主要分为几个部分,分别是:

  1. 直播站的现状

  2. 斗鱼的前端架构变迁

  3. Shark2.0体系

 

首先介绍一下目前直播站的现状:

斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享

大家可以先看一下目前主站的数据交互简图,现在首要的业务场景是数据来源多,可以看到目前主站的核心直播间页面,以Flash和相关的BOM/DOM为主,Flash需要与CPP进行长连接,进行即时的弹幕消息通信。Flash和DOM/javascript之前通过Flash JSBridge事件进行通信;Flash与后端php/Java,页面JS与PHP/Java都有接口上的通信。


数据交互场景复杂,数据格式也没有得到统一,页面JS需要多种方法与其他数据源进行交互,并且有各自的数据转换;由于业务庞大、复杂,DOM会面临性能的压力,大量的弹幕、礼物造成了DOM渲染压力。以上这些是主要的直播站的现状。


       接下来简单介绍一下斗鱼的前端架构变迁。都说PHP是最好的语言,斗鱼在创业初期,还没有组建强大的前端团队的情况下,需要快速的验证想法、验证市场,采用PHP进行开发,前后端混在一起,使用PHP模板进行页面渲染,并使用JQuery做页面交互。


这样的开发方式,优点是开发效率高、成本低,但是也带来很多弊端,由于前后端没有分离,导致代码混杂在一起,难以维护;并且专业的前端很难与后端进行协作开发,JQuery简单易用但是并没有引入模块的概念,并且视图和逻辑混合在一起,很难写出复用性很高的代码。


随着斗鱼的不断发展,以PHP为主的方案已经很难适应日益扩大的业务需求,于是我们引入了Shark1.0体系。Shark1.0主要实现了前后端分离、前端引入了自研的基于AMD标准的模块化加载器,使代码能够在一定程度上进行复用;JS框架仍然选用JQuery,使用Grunt处理前端工程化,在需要SEO的首屏页面还是使用PHP进行页面直出,其他后续的页面请求与PHP进行AJAX交互。Shark1.0虽然实现了前后端的分离、模块化,但部分问题仍然存在,模块的粒度不够,视图和业务逻辑仍然容易混杂在一起,没有解决渲染性能问题。


最终,在业务不断发展到今天,很多代码已经很难进行扩展和维护,老的开发体系已经不足以支撑现有的主站体量的时候,我们引入了最新的Shark2.0开发体系。


Shark2.0引入了一些JavaScript领域的新特性,包括组件化(React)、基于组件化的状态管理(Redux)、响应式编程范式(RxJS),副作用的隔离(Redux-Observable),以及后端领域常见的依赖注入、装饰器、Schema(类似于POJO或者DTO,可以对网络请求的数据格式进行运行时的类型检查、校验和字段映射),引入了Webpack和Babel进行工程化相关的工作、开发Babel插件来做一些静态代码检查的工作,提高代码质量。最后Lua作为首屏的渲染方案,相比PHP性能更好。

       下面主要围绕大纲的几个方面来进一步的详细介绍Shark2.0:

      整体设计思路

      组件化

      状态管理

      响应式编程

      副作用隔离

      依赖注入

      装饰器

      工程化

 

首先介绍Shark2.0的整体设计思路:

斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享

我们首先看一下这个架构图,所有的源码首先会进行工程化的预处理,打包成一个个的Module(每个Module是可以独立执行的业务模块),每个Module会通过工程化打包成单独的JS文件,通过加载器将这个Module下载下来,然后执行并挂载到对应的静态模板的挂载点上。

我们继续看一下Module的内部结构,Module会由很多Components组成,并暴露出一个根Component作为Module的入口,Component封装所有视图组件,并不包含业务逻辑,所有的业务逻辑都会封装到Components下面的Service层,Service通过依赖注入的方式注入到对应的Components中,达到组件和逻辑独立复用的效果。Service通过发送action与redux的store进行通信,修改状态,store再将数据渲染到Components中,如果是副作用,例如通信,Service会发送action到通信层,通信层再将返回的数据更新到store。

其中还有个Data Center(数据中心),数据中心负责处理多个异构的外部数据源,例如需要共享的通用数据,通过后端渲染到模板的全局数据,还有与Flash播放器、弹幕服务器通信的数据。这些数据会以RxJS的方式提供,Service层订阅了这些数据后,接收这些数据的推送。后面会细讲RxJS。

以上就是整个Module的构造,我们会有很多个Module负责来处理页面上的不同的关键子系统:礼物系统、弹幕系统等等,最后组成更大的业务模块。

斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享


接下来我们简单的过一下组件化/状态管理,Shark2.0使用成熟而且在社区非常活跃的React作为组件化方案,React的组件化粒度可以做的很细,并且可以使用HOC、Render Props等技术进一步的使组件得到复用,Virtual DOM可以让渲染性能有一个很大的提升,并且有非常成熟的Redux作为状态管理方案,Redux和React结合的单向数据流的方案,进一步的降低了开发的复杂度,使得视图层View和视图状态处理Reducer都可以将粒度做的更细,每一个单元只做一件事情,方便复用。

斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享

再讲到另外一个技术:RxJS。RxJS来源于一个基于.NET的一个库,叫Reactive Extensions,即响应式扩展,用来实现响应式的编程范式,而RxJS即为JavaScript的实现。

响应式是一个什么样的概念呢,数据是根据变化而主动推送、传播的,而不是被动拉取,即面向异步数据流和变化传播的编程范式,举个例子,a=1,b=a+1,c=b+1,那么如果将a的值赋值再次为2,不需要再次显式的执行表达式,c会接受到a的变化推送而自动的变为4。

RxJS使用类似发布订阅的模式,就像一个管道,一边不断的发送数据,在管道的另外一边订阅它,消费这些数据,而流经管道的数据可以通过很多Operators(操作符)对数据进行处理,例如filter(x=>x>10)将会过滤大于10的数据,最后流出加工后的数据,RxJS内置了非常多的操作符,能够处理几乎所有的数据加工场景。


在Shark2.0中,我们主要使用RxJS处理几个方面的问题,一个是统一数据接口,之前我们介绍过的主站现状是数据来源很多,我们通过RxJS将网络请求、播放器与页面的交互数据、数据中心的一些数据、弹幕消息的推送数据、还有一些全局的事件(window.resize等),封装成统一的接口(RxJS中的Observable),然后通过封装很多处理这些数据的Operators管道,提取出通用的数据处理单元(FP函数式编程的优势)并在不同数据源中得到复用,对于事件类型的Observable还可以进行节流的操作,最终将数据推送到视图层使用。

斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享

接下来介绍一下副作用隔离,我们把所有的无法预期执行结果的操作,统一称为副作用(Side Effect),例如网络请求,I/O操作,以及其他异步相关的操作。我们使用Redux-Observable作为副作用隔离的方案,类似Redux-Saga,而Redux-Observable是针对响应式编程的。我们可以看上图,传统的同步的、无副作用的操作,视图层发送Action给Reducers订阅消费,然后更新到Store,最后再反应到View层,Redux本身是不提供异步操作的,所以有了Redux-Thunk,Redux-Saga等相关的库来处理异步操作。


如上图,对于副作用,视图层会发对应的副作用的Action,进入到Epics,即专门处理副作用的隔离区域,Epics中有很多单独的逻辑处理单元,这些单元可以根据需要只处理一件或少数几件异步任务,粒度可以非常细化,Epics中的处理单元能够订阅Action,处理完后再发送新的Action,而在订阅和发送Action时使用的是RxJS,同样可以用RxJS提供的Operator对数据进行加工。Epics中的处理单元再处理完一个任务后除了可以发送Action直接给Reducer,也可以被另外一个单元订阅,组成一个异步调用链来处理复杂的,流程化的异步场景。

斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享

依赖注入是后端常见的一种模式,例如Spring MVC、以及Angular中也使用的非常广泛。


我们在Shark2.0中使用装饰器实现依赖注入。依赖注入可以使UI视图(Components)与业务逻辑(Service)进行分离,使用Service工厂,实现控制反转,即Components使用哪种业务逻辑,并不是Components自己决定的,而是外部的工厂服务根据情况动态创建的服务实例;另外依赖注入对单元测试也非常友好,可以注入Mock Service来对UI进行单测,也可以只对Service进行逻辑测试。这里装饰器所标记的Service相当于是接口(Interface)的作用(ES6还没有接口特性)。


下面讲讲Shark2.0广泛使用的装饰器,装饰器模式相当于Java中的Annotation,.NET中的Attribute。在Typescript中已经实现了,在ES6中属于Stage-2的特性,我们可以使用transform-decorators-legacy和transform-class-properties这两个Babel Plugins来支持装饰器。


我们主要用在了schema、性能监控、异常捕获还有打点中。装饰器首先实现了AOP模式,AOP即面向切面编程,在不修改业务代码的情况下,能够通过装饰器统一在Component、Service、Epics还有Schema层增加性能日志、异常捕获等特性。使用装饰器能够很方便的代理Component的生命周期方法、Epics的网络请求方法等,在前后插入性能计时、异常捕获相关的代码。


后面可以主动上报这些性能和异常数据做一些监控相关的平台,甚至可以结合redux的中间件上报页面状态。我们还通过结合装饰器和redux中间件实现了一个比较优雅的打点方案。如图

斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享

针对redux项目,action是天然的点位,我们可以在action上通过使用装饰器打上标记,扩展action数据,在redux中间件中监听这些需要打点的action并上报数据。

斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享

我们通过装饰器来扩展属性字段的元数据,实现了Schema,用来做运行时的类型检查,通常的Flow.js或者Typescript只提供了静态的类型检查,方便开发过程中使用,而使用Schema可以在网络请求格式有问题的时候提早的发现数据问题,避免污染到Store或者视图层,提高开发和调试的效率。我们基于装饰器扩展了很多基本类型,例如@string@number @boolean,@optional(可选类型),还支持嵌套类型例如@typed @array,复合类型@oneOf等等。在Schema的类上面通过@mapping标记进行字段的映射,使命名并不规范的响应数据规范化。

斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享

下面来聊聊Shark2.0的工程化。Shark2.0使用Webpack对源代码资源进行压缩,打包,使用Babel-Loader来支持ES6的一些新特性,我们还开发了一些Babel插件针对代码进行预处理和静态检查。Babel-Loader将es6/jsx的代码转换成AST(抽象语法树),我们使用了一些第三方的Polyfill和插件转换了一些es6 的新特性,并且开发了一些插件来做组件、Service、Epics等规范检查,还做了一些代码或者元数据生成,例如遍历所有打点的装饰器可以做点位的一个全局的是梳理和可视化。这些步骤结合内部的Shark CI持续集成平台可以很方便的输出每次构建的详细报表。


斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享

为了提高页面的加载性能,我们设计了一套CRP(Critical Rendering Path即关键渲染路径),表示浏览器在不同的加载阶段,(首屏,DOM Content LoadedLoad)来下载各自需要的资源,保证最早的下载到各自阶段所需要的资源,并不会产生渲染阻塞,例如,图上的首屏阶段加载静态模板的html并渲染CSS(防止渲染阻塞),在DOMContentLoad阶段会下载可以延迟加载的CSSJS模块,在Load阶段去加载按需加载的模块(例如根据不同的用户操作来懒加载一些弹窗模块等)。

斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享

我们设计了一套加载梯队的方案,可以在每个Module上打上加载梯队的标记,在预编译阶段收集每个模块的梯队信息并生成配置输出到页面模板,在运行时加载器根据模板上的梯队信息在浏览器的各个阶段去请求各个对应梯队的资源并运行,达到可以通过修改配置就可以调整和优化资源加载时机和顺序的目的。

最后通过一张流程图来介绍一下Shark2.0中的持续集成流程:

斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享


首先需要创建项目和需求,根据创建项目流程图,在gitlab中创建对应的仓库,通过Shark-CLI工具,对开发环境进行检查,通过CLI中的相关命令创建脚手架、页面、模块等。

本地功能开发完毕之后,将代码提交到gitlab版本库,触发Shark CI(内部持续集成平台)自动构建工作流,对代码进行静态检查(lint)、单元测试、sonar代码分析,并生成各自对应的报表,对应发版负责人审核构建报表并合入发布分支(master),触发集成测试环境的CI工作流,同样需要审核,之后部署到公司内部集成测试环境,集成环境测试通过,此功能进入随时可上线状态。

发版平台拉取发布分支,选取要发布的功能提交记录,将代码发布线上。

 

以上便是我的个人分享,欢迎各位同学踊跃交流发言



添加小猫助手,加入猫友会交流群


以上是关于斗鱼前端开发体系 - Shark 2.0|前端交流群 第二期分享的主要内容,如果未能解决你的问题,请参考以下文章

2019给前端开发的 6 点建议

前端开发知识体系部

前端开发博客,分享互联网最精彩的前端技术,欢迎关注我微信公众号:前端开发博客,回复 1024,领取前端进阶资料包,回复 加群,与大神一起交流学习。

前端技术图谱体系和前端开发行业现状分析

前端开发交流群---期待你的加入

前端开发交流群---期待你的加入