京东架构师:前端工程化在京东首页实践

2016年3月,互联网技术联盟(ITA1024)推出前端技术专题月,由联盟成员企业推荐国内一流技术专家联手打造。经过每周的线上万人课堂和每个月的线下ITA1024互联网技术大会,针对前端开发,前端框架,性能调优,复合型前端技术等热门话题展开深刻的分享和交流。javascript

 本期分享嘉宾:刘威(京东资深前端架构开发工程师)php

本期特邀主持:赵晓强(百度高级前端工程师)css

 以下是3.28刘威在ITA1024前端精英群分享实录。html

京东首页前端架构设计与实现

面临挑战前端

前端页面静态化java

前端页面总体架构node

前端页面加载策略python

前端基础架构linux

前端工具和系统nginx

前端灾备策略

前端性能优化

前端工程化在电商首页的实践

命令行工具

前端模块

前端组件

前端开发流程

前端文档

实际应用

京东首页前端架构设计与实现

一、面临挑战

  • 页面DOM元素剧增:单个楼层Tab标签由5个到9个

  • 页面总体高度翻倍:算上头尾,共计14个楼层,高度也由4820px到9862px

  • 页面图片量增长:80%的位置变为图片展现

  • 首屏加载时间要有保证:加载时间相比原来不能增长

  • 首页独特的影响力:页面不能空白,不能有报错

  • 大流量高并发,对稳定性要求极高

  • 对接业务方不少,临时需求、紧急需求较多

 

二、前端页面静态化

众所周知,通常网站首页栏目会有不少,不一样栏目的数据库查询方式也不一样,而首页流量巨大,若是按照通常的动态网站每次用户来时查询后台数据库取数据的话,开销巨大,从而致使首页访问速度下降,因而要考虑作静态化,咱们具体的架构以下:

 

接入层:CDN—>HAProxy—>nginx

应用层:PHP Redis

存储层:Mysql

首先,用户访问首页实际是请求CDN上拼接好的静态页面,往下会到达Nginx动态缓存,而后到达Nginx,Nginx再把请求转发给PHP进行处理,并经过HTTP头来精确控制缓存;接着到了PHP应用层,使用了Redis做为缓存,并用MySQL存储数据;定时2分钟循环任务生成静态文件,从源服务器同布分发至各地CDN结点。这样用户访问时,会访问到离当前用户最近CDN结点机器上拼装好的静态文件。

三、前端页面总体架构

  • JS部分

首页页面依赖的JS库为jQuery V1.6.4版本,前期调研评估后采用这个比较低的版本,是由于首页改版上线后,头尾组件以及一大部分公共组件要推至全站使用,即还要平滑升级上千个一二级页面站点的公共头尾,而旧页面引用了很是旧的jQery版本,要想平滑升级综合评估后,1.6.4这个版本最合适;另外jQuery咱们根据总体业务进行特殊修正,就好比jsonp回调函数名称后端规范对长度有要求,咱们就对长度进行修正等等。

 第二层是JDF公共组件,包括UI组件、Unit业务组件、Widget模块,这些组件包括event,localStorage,焦点图,动画,地区选择,对话框,下拉菜单,懒惰加载,suggestion,login,search,category,cookie等常见交互组件和业务组件;同时也有当前业务级须要的公共组件,好比楼层懒加载组件,楼层切换电梯组件等。

 第三层是页面脚本,好比今日推荐楼层,猜你喜欢楼层,广告楼层,每天低价楼层等等。

 

  • CSS部分

改版前的公共样式base.css耦合很多公共组件样式,如今采用很轻的样式重置ui-base.css,公共组件单独按需调用自身样式,完全解耦。另外首页全部楼层依赖的样式文件是放在首屏加载,而不是请求当前楼层时异步加载,主要为了保证楼层的高可用性和预加载,另外单一个楼层文件很是小,每次到达时候异步加载并不如放在首屏,全部楼层样式文件combo成一个文件请求加载合算。

四、前端页面加载策略

