京東架構師:前端工程化在京東首頁實踐


2016年3月,互聯網技術聯盟(ITA1024)推出前端技術專題月,由聯盟成員企業推薦國內一流技術專家聯手打造。通過每周的線上萬人課堂和每月的線下ITA1024互聯網技術大會,針對前端開發,前端框架,性能調優,復合型前端技術等熱門話題展開深入的分享和交流。

 本期分享嘉賓:劉威(京東資深前端架構開發工程師)

本期特邀主持:趙曉強(百度高級前端工程師)

 如下是3.28劉威在ITA1024前端精英群分享實錄。

京東首頁前端架構設計與實現

面臨挑戰

前端頁面靜態化

前端頁面整體架構

前端頁面加載策略

前端基礎架構

前端工具和系統

前端災備策略

前端性能優化

前端工程化在電商首頁的實踐

命令行工具

前端模塊

前端組件

前端開發流程

前端文檔

實際應用

京東首頁前端架構設計與實現

1、面臨挑戰

  • 頁面DOM元素劇增:單個樓層Tab標簽由5個到9個

  • 頁面整體高度翻倍:算上頭尾,共計14個樓層,高度也由4820px到9862px

  • 頁面圖片量增加:80%的位置變為圖片展示

  • 首屏加載時間要有保證:加載時間相比原來不能增加

  • 首頁獨特的影響力:頁面不能空白,不能有報錯

  • 大流量高並發,對穩定性要求極高

  • 對接業務方很多,臨時需求、緊急需求較多

 

2、前端頁面靜態化

眾所周知,一般網站首頁欄目會有很多,不同欄目的數據庫查詢方式也不同,而首頁流量巨大,如果按照一般的動態網站每次用戶來時查詢后台數據庫取數據的話,開銷巨大,從而導致首頁訪問速度降低,於是要考慮做靜態化,我們具體的架構如下:

 

接入層:CDN—>HAProxy—>nginx

應用層:PHP Redis

存儲層:Mysql

首先,用戶訪問首頁實際是請求CDN上拼接好的靜態頁面,往下會到達Nginx動態緩存,然后到達Nginx,Nginx再把請求轉發給PHP進行處理,並通過HTTP頭來精確控制緩存;接着到了PHP應用層,使用了Redis作為緩存,並用MySQL存儲數據;定時2分鍾循環任務生成靜態文件,從源服務器同布分發至各地CDN結點。這樣用戶訪問時,會訪問到離當前用戶最近CDN結點機器上拼裝好的靜態文件。

3、前端頁面整體架構

  • 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成一個文件請求加載合算。

4、前端頁面加載策略

常見的前端頁面加載策略一般為:后端從數據庫讀取出數據,拼裝好必要的頁面元素,圖片做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,減少后台服務器壓力,節約成本。請求數據時延時查看成功與否,同時二次請求保證高可用性。樓層之間數據和腳本完全解耦,有利於后期維護,可隨意定時上線和下線運營樓層。

5、前端基礎架構

如下圖:

6、前端工具和系統

主要是以JDF命令行工具為核心進行開發,工具和系統的具體使用步驟如下:

  • 在代碼共享平台新建項目工程

  • 使用JDFinit進行項目構建,生成標准化的文件目錄和工程文件

  • 使用JDFbuild進行模塊編譯

  • 使用JDFoutput進行輸出

  • 聯調時使用JDF upload把編譯好的靜態文件上傳至測試服務器

  • 在頁面元素上增加前端統計埋點,檢測元素的點擊量

  • 聯系后端把項目中頁面頭部和尾部代碼放在頭尾系統里,以備后續頭尾業務變更

  • 上線。上線前通過git提交代碼至代碼庫,上線時在Jone統一工作平台使用線上JDF進行打包;通過Deploy系統把靜態文件發布至CDN; 上線后清除靜態文件CDN緩存,同時檢查線上是否正常;上線后在聽雲上監控頁面性能,定期生成性能報表郵件。

7、前端災備策略

災備是為了保持線上業務在極端很差網絡環境下的高可用性,具體做法如下:

  • 本地緩存:異步接口數據優先使用本地localStorage中的緩存數據

  • 二次請求:接口數據本地無localStorage緩存數據,重新再次發出ajax請求

  • 雙層接口:部分接口和域名會上線至CDN,業務調用時優先使用CDN接口,如果一定時間內未請求到數據,會用源站接口再次請求

  • 墊底數據:源站接口萬一無法訪問,使用預設好的墊底備份數據

  • 接口下線:必要時候,下線非核心業務接口和非核心功能

 8、前端性能優化

好的頁面性能可以提高頁面加載速度,從而增加用戶體驗,主要如下:

  • 盡可能減少首屏元素數量:用異步加載和本地緩存加載數據

  • 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/

 

1、命令行工具

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插件

 

2、前端模塊定義

前端模塊即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多個模塊,也只會發出一個請求

3、前端模塊使用方法

  • 模塊安裝:(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命令行工具進行發布,下載

4、前端組件

前端組件主要由UI交互組件和Unit業務組件構成,經過積累,已有以下部分公共組件可供全站使用,如下:

前端開發流程

  • 使用JDFinit進行項目構建,生成標准化的文件目錄和工程

  • 引用JDFwidget -install模塊雲中可以直接使用的已有模塊,同時對頁面按業務進行拆分,一個同學負責一個或者多個模塊,獨立開發和調試

  • 使用JDFbuild進行模塊編譯

  • 使用JDFoutput進行輸出

  • 聯調時使用JDF upload把編譯好的靜態文件上傳至測試服務器

  • 上線前通過git提交代碼至代碼庫,上線時使用線上JDF進行打包

6、前端文檔

 

    • 前端文檔-規范

 

文檔請參考: https://github.com/putaoshu/jdf/tree/master/doc

  • 前端文檔-組件API

生成工具請參考: https://github.com/putaoshu/jdd

  • 前端文檔-命令行工具

文檔請參考: https://github.com/putaoshu/jdf/tree/master/doc

7、實際應用

本地新建項目,調用線上模塊雲相關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,前端在本地修改測試后,可以一鍵發送至聯調機器

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM