查看原文
其他

干货 | 如何一步步打造基于React的移动端SPA框架

喻珍祥 携程技术中心 2019-05-02
作者简介
 

喻珍祥,携程港澳研发高级经理,2004年接触互联网开发,见证前端开发从美工到全栈开发的全过程。2014年加入携程,主要负责永安旅游APP移动前端架构和研发。

*本文首发于GitChat,转载请注明*


现今前端新技术井喷一样层出不穷,且各有特点和使用场景,交互变得前所未有的复杂,那么,在众多框架中,如何选择又如何落地呢?
前端框架作为工具,是各种模式,结构的集合,一个原则就是:“如非必要,不换”。但是,打算换一定要有换的道理,首要的原则就是当前的框架已不适应业务的发展,而框架就是要解决业务扩展性的问题。技术选型应从实际出发,透过各种框架的本质,了解它们使用的场景,选择接近自身业务和利于团队成员现状的框架,接着使之工程化,自动化,让它与实际严丝合缝,成为协助业务发展的利器,这不能不说是件非常有挑战力的事。


本文以实际项目为例,给大家分享一个前端业务框架设计和实践过程,其中有对框架设计的考虑,对某些技术点应用场景的处理,以及踩过的“坑”。


1、构建前端业务框架前的思考


程序员在设计业务框架时很容易陷入技术思维的陷阱:用最新最牛的技术,要做大做全。如果只是执着于这两点,就会忽略成本,适用范围,使用者的满意度等这些重要因素。


理解开发框架的价值


一个业务框架的价值就是让基于它开发出的产品质量高,开发过程高效,开发成本低,还能给开发人员带来幸福感。


明确开发框架的目标


开发目的我们分为业务目标,技术目标和人事目标三部分。


定义框架适用的场景或范围


当我们理解了框架的价值,明确了开发的目的,我们就需要定义适应场景和范围,我们需要对产品目标用户的环境进行确定,这对开发,测试以及产品三个层面都是有意义的。产品预测和定位用户群体,开发用来预算开发周期和成本,测试用来确定用例的边界和测试的范围。


2、技术选型与实用性分析


框架开发技术选项首先要考虑的是提供什么功能才能让业务系统开发人员更加方便开发。SPA作为用户体验好的一种产品类型,用户体验是框架开发需要考虑的一个重要因素。我们团队开发的产品属于典型的电商产品,业务框架需求也跟大多数电商公司相同。下面就是我们选定的基础层次结构和实用选型思路。


开发规范


无论是从代码清洁度、可维护性、健壮性、还是团队配合效率,我们在开发框架时的第一步都是制定和确定团队的开发规范。我们前后端统一用CommonJS模块化、基于React组件化、用部分ES6特性、CSS用LESS编写,最后我们定义了这些:


  1. 前端编码规范(基本的JS,HTML,CSS最好写法)

  2. 项目/目录结构规范

  3. 组件化编写和拆解规范

  4. React编码规范

  5. MVC结构的定义,Controller,Actions,Reducers,React组件之间职能的划分

  6. ES6使用指南及限制。例如:class、import,对象表达式等是禁止使用的。依据是使用这些特性后,Bable生成后的代码特别多,导致文件增大,影响加载性能。

  7. LESS编写规范

  8. 第三方库引用规范


Infrastructure


包含所有的基础建设模块和应用启动生命周期,这里介绍几个常用的模块。


Infrastructure - 存储器


基于浏览器sessionStorage、localStorage、memory和cookie,框架提供SessionStorage、LocalStorage、MemoryStorage和CookieStorage四种存储器。目的是更好地控制缓存,下面是存储器的主要实现:


  1. 统一四种存储的方法调用,规范了增删改查接口,方便插拔式调用。

  2. 支持JSON数据直接读存

  3. 支持过期时间设置,和过期数据自动清理

  4. 支持浏览器存储超出限额后,自动清除过期时间最早的数据

  5. 支持版本迭代后,数据自动清除


Infrastructure -用户标识


ClientID机制,用户唯一标示,用户初次启动应用时为每个客户端LocalStorage中存储ClientID,用于分析用户行为,对于错误处理和行为分析非常有帮助。


Infrastructure - 错误处理


框架集成错误处理,通过onerror事件,将客户端错误信息直接回发到用户行为系统。错误信息包含用户唯一标示ClientID、错误发生的文件以及行数,这样能使我们能及时掌握生产上的错误,并能快速定位问题。


Infrastructure – 继承


实现组合继承和对象扩展机制,支持构造函数和多对象扩展。不用ES6继承的原因是避免Webpack解析出的代码太多和冗余,导致文件增大。

 

Router


路由是SPA必不可少的一个模块,我们没有选择React-Router,而是自己去开发。其原因有三:


  1. H5、Hybrid以及服务端端实现路由规则同构。我们Hybrid是属于静态文件直出,只支持hash,而H5需要使用HistoryAPI来和服务端路由同构。

  2. 我们业务系统相对比较复杂,部分系统超过30页面,需更灵活的路由规则来配合APP运行生命周期,比如异步加载、页面缓存都是根据路由来做的。

  3. 我们原有框架已经实现基于hash的路由,团队成员也非常熟悉,所以实现和应用成本都非常低。没必要去趟React-Router这趟浑水了。


我们路由模块实现思路:H5端基于浏览器popstate事件,Hybrid端基于浏览器hashchang事件。同一套路由在启动时根据判断环境自动切换,与服务端实现对相同的路由解析规则保证这部分代码同构。


MVC


MVC最开始考虑用Backbone,但发现结合React后存在的意义不大,还需要在它的基础上扩展上我们的应用生命周期,成本跟自己研发一样,果断放弃。我们结合Redux形成了现在的MVC模型。


MVC – Model


Model职责相当于操作业务数据的ORM,属于三层中代码最重的一块,基本业务逻辑都在这层实现。在我们业务框架实现Model基类的时候,我们考虑业务系统开发时仅需要根据业务场景做这些实现和配置。


  1. 配置Ajax调用参数,例如路径、Method、是否缓存等。

  2. 可以实现Ajax调用参数的格式化方法以及结果格式化方法。

  3. 可以配置存储器缓存参数和结果。

  4. 如有需要子类可以重写基类的execute方法,改变Ajax调用方式。基类execute默认执行Ajax请求并返回Promise。

  5. 实现Model时也可以不配置Ajax,仅当Model为一个本地数据存储实体。


MVC – View


View的职责还是负责页面展示,这层我们选用了React,原因如下。

  1. 页面在复杂交互中渲染更快,同时用它来实现组件化。

  2. 相比Vue,我们团队成员更熟悉。

  3. 相比Vue,我们将来迈进React Native将更近。

  4. JSX比在模板中嵌入表达式更适合JavaScript。


我们没有将整个应用作为一个大组件,而是为每个页面创建了一个容器,在每个容器中插入页面组件,页面组件中调用其他UI组件。这样做的目的为了让数据分到页面,数据量分散,解析和操作时性能更好。


MVC – Controller


Controller的职责是负责将View跟Model串联起来,同时提供Redux的功能。如上图所示,Controller中的States Manager就是Redux中的Reducers和Store。


引入Redux,目的是为了解决React自身状态管理太乱。但我们还是进行了两点改造:一是用基础类库中的函数替换它使用的原生方法,减少代码量;二是扩展存储方式,使他支持我们的存储器。


StatesManager中的Store主要存储页面上状态数据,就是我们挂载的存储器。分为页面存储器和应用存储器两种,其中页面存储器存储当前页面的状态,而应用存储器全局状态和全局数据。

 

Vendor


第三方库,包含React-Lite,React,ReactDOM,FastClick,Underscore,Zepto等,方便开发时使用和后期定期更新。


Services


包含了广告操作,定位操作,地图操作,旅客操作,登入登出,优惠操作,Native操作等公共的服务。存在的目的是调用方不用关注数据源头和去向,只需关注功能本身即可。


Plugins


包含了Underscore的扩展插件,Webpack错误处理插件,统计收集插件,平台/浏览器兼容插件等。存在的目的是为了封装一些需要在应用/页面生命周期中执行,但不能破坏生命周期的一些公共模块。


UI Components


这层主要包含公共组件,起码需要提供常用纯组件和常用的业务组件。我们这里提供了各种表单组件,列表展示组件,预加载组件,日历组件,广告组件等。


Hybrid Shell/Bridge


这层属于独有的,根据Native提供的方法写的。Native通过JS Core提供了一系列的全局JS方法,而Hybrid Bridge就是将这些方法分类型封装起来,作为与Native通讯的桥梁。


HybridShell实现一套事件订阅机制来实现Hybrid代码和Hybrid Bridge的通讯保护机制,保证无论Bridge中是否存在相应的方法,或者调用参数是否错误都不影响APP的运行。


Server


服务器选用Nginx+Node+PM2,这样的搭配无懈可击,稳定,高性能,高可用。Nginx和Node都是可以做单台/多台集群的,充分利用资源,对于H5站点轻松应付。PM2主要为了守护Node进程,但为了保证它的稳定性,我们加了双保险,除了监控,还加了系统级别的守护进程。


3、构建脚本执行生命周期和开发流程


脚本执行生命周期,即是将脚本执行过程拆解成一系列的顺序阶段。目的是为了对整个应用做更好的控制,让复杂繁多的代码更清晰。同时也便于开发人员理解整个脚本执行过程,对后期性能优化也非常有帮助。我们的框架分为框架应用脚本生命周期和页面脚本生命周期。


框架应用生命周期


架开发人员在开发过程中定义好每个阶段职责。当我们定义的阶段职责明晰后,后期性能优化就有了一个非常清晰的路线图。从性能角度上看,在“进入页面生命周期”这个阶段前,都会是白屏时间,我们在每个阶段都加入了性能埋点数据,可以清楚的知道每个阶段的耗时,后期可以根据这个耗时来进行优化了。


页面生命周期


框架开发人员负责定义好这个流程,业务开发人员负责用业务代码来填充这个流程。和应用生命周期一样,对性能优化也有重大意义,同时给业务开发人员编写也提供了一个根据页面生命周期编写的开发流程。


如下面代码,一个页面控制器的写法


4、前端插件化/组件化/服务化/模块化的应用


组件化、插件化、服务化和模块化并非为前端而提出,在后端存在已久,都非常成熟。而他们的目的就是“高内聚”和“低耦合”


组件化


引入组件化能使我们在开发和维护中节省了大量的工作。因为新业务框架上线后,我们需要超过8个系统几百个页面要改版,无论从KPI还是个人幸福感都需要在开发业务框架时引入组件化。


我们引入组件化,可以获得以下好处:


  1. 开发方便,无需关注组件的依赖关系

  2. 测试方便,写完组件即可测试。

  3. 职责清晰,独立开发,功能高内聚

  4. 接口规范,维护性更高

  5. 框架层直接提供大量公共组件,缩短业务系统开发时间

  6. 灵活组合,方便调用,效率更高


选择组件化标准


从长远来看Web Components是一个很好的选择,是未来的一个组件化的标准,受到Google的大力推进,Chrome已经全面支持,其他浏览器也是紧随其后开始支持和兼容,到写这篇文章时已经基本支持了。但我们当时为了更好的兼容性和服务端渲染选择了React Components。


ReactComponents相比Web Components没那么规范,样式隔离性也不好,但它的组件状态管理机制和渲染算法还是非常具有竞争力的。我们在React Components的基础上将所有UI都是进行组件化,现阶段组件化的做法:


  1. 将职责单一,能独立开发,测试和维护的UI块划分为组件。

  2. 分为容器组件和功能组件,容器组件用来组合功能组件。

  3. 组件封装出CSS外的所有功能。常用公共组件CSS跟框架样式文件一起打包,而非常用公共组件CSS则需要单独在项目中引入。这是我们做的不是非常好的地方,这样做的目的是为了减少CSS引入大小和利用CSS文件缓存。

  4. 尽量将组件定义为无状态组件,增加复用度。


插件化


可以这么概括插件化,在应用开发完成后,希望不修改原有应用情况下,将新功能插入到应用系统中,这就是所谓的插件化。插件化的最大优点就是不破坏原有程序和生命周期,现在流行的框架中用到极致的就是Webpack的插件系统。

 

我们按下面三种规则来定义插件:


  1. 需要插入到应用或页面生命周期的某一个环节的功能

  2. 该功能可以独立封装,不依赖外部功能

  3. 多系统或页面共用。


例如:扩展Underscore的一些方法插件,收集统计插件等


服务化


服务化在前端很少被提到,多用于服务端API。可以这么概括服务化,将一些特定功能由提供方以服务的形式提供出来,应用方不用关注其实现方式,只需关注调用功能即可。


服务化在后端很好理解,前端如何理解?每个特定功能都能看成一项服务,可以是组件,插件,以及单独的功能模块;把这些功能都封装部署在一个特定的站点,业务系统需要用的时候直接异步调用这些服务的地址即可,不用关注其依赖和实现过程。