常见的前端页面加载策略通常为:后端从数据库读取出数据,拼装好必要的页面元素,图片作lazyload(懒加载),用户经过浏览器加载页面上的全部HTML元素。

 基于首屏加载时间最短的原则,咱们的页面加载采用:楼层异步加载和本地缓存方式,具体以下:

    •   把页面按楼层进行拆分,把首屏作为页面框架主体,每一个楼层的数据,单独作成数据接口,异步加载,页面仅保留一个楼层一个DIV标签,好比以下服饰楼层:
  • <divclass="w floor lazy-fn" data-title="服饰"id="lazy-clothes" data-path="floor1-floor_index.js"data-time="01d15d664a61ff8f11cf6321f5b7a503"></div>
    其中floor1-floor_index.js是楼层数据文件 ,包含楼层数据和信赖的脚本文件,其样式已放在header里预加载。
    

      

  • 给每一个楼层设置默认高度,到达这个区域时请求当前楼层数据文件,同时对楼层数据文件进行md5(即data-time),并把楼层请求后的数据文件和其data-time localStorage至本地,若是页面上的楼层data-time和本地localStorage中的data-time值同样,数据直接取localStorage,若是不一致,从新ajax请求数据,请求后同时会localStorage数据至本地;

  • 第一次ajax请求数据时,会延时5秒,查看请求是否成功,若是成功则退出,若是失败:首先会取上次localStorage数据作垫底数据,但浏览器若是不支持localStorage, 基于高可用性原则考虑会进行第二次ajax请求数据,若是第二请求成功则正常渲染和localStorage楼层,若是第二次请求失败,此时极大可能说明网络存在异常,这时为了保持良好的用户体验,不能留有空白,直接隐藏当前楼层。

  • 其中用户到达当前楼层时,请求楼层数据(包括楼层的html结构和当前楼层的脚本),渲染数据到楼层元素中,同时初始化楼层脚本。另外楼层有提早预加载逻辑,即用户在第一二楼层时,提早加载好第三四五楼层的数据,到达楼层即显示当前楼层数据。

  • 楼层异步加载和本地缓存方式的好处:页面大约共有3288个元素,首屏大约仅900左右,楼层数据lazyload,只渲染首屏元素,大大下降首屏页面内容大小,极大的缩短页面首屏加载完成时间,当时测算首屏约1.6s即加载完,时间比旧版和竞品缩短不少。

  • 数据接口优先请求走本地localStorage,减小后台服务器压力,节约成本。请求数据时延时查当作功与否,同时二次请求保证高可用性。楼层之间数据和脚本彻底解耦,有利于后期维护,可随意定时上线和下线运营楼层。

五、前端基础架构

以下图:

六、前端工具和系统

主要是以JDF命令行工具为核心进行开发,工具和系统的具体使用步骤以下:

  • 在代码共享平台新建项目工程

  • 使用JDFinit进行项目构建,生成标准化的文件目录和工程文件

  • 使用JDFbuild进行模块编译

  • 使用JDFoutput进行输出

  • 联调时使用JDF upload把编译好的静态文件上传至测试服务器

  • 在页面元素上增长前端统计埋点,检测元素的点击量

  • 联系后端把项目中页面头部和尾部代码放在头尾系统里,以备后续头尾业务变动

  • 上线。上线前经过git提交代码至代码库,上线时在Jone统一工做平台使用线上JDF进行打包;经过Deploy系统把静态文件发布至CDN; 上线后清除静态文件CDN缓存,同时检查线上是否正常;上线后在听云上监控页面性能,按期生成性能报表邮件。

七、前端灾备策略

灾备是为了保持线上业务在极端不好网络环境下的高可用性,具体作法以下:

  • 本地缓存:异步接口数据优先使用本地localStorage中的缓存数据

  • 二次请求:接口数据本地无localStorage缓存数据,从新再次发出ajax请求

  • 双层接口:部分接口和域名会上线至CDN,业务调用时优先使用CDN接口,若是必定时间内未请求到数据,会用源站接口再次请求

  • 垫底数据:源站接口万一没法访问,使用预设好的垫底备份数据

  • 接口下线:必要时候,下线非核心业务接口和非核心功能

 八、前端性能优化