在我们的SPA框架中,把一些功能组件和模块当成服务,业务系统不需要预先引进,只需要在用的时候调用相应地址就可以了。目前做的不是非常好,主要是这些功能模块和组件,还依赖业务系统引用我们的SPA框架,如果要做到极致,就不再需要关注这些了,我觉得这将是前端未来几年一个趋势。


例如:不常用的公共组件,不常用的公共功能模块


模块化


模块化自从CommonJS出来后就成为前端的架构热点。模块化就是功能拆解,将小功能内聚,拆解系统耦合。也就是说拥抱模块化就能避免在代码中嵌入依赖关系。这里不做过多讨论,网上资料很多,只讨论下面几个问题。


是否需要模块化?模块化毋庸置疑,不做模块化前端就无法完成复杂的系统开发。只要你编程技能在提升,你就会不知不觉对代码功能进行模块化,跟你使用什么类库没关系。不是你不使用CommonJS,AMD,CMD,ES6就不能模块化,一个对象都可以算一个模块。只是CommonJS这些类库规范了模块的定义,使用和依赖关系的调用而已。


模块化还是组件化?模块化和组件化并非矛盾关系,而是一种包含关系。像上面写到的,组件、插件和服务都属于模块的一个子集。对于我们做SPA时定义的就是组件跟UI有关,非UI相关的模块细化为插件和服务,以及不能区分开的功能模块。


ES6还是CommonJS?很多同学讨论ES6可以实现模块化,用Node写后端还用CommonJS吗?其实这是不是重点,重点是你的项目成本和成员喜好,并不妨碍你写一份优雅的代码和实现一个伟大的产品。我们就是前后端都使用了CommonJS的模块化写法,前端利用Webpack打包时来做解析。


5、Hybrid性能痛点及处理方案


Hybrid作为现在流行的APP开发模式,拥有着跨平台、迭代快、开发体验好等明显优势,同时也存在着加载慢,用户体验差这两个痛点。如果要像Native一样的体验,H5真的很难处理,H5无法控制,我们需要React Native。那这里只讨论“加载慢”这个痛点。


我们把Hybrid的“加载慢”问题拆分为下面3个点。


1. WebView打开慢


根据我们的测试无论Android还是iOS首次初始化WebView时所花费差不多要300~400ms,第二次初始化需要100~200ms左右。可以看出第二次初始化要快一些,所以这里我们可以通过提前初始化一个WebView来提升性能,或共用一个Webview。


2.   页面加载慢


如果页面在服务器端渲染这个问题会比较大。我们选择静态直出,将Webapp包嵌入到APP中,基本页面可以达到秒开。


静态直出带来一个问题是如何实时更新?我们Native端做了一套更新机制,可以根据API的数据实行打开APP就更新静态文件。我们只要保证打包Webapp将Webpack打包的模块ID固定不变,这样我们就可以在提交更新包时做文件差异化比较,更新包会非常下,加载也会很快。


3.   页面脚本资源加载和解析慢,数据资源加载慢


这一环节是性能优化的重点,优化不好直接导致了白屏时间过长。因为静态直入方式,页面基本在300ms内会出来,所以我们做下面几个优化操作。


第一步,我们将页面调用的种子JS文件精简到最小,然后页面加载完后再去异步加载和执行其他JS文件。这样做的目的是使Android WebView尽早触发OnPageFinished事件,减少白屏时间.


第二步,接口缓存数据,接口缓存数据即是每次请求接口的数据根据业务场景设置缓存时间,在这段时间直接使用不再调用接口,这样只有渲染消耗了。


第三步,有了接口数据缓存,但仍没有解决首屏数据首次记载的问题。这一步就是通过在发布APP前,打包最新首屏接口数据以JSON的格式一起打包到APP中,同时首屏图片资源也一起打包进APP。在页面展示时先从本地取数据展示,然后再请求接口,等到接口返回最新数据后替换掉页面数据和本地缓存中的数据,保持数据新鲜度。


第四步,有了前三步还是有部分白屏时间,特别是首屏组件复杂的情况下。我们紧接第三步,打包时我们不再只将接口数据打包成JSON文件,而是直接生成HTML到首屏静态文件,只要页面打开就能看到内容了。这也是我们最近正在优化的一步。