好的页面性能能够提升页面加载速度,从而增长用户体验,主要以下:

  • 尽量减小首屏元素数量:用异步加载和本地缓存加载数据

  • CDN加载慢时候,减小页面空白几率:页面CSS样式文件内联在页面上

  • 减小页面请求数量:CSS/JS combo,CSS sprite

  • 减小静态文件体积:CSS/JS/Images压缩

  • DNS预解析:头部增长好比<link rel="dns-prefetch" href="//d.jd.com"/>

  • 减少Cookie体积等等

 

前端工程化在电商首页的实践

JDF京东前端开发集成解决方案,核心就是解决前端工程化的问题,包括命令行工具、前端模块、前端开发流程、前端组件、前端文档。

Github地址:https://github.com/putaoshu/jdf/

 

一、命令行工具

JDF命令工具基于nodejs,介绍以下:

  • 跨平台

        完美支持windows、mac、linux三大系统

  • 项目构建

        生成标准化的项目文件夹

        支持本地,联调,线上三种开发流程

        每一个项目都拥有一个单独的配置文件,按选项统一编译

  • 模块开发

        可快速方便的对模块进行建立,引用,预览,安装和发布

        经过积累,可造成彻底符合本身业务的模块云服务

  • 模块编译

        支持模块编译,内置模块编译引挚

        支持将vm和smarty模版编译为html

        支持将sass和less编译为css

        支持ES6

  • 项目优化

        自动将页面中的js、css引用转换成combo请求格式

        自动压缩优化js、css、png文件

  • 项目输出

        默认给全部静态资源添加CDN域名前缀或后缀戳

        支持cmd规范,自动提取文件id和dependencies,压缩时保留require关键字

        支持png图片压缩插件,将png24压缩为png8

        自动生成css雪碧图,并更新background-position属性值

        可将小图片一键生成base64编码

        文件编码统一化,即不管当前文件格式是gbk,gb2312,utf8,utf8-bom,统一输出utf8

  • 项目联调

        一键上传文件到测试服务器,方便其余同窗开发预览

  • 本地服务

        支持开启本地服务器,方便调试

        支持本地静态文件预览,内置本地开发调试服务器,以及当前目录浏览

        支持实时监听文件,文件被修改时会自动编译成css,并刷新浏览器

        实时在控制台输出错误信息,方便定位代码错误

  • 辅助工具

        支持html/js/css文件格式化

        支持html/js/css代码压缩

        支持html/js/css文件lint,代码质量检查

        支持chrome浏览器的LiveReload插件

 

二、前端模块定义

前端模块即widget:包括配置文件、数据源文件、模版文件、样式文件、JS文件、图片文件,以下ui-product-list为一个widget:

 

  • ui-product-list[文件夹]

  • component.json[配置文件]

  • ui-product-list.json[数据源文件]

  • ui-product-list.vm[模板文件或者.smarty文件]

  • ui-product-list.scss[scss文件]

  • ui-product-list.js[js文件]

  • images[图片文件]

 

页面中引用widget用以下代码片段

{%widgetname="ui-product-list"%}

很明显html不支持{%%}语法,此时会用到jdf的编译命令"jdf  build",核心是把ui-product-list.json中的json数据打到ui-product-list.vm模板上,最后输出静态html片段;同时.scss编译成.css,而且在header头增长样式引用:

<linktype="text/css" rel="stylesheet" href="/widget/ui-product-list/ui-product-list.css"source="widget"/>

页面尾部增长js引用:

<scripttype="text/javascript" src="/widget/ui-product-list/ui-product-list.js"source="widget"></script>

输出的时候使用jdf output会把多个wiget中js/css引用变成combo的形式如:

??/widget/ui-product-list/ui-product-list.css,/widget/header/header.css/widget/footer/footer.css