第五步,有了第四步,白屏时间已经缩短许多了,但会发现出来了页面却不能操作的情况,这就是这步需要去做的,通过减小初始化执行代码量和减少和APP Native代码的交互来解决脚本解析慢的问题。这是我们将来的一个优化方向。


这其中第3点是所花的时间最多,效果最不明显,可以考虑在后期再慢慢优化。


6、同构:基于Node的SPA SEO解决方案


“Write once,run everywhere”这是一句形容Java的语句。现在Node出来后JavaScript也可以用这句话来描述。一份代码同时在客户端浏览器和服务端Node运行,这就是JavaScript同构。


SPA的硬伤是首屏性能差和几乎达不到SEO效果,这导致很多需要SEO和首屏快速渲染的应用不会使用SPA这种模式。而小部分SPA应用通常用下面两种方法来处理这块硬伤。


1.  用服务端语言重写一套页面给搜索引擎用。

2.  理解JavaScript解析器在服务端来解析客户端的脚本语言,例如服务端嵌入V8解析器。


前者属于高成本的方案,而后种属于低性能方案。所以我们基于Node,利用JavaScript同构来解决SPA的这两个问题。


理想的前后端同构方案


目标:前后端同构数据Model、页面View,路由规则以及一些工具类方法。


同构Model层代码


Model作为连接前端展示和后端业务数据的重要层,前面有讲到,它包含了接口名称,接口调用方法,数据格式化方法和缓存处理,以及一些错误处理方法。而接口调用方法和缓存处理这两块客户端和服务端的实现有所不同。要同构,客户端与服务端的调用方式必须相同,而我们需要Node做到以下三点即可:


  1. 写一个类似Ajax的方法,将接口调用方法由原来的XMLHttpRequest替换成Http模块请求。保证调用使用相同方法。

  2. 写一个存储器基类,处理原来Model的本地缓存机制,这里可以用Redis或Node变量实现。

  3. 重写一个Model基类,方法属性跟前端框架中的Model基类一样。

  4. 为避免模块调用依赖,客户端和服务端的Model必须都是两个,一个是无依赖模块的纯数据处理Model,另一个Model基于这个纯数据处理Model的扩展,区别于服务端和客户端,分别加载不同的依赖。


同构View层代码


我们框架没有实现这块同构,原因:


  1. 我们SPA中的React组件相对复杂,依赖模块也较多,我们必须跟Model一样抽离出一个纯展示组件。而对于一个所有操作都由数据流控制的React组件,要抽离一个纯展示组件来兼容成本高。

  2. SEO和首屏渲染需要的React组件非常简单,而且必须简单,这样才能提高首屏渲染效率,就算不复用成本也不高,也没有必要和SPA的结构时时保持一致。


我们的这层处理方案:服务端和客户端用了两个不同React组件来处理,服务端组件仅包含首屏的数据结构,在服务端通过Node渲染好,呈现给用户和搜索引擎。这样搜索引擎能搜到内容,用户打开网页也可以跳过JavaScript加载和渲染这段白屏时间。


同构路由规则和工具类层代码


路由规则重构非常简单,在SPA框架的路由规则支持Express路由即可,然后路由规则放一个模块中前后端同时调用即可。


工具类更不用说了,都是JavaScript,语言上就可以重用。只是要注意,这些工具类都是不依赖其他模块的。


最终的方案


  1. 客户端服务端的脚本写法我们都遵照Node的CommonJS规则。

  2. 同构Model,路由规则和工具类

  3. 服务端负责处理路由规则,调用自身的页面展示组件,生成HTML再呈现给用户。HTML中还包含本页所需数据JSON数据(由于这些数据服务端已经请求好,避免客户端再掉接口获取,作为初始化数据返回)。

  4. 客户端JavaScript加载完后,判断HTML中有初始化数据,用这些数据重新渲染当前页,并绑定各个事件。


最后一点大家可能疑问,为什么这样?这样会出现渲染两次的。没错就是渲染了两次,这就是我们现在框架需要改进的方向,我们将来的方案应该是利用后端提供的数据绑定页面上的React组件,而非重新渲染。


7、SPA和React结合的思考


SPA的优势是体验好,但由于脚本操作DOM渲染,在复杂的富客户端情况下,导致渲染速度是最大的性能瓶颈。而React就是为解决富客户端渲染速度问题而生的一个框架。框架总是在解决问题的同时会带来新的问题,我们现在就来看看我们碰到的新问题。