这样就算页面引用N多个模块,也只会发出一个请求

三、前端模块使用方法

  • 模块安装:(jdf widget -install xxx ) 解决可复用的问题,不用每次都新建,能够先在模块云上查找合适的模块,而后下载至当前项目

  • 模块新建:(jdf widget -create xxx ) 在本地新建一个模块

  • 发布模块:(jdf widget -publish xxx ) 解决了共享和沉淀的问题,项目结项,能够把可复用或者修改后的模块提交至模块云,审核经过就会发布至模块云上

  • 模块预览:(jdf widget -preview xxx ) 用于调试单个模块,同时解决了团队协做问题,项目分红多个独立模块,各负其责

  • 模块列表:(jdf widget -list) 展现模块云上全部模块的列表

 

搭建成本很是低,只需配置好一台FTP服务器,经过FTP服务器储存,下载,以及分配相关用户权限,版本管理依赖于widget中的componet.json中的version,使用方便,可使用jdf命令行工具进行发布,下载

四、前端组件

前端组件主要由UI交互组件和Unit业务组件构成,通过积累,已有如下部分公共组件可供全站使用,以下:

前端开发流程

  • 使用JDFinit进行项目构建,生成标准化的文件目录和工程

  • 引用JDFwidget -install模块云中能够直接使用的已有模块,同时对页面按业务进行拆分,一个同窗负责一个或者多个模块,独立开发和调试

  • 使用JDFbuild进行模块编译

  • 使用JDFoutput进行输出

  • 联调时使用JDF upload把编译好的静态文件上传至测试服务器

  • 上线前经过git提交代码至代码库,上线时使用线上JDF进行打包

六、前端文档

 

    • 前端文档-规范

 

文档请参考: https://github.com/putaoshu/jdf/tree/master/doc

  • 前端文档-组件API

生成工具请参考: https://github.com/putaoshu/jdd

  • 前端文档-命令行工具

文档请参考: https://github.com/putaoshu/jdf/tree/master/doc

七、实际应用

本地新建项目,调用线上模块云相关widget,本地使用JDF工具进行编译,联调,输出,上线时使用线上JDF进行打包,发布至CDN,最后清静态文件缓存。

 

————————————Q&A————————————

问:公共模块云是本身搭建的,仍是基于私有的npm仓库?

刘威:本身搭建的,只需在linux上配置好一台FTP服务器,经过FTP服务器储存,下载,以及分配相关用户权限;有好的模块能够发布,开发项目时候看到已有模块能够下载到当前项目中;版本管理依赖于widget中的componet.json中的version;使用方便,可使用jdf命令行工具进行发布,下载

 

问:不少这种样式class=“xxx|yyy|zzz” ,是什么意思啊?

刘威:你说的应该是clstag,这是前端埋点统计用的,就是记录某个连接或者某个区域点击的次数,xxx是页面,yyy为楼层,zzz为某个元素,是当前站惟一的标识

 

问:jdf用什么语言写的?go?python?

刘威:Nodejs

 

问:刚才提到的“上线后清理本地缓存”是什么意思,是指服务器的cache么?那么用户本地的资源文件缓存是如何处理的?

刘威:是指清CDN上静态文件的缓存,咱们某个项目中静态资源会有统一前缀,好比product/home/1.0.0/a.js,若是项目有更新会发product/home/1.0.1,大型项目会变动大的版本号product/home/2.0.0这样,这样到用户那里都是全新的文件。

 

问:关于cdn缓存,假若有文件更新大家是怎么处理?时间截、md5?

刘威:通常是给静态资源前面加统一的版本号来实现,若是特别小的变更,不想变动文件url,能够在文件上线后,经过CDN后台,手动清当前文件的CDN缓存。

 

问:大家的图片压缩是如何实现的,应用那些组件?

刘威:咱们本身根据不一样操做系统进行了封装,能够下载https://www.npmjs.com/package/jdf-png-native查看

 

问:模块的依赖解析也是在jdf构建时处理的么?好比a依赖b,b依赖c,在使用a时,是否是会把a b c三个js都加到尾部?

刘威:模块中的js依赖咱们基于CMD规范,jdf工具会自动提取文件id和dependencies,压缩时保留require关键字,另外某个项目通常建议有一个统一的入口文件,好比init.js,若是init.js有依赖的经过seajs的combo插件完成全并依赖加载。

 

问:刚才资源文件是用版本号来区分,那么若是多出引用更新后是否须要多处修改版本号?

刘威:咱们项目通常会把页面的公共头尾接入公共头尾系统,就是头部的静态html片段以及js/css引用,而项目会对应头尾部系统中某一个编号的头尾,前端静态资源上线后,只须要修改一处,而后系统就会推送到业务线中。

 

问:DNS预解析在不一样浏览器的兼容性如何?会不会影响当前页面的性能? 确实咱们也有一些页面作了上十个DNS预解析,甚至一些暂时用不到的。

刘威:DNS预解析在不一样浏览器的兼容性:Chrome: 所有 IE: 9+ Firefox: 3+  Safari 5+ ,能够说大部分高级浏览器都支持的;若是业务依赖的域名不少,建议增长上DNS预解析。

 

问:先后分离的状况下,SEO方面,怎么处理会比较好?

刘威:先后端分离对SEO没有什么大的关系吧,个人建议是最好前端把后端模板层接过来,好比php的smarty,java的vm,由前端来写模板,另外而jdf工具支持.smarty,.vm模板渲染,能够看一下;SPA页面的SEO我还没好的建议

 

问:jdf应该与glup,grunt,fis等相似的构建工具吧,jdf有什么特色?

刘威:jdf比较轻便,另外jdf依赖的npm插件通常通过会咱们精心筛选,也有部分第二次封装,不像grunt的插件质量良莠不齐;fis相对jdf比较重,fis基于java,php,node都有一套独立的解决方案,而jdf只须要nodejs下便可支持.smarty,.vm模板渲染和解析

 

问:关于页面静态化,有几个问题

1.“并经过HTTP头来精确控制缓存”,这个精确控制指的是什么?

2.我是否是能够这么理解,用户请求先访问CDN,若是CDN有数据直接返回,没有数据回源到后端服务器,此时的逻辑是分级缓存策略,减小对服务端上游接口的压力,“定时2分钟循环任务生成静态文件” 这个任务是单独部署一台服务器,只从上游接口拼装数据生成静态html,而后推送同步到CDN节点?

3.静态html从源服务器同步到不一样的CDN节点这个是怎么实现的?

4.若是个性化的数据好比根据用户维度下发不一样的定向数据,或者是 有秒杀楼层等时效性很强的楼层,这个静态化就没有意义了吧?

刘威:1.经过Cache-Control控制,每两分钟更新一次 2.对的 3.生成好静态文件碎片,经过nginx定时任务,从源服务器同步至CDN 4.对的,个性化推荐是经过ajax异步加载数据,每次都不同。

 

问:若是是统一头尾更新版本号那么是针对各个资源更新仍是全部资源?若是是各个资源岂不是要维护不少版本?如何自动化?若是是全部资源用户的缓存岂不是浪费了?

刘威: 恩,jdf工具能够支持全部静态资源加统一前缀版本号,另外咱们有线上jdf环境,只须要在本地版本库里更新一下版本号,上线会系统自动处理,再而后这些静态资源上线后经过头尾系统手动更新版本号;固然最理想的方式是前端所有接入模板层,这样头部文件的版本号能够用所有jdf工具来自动更新了,但电商网站的后端系统复杂性暂时选择了半手动的方式,只有部分业务线进行了实现.

 

问:京东是如何处理线下测试smarty模版的?是前端直接在php环境下开发吗 ?

刘威:jdf工具支持.smarty解析,只要nodejs就ok,前端在本地修改测试后,能够一键发送至联调机器

相关文章
相关标签/搜索