性能没有得到解决


本打算用React来解决性能问题,但用后才发现性能问题仍没得到解决,甚至比原来还差。我们总结了几点:


  1. React文件太大,导致加载JS耗时增加,导致首屏慢。此问题可以用react-lite代替React上线来解决。现在随着React的升级,该问题也会被官方慢慢在解决。

  2. 首页组件数据太多,数据同时渲染,导致渲染耗时增加。解决此问题需要拆分组件,数据分部渲染。对于需要请求数据的组件可以用默认数据填充或加载中组件临时替换。

  3. 组件绑定数据太大,导致每更改一个属性导致整个组件刷新。解决此问题需要做两点,首先要思考该组件是否需要绑定这么多数据,其次可以用shouldComponentUpdate来优化。


数据流控制与Redux


React的状态机制很强大,所有UI变化都有状态来控制。但如果状态太多,特别是对于组件间经常通讯频繁的情况,靠自身的状态管理机制来处理太复杂了。为了解决这个问题,我们引入了Redux来管理React的状态机制。事物总是辩证的,Redux的引用也一样,带来好处的同时,也给我们带来了烦恼,我们总结了一下。


思维大转变与全局公共组件调用


当业务开发人员写业务代码时,以前关闭和打开隐藏一个加载组件,只需要写一行代码即可。但现在我们告诉他,你不能这么做,你需要通过dispatch一个action,然后在reducer识别到这个action,并将store内的属性更改,然后reducer返回一个新的state。大家都觉得相当复杂,包括我自己都这么认为。


这其实是在项目前期,我们心里对Redux还是有所抵触,思维要从原来的操作DOM到操作React这种状态操作,同时对从React直接操作状态到通过action去更改组件状态,的确需要时间消化。于是我们还是把这些基础方法定义在了我们的全局对象上,同时在基类实现了这些复杂的操作,业务只需要调用这些方法发送相应的action即可,还按原来的方式调用。


我们是否真的需要Redux?


当我们用到Redux-devTools这个插件后,充分看出Redux可预测性好处。但用了一年多后还是做了这个思考:我们是否真的需要Redux?原因是Redux有很多束缚,很多简单的页面,严重增加了代码的复杂度和开发时长。Redux优势是管理复杂的状态,而我们大部分场景的复杂度可以通过一些内部状态和高阶组件的方式来规避,而不一定要Redux。


于是我们开始考虑,Redux的思想非常好,我们需要保留。action和reducer有些情况是否真的有必要写? 这些问题都在等待我们解决,这里不深入,因为我们也只是思考中。提到的目的是让大家在实现自己的移动业务框架考虑一下自己的应用场景是否真的需要Redux。


8、我们如何实现工程化,自动化


最后我们来我们在做这个SPA框架时如何实现的工程化。


1. 技术选型时,我们就做了一系列的代码规范。框架开发完后有提供了一些说明文档Native通讯说明,数据存储说明,全局变量及工具类说明,模块按需加载说明,组件编写说明等。


2. 模块化,组件化,插件化,服务化严格定义了模块的分工和应用规范,提供公共组件、插件和服务模版参考。


3. 利用JSDoc生成框架各模块使用说明,开发一个UI通用组件展示站点,方便公共组件应用和推广。


4. 实施敏捷开发,明确阶段目标和版本计划。使用敏捷,主要目的是让开发过程更透明,更稳定和高效。线上敏捷系统方便各级项目管控;线下白板,小组内部实时沟通,方便跟进进度和处理紧急任务。


5. 统一开发IDE为VSC。利用IDE的插件和代码片段功能,自定义框架的代码提示和补全片段和插件,降低开发成本。同时统一配置ESLint,CSSLint插件,随时检测代码质量。


6. 一键自动构建业务系统脚手架,参考Grunt-init。


7. 实现开发代码和浏览器代码自动同步,利用Webpack+ Browser-Sync保存代码自动刷新浏览器。


8. 代码自动化构建Gulp+Webpack实现代码的解析,压缩合并,异步加载,数据缓存等,达到一键构建。


9. 自动化单元测试Karma+ Jasmine配合Jenkins,Webpack,实现打包和构建前先运行单元测试。


10. 持续集成部署,Jenkins加各种插件实现持续集成,一键打APP和H5最终发布包,同时非生成环境的自动部署和一键部署功能。


11. 将用户访问的性能和错误数据实时反馈到服务端,定期分析和修正。


12. 代码Review+持续学习+鼓励创新,提高团队自身实例。


自动化测试


单元测试,我的目标TDD。TDD对于前端开发人员的要求非常高,主要是思维模式上。这是我们的一个方向,我们现在单元测试这块主要做了一些必要逻辑的单元测试,未做到全系统。主要使用的框架:Karma + Jasmine。其中Jasmine负责测试代码部分,Karma负责自动化。


写单元测试要注意的几点:


  1. 不要写对接口返回结果测试的代码,那是接口测试的范畴。单元测试只关注传值是否正确。

  2. 业务代码不要写对框架方法的单元测试,业务代码只需要验证调用的方法和传值是否正确。框架的单元测试代码自有框架去写。

  3. 不要写能功能测试,单元测试是对单个方法逻辑的检验。如果要涉及到多个方法或这个功能依赖,要么单元测试思路有问题,要么就是代码需要重构。

  4. 不要追求100%覆盖,特别是时间仓促的情况下。

  5. TDD比后补单元测试更节约时间。


持续集成与自动化构建


我们整个持续集成如下图,我们持续集成分开发,构建,测试和部署四块。


持续集成整个过程中,出了开发写代码和人工测试这两个过程,其他过程基本都能自动化实现。


自动化构建


自动化构建,我们分两块:Webpack构建和Jenkins构建。Webpack主要follow在代码级别,而Jenkins则在工程级别。


Webpack打包,存在开发和构建两个阶段。构建阶段的“应用打包”即是开发阶段的整个打包过程。主要用到了Webpack,Gulp,Babel,Browser-Sync,ESLint,CSSLint,Karma等。


  1. ESLint,CSSLint检查代码写法。

  2. 利用Babel解析代码ES6->ES5,LESS->CSS,JSX->JS。

  3. 处理异步请求代码

  4. 运行单元测试并生成报告。这属于可选步骤,如果开发时可以关闭,单是Jenkins构建必须走的一步。

  5. 压缩和优化代码。

  6. 开发模式下,更改代码后自动更新浏览器内容。

  7. Hybrid模式下,下载最新生产首屏内容数据打入包中,降低APP下第一次打开时的白屏时间。

  8. 框架打包时,生成框架全局变量VSC代码提示片段。


Jenkins构建,整个构建和部署阶段都可以在Jenkins上完成。目前我们除生产部署外,其他环境都在Jenkins上进行。简单的说Jenkins构建就是将打包的各种人工操作集成到一个Job,实现自动透明的打包和部署操作,而整个过程生成完后,我们还能看到整个生成后的结果报表。


Dev: 开发人员提交代码,Jenkins就自动拉代码,做好打包准备,运行Webpack打包,打包完后发布到DEV站点。打包到DEV站点的代码都是经过代码质量检测和单元测试的,明显问题不会很多。


Test:测试人员准备测试时,在Jenkins点击Test环境构建Job就可以一键部署了,过程和dev相似,不同的是Test环境发布的同时这时候会生成一个以产品版本号、日期和构建版本号命名的包到GIT。如果这轮测试没问题,这个包将会成为生成发布包。


Prod:我们集群部署公司自己编写的软件发布,取GIT上通过测试的包。当然这个工作Jenkins也是可以胜任的。


持续集成在整个工程化过程中也是非常重要的一环,而整个持续集成过程中自动化测试为不可或缺的一部分。我们现在只做到了代码自动化测试,如果做到UI自动化测试这就更完美了。UI自动化测试也是我们将来的一个方向,通过selenium来实现已经在我们的日程中,我相信UI自动化后,会使整个工程化的效率更高。


最后总结,在整个开发的历程中,我们知道没有最好架构,只有最适合的方案。


Facebook,Google等公司的方案,我们可以参考,但不能照搬。每个团队都有自己的特色,在开发业务框架的过程中,我们要多利用团队的优势,多思考自身团队整体能力区间以及产品所处的应用阶段,多考虑成本和效益,从重点功能着手,以多次迭代的方式来开发和完善它。最后分享一张我们框架基础架构图给大家参考。


推荐阅读:






本周六(12月16日)Ctrip Cloud Meetup

聚焦人工智能/云计算/容器技术/持续发布话题

点击下方图片直接报名

↓↓↓

 



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

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