京東前端工程化和靜態資源管理全面總結

凹凸實驗室發表於2016-07-27

隨著網際網路的發展,我們的業務也日益變得更加複雜且多樣化起來,前端工程師也不再只是做簡單的頁面開發這麼簡單,我們需要面對的十分複雜的系統性問題,例如,業務愈來愈複雜,我們要如何清晰地梳理;團隊人員愈來愈多,我們要如何更好地進行團隊協作;功能愈來愈多,我們要如何保證頁面的效能不至於下降,等等。所有的這些都可以歸結為如何提升開發體驗和效能問題。

提升開發體驗

我們主要從以下三個方面來提升我們的開發體驗。

規範化

當團隊人員不斷擴充時,我們需要制定統一的規範來對平時的開發工作做出一定約束和指導。統一的規範包括前端的程式碼規範,根據規範定義好一套程式碼檢查的規則,在程式碼提交的時候進行檢查,讓開發人員知道自己的程式碼情況。

同時,根據以往的開發經驗,我們制定了統一的專案框架,根據業務功能不同,將一個專案(app)拆分成不同的業務模組(module),而每一個模組都包含自身的頁面(page)以及構成頁面所需要的元件(widget),每一個專案涉及到app、module、page、widget這些已經約定好的概念,這樣讓專案結構更加清晰,而且讓團隊內不同業務的人員之間切換無障礙。

元件化

在專案中引入元件化的概念,這裡的元件對應上文講到的widget,每一個元件都會包含元件自身的模板、css、js、圖片以及說明檔案,我們使用元件來拼裝頁面,像搭積木一樣來拼裝我們的頁面,同時一個元件內可以呼叫另一個元件。

在拿到設計稿後,我們首先需要確定哪些需要做成公共元件,那些是要做成獨立元件,以及元件間如何進行通訊。在頁面中呼叫這些元件後,會自動載入元件的模板以及元件的靜態資源,而當元件不再需要時,只要移除掉元件引用,那麼相應的模板和靜態資源也會不再載入。

元件化的好處主要有這麼幾點

  • 管理方便,我們可以把一個獨立功能相關的檔案在工程目錄中放在一起,這樣程式碼管理起來會非常便利
  • 元件複用,通過抽取公共元件,可以實現元件複用,從而減少工作量,創造價值
  • 分而治之,這是元件化最重要的一點,將頁面元件化,就是對頁面功能的拆分,將一個大的工程拆成小的零件,我們只需要關注每一個零件的功能,極大地降低了頁面的開發與維護的難度

自動化編譯

在前端開發中,我們總是會去使用很多工具、手段來優化程式碼、提升開發效率,例如,我們會使用sass、less等CSS預處理工具來編寫更好維護的樣式程式碼,我們也會使用CSSLint、eslint等程式碼檢查工具來檢查程式碼的語法錯誤,使用檔案合併壓縮等手段來減少資源大小,除此之外我們還會去做雪碧圖合併、多倍圖處理、字型壓縮處理、程式碼釋出等等。

曾經有大神說過,超過90s的工作都應該自動化掉。而以上所有的這些工作,貫穿我們整個開發流程,但是不同工具的切換不但顯得凌亂,而且影響開發效率。在自動化、工程編譯的思想早已深入人心的當下,我們當然也要緊跟潮流,所以我們考慮通過自動化手段來提升我們的效率,讓所有操作可以一鍵式開速執行完。

我們將通過定義好一系列的編譯任務,按照一定順序依次對我們的專案自動進行編譯操作,最後產生出可上線的程式碼。

提升效能

我們主要從以下四個方面來做好效能優化。

首屏優化

頁面的開啟速度一直是大家非常關心的一個指標,一個頁面開啟太慢會讓讓使用者失去等待的耐心,為了讓使用者更快地看到頁面,我們考慮將頁面中部分靜態資原始碼直接嵌入頁面中,我們通過工具處理,在工程編譯階段,將指定的靜態資原始碼內嵌入頁面中,這樣可以減少HTTP請求,提升首屏載入速度,同時降低頁面裸奔風險。

按需載入

同時,我們考慮通過儘量減小頁面體積來提升頁面開啟速度,在業務上我們將頁面劃分為一個個樓層元件,以京東美妝館為例,頁面中從上而下分為首焦、至IN尖貨、今日特惠、潮流前沿、口碑榜單這麼幾個樓層元件,其實這個頁面還有很長,內容非常多且複雜。

之前我們的做法是整個頁面直出,這樣一次性載入的內容會非常多,為了提升開啟速度,我們考慮通過按需載入的方式來優化頁面的載入。我們在頁面中只放每一個樓層的框架性程式碼,樓層的模板和資料都通過非同步的方式去拉取,來實現樓層元件的按需載入,同時我們可以對模板以及資料進行快取,以此來減少請求,做更極致的優化。在開發中我們以正常元件的方式去開發整個頁面,隨後通過編譯工具,在程式碼編譯階段自動將樓層的模板抽離成一個獨立的JS檔案,並給樓層容器打上標記位,通過頁面載入邏輯去按需拉取模板,再進行渲染。

通過給樓層容器和模板分別加上標記位 o2-out-tpl-wrapper o2-out-tpl

在編譯時自動將指定的模板程式碼抽離成獨立js檔案

並且給樓層容器打上標記

同時在邏輯指令碼適當位置自動加入模板的版本

通過上述步驟,實現按需載入的自動化生成,在提升效能的同時,很好地解放我們生產力。

基於資源表載入

根據頁面元件化,通過工具分析,我們將獲得頁面與元件的依賴關係表,同時也能確認頁面所引用資源的依賴關係,例如,我們在頁面hello中同步引用元件topbar,那麼依賴關係表中將會記錄同步引用關係hello引用topbar.tpl、topbar.css、topbar.js,那麼頁面hello將會自動載入元件topbar的CSS與JS,同時依賴表會記錄非同步引用的關係,假如我們在元件C中通過API非同步引用了元件D的js,那麼會在依賴表中記錄C非同步引用D.js這一個依賴關係,這樣D.js這個資源將會在用到的時候被非同步呼叫。

同步引用的資源通過生成combo形式連結,在服務端進行檔案合併,這樣在頁面載入的時候,頁面只會載入自己需要的同步資源,非同步的資源將會在用到的時候再載入,有效避免資源冗餘。同時刪除、增加元件也非常方便,只需改動模板中對元件呼叫,通過編譯工具會自動重新生成模板以及combo連結。

我們可以將資源載入的操作抽離出來,形成一套統一的資源載入框架設計,這樣我們使用的模板可以變得更加靈活,無論是純html模板,還是PHP或Java之類的後端模板都能有效支援。編譯工具掃描程式碼後只生成資源依賴表,我們通過實現各語言平臺的資源載入框架,讓不同語言的模板都能基於同一個資源依賴表進行資源載入。

同時,對資源進行MD5重新命名處理,檔案md5重新命名也是一種提升效能的有效手段,使用檔案md5後開啟伺服器強快取,可以提升快取的利用率並避免不必要的快取判斷處理。但檔案md5重新命名後會出現開發時引用的檔名對不上的問題,這就需要在資源表中記錄原檔名與md5重新命名後之間的對應關係,當我們引用一個資源時,就會通過查表獲取重新命名後的資源名,然後利用程式碼中引用資源定位的能力來進行資源名自動替換。

靜態資源預載入

所謂靜態資源預載入,就是當使用者在進行瀏覽頁面的時候,我們可以在當前頁面靜默載入下一個頁面的靜態資源,這樣當使用者進入到下一個頁面時就能快速開啟頁面,從而在不知不覺中提升頁面的開啟速度。

我們會在靜態資源預載入平臺上配置每一個頁面id對應需要預載入頁面資源的id,然後系統通過讀取資源依賴表獲取到所需要預載入的靜態資源,生成預載入資源列表檔案,再將檔案推送到線上伺服器,通過頁面掛載js請求獲取預載入資源列表,隨後靜默載入資源。在有了資源依賴表後,我們可以準確地分析到每一個頁面引用資源的請求,就可以很好地實現靜態資源預載入的功能。

Athena

工欲善其事,必現利其器。為了實現我們對提升開發效率和產品效能的訴求,我們提出了比較完整的工程化解決方案以及對應的工具Athena

Athena是由京東【凹凸實驗室】(aotu.io) 推出的一套專案流程工具,通過Athena,我們可以很流程地跑完整個開發流程。Athena分為兩部分,一是本地自動化編譯工具,二是資源管理平臺,其架構如下

本地自動化工具

Athena本地編譯工具是一個基於NodeJs的命令列工具,通過執行命令的方式來優化我們的開發流程,目前Athena的主要功能有

  • 自動建立專案、模組、頁面、元件結構
  • 輕量元件化功能,根據元件載入情況生成資源依賴表
  • Sass/less 編譯
  • 程式碼檢查
  • CSS prefix等處理
  • CSS合併壓縮,JS合併壓縮
  • 自動生成雪碧圖,自動多倍圖,圖片壓縮
  • 字型檔案壓縮
  • 自定義圖片轉base64
  • 檔案內聯,可以內聯樣式及JS程式碼
  • 檔案MD5戳,將檔案進行使用MD5進行重新命名
  • 本地預覽,直接檢視整個專案
  • 資源定位(圖片等資源路徑替換)
  • 生成CSS頁面片,提供將頁面引用的CSS/JS抽離成頁面片的形式,方便管理CSS資源
  • 部署到預覽機和開發機

建立專案結構

在執行建立命令時,Athena會從管理平臺下載自定義好的專案模板,可以根據模板建立專案、模組、頁面、和元件。Athena有四個建立命令:

通過執行 $ ath app demo 命令就可以生成定義好目錄結構的專案。

隨後可以通過 $ ath module home來建立一個業務模組;

通過 $ ath page index 來建立頁面;

通過 $ ath widget widgetName 來建立元件。

開發使用

元件化

Athena中實現元件化主要是分為兩種,一是針對純HTML模板,通過擴充套件模板引擎方法實現,提供了元件化API widget.load,它可以方法接收三個引數,第一個引數是widget的名稱,後面兩個引數是可選引數,第二個是向widget傳遞的一些引數,第三個是widget所屬的模組,如果是本模組,可以不傳例如

<%= widget.load('user') %>
<%=
	widget.load('user', {
		param: 'test'
	})
%>
<%= widget.load('user', null, 'gb') %>

通過模板引擎編譯,執行widget.load方法,可以實現載入模板,記錄依賴關係的目的。

二是針對不同語言的後端模板,通過實現各自的元件化框架來進行元件的載入,例如 PHP 下使用 <?= $widget->load('user', NULL, 'gb') ?> 來進行元件載入,再通過程式碼掃描得出元件依賴關係。

Athena中的API

Athena針對模板提供了一系列的API來擴充套件豐富的功能,例如前面提到的 <%= widget.load() %> 來實現元件化。

同時Athena中還提供了其他API:

<%= getCSS() %><%= getJS() %> 用來引用CSS/JS檔案,傳入檔名和模組名;

<%= uri() %> 提供了資源定位功能,可以在模板中標記資源,編譯過程中會進行替換,而且在JS中也有資源定位API __uri()

<%= inline() %> 提供了內聯資源的功能,傳入檔名和模組名,可以在模板中內聯任意資源,例如圖片以及JS指令碼;而且 inline 也可以內聯一段網路資源,例如線上的JS檔案,同樣的在JS中也有內聯資源API __inline()

雪碧圖示識 ?__sprite ,在CSS中引用圖片最後加上標識 ?__sprite 可以自動生成自定義名稱雪碧圖,同時支援自定義生成多張雪碧圖,只需要要標識後面帶上一個檔名,就可以生成一張以這個檔名來命名的雪碧圖,例如 ?__sprite=icons ,這樣所有帶同樣標識的圖片就會生成一張以 icons為檔名的雪碧圖。

編譯預覽

編譯任務

在編寫完專案,就可以通過命令來對專案進行編譯了,執行編譯命令 $ ath build,會針對指定模組執行已經定義好的編譯任務,根據專案需求,目前編譯都是基於業務模組去編譯,編譯任務的最小執行單位是頁面,每次編譯都會執行以下編譯列表

本地預覽

執行預覽命令 $ath serve 會執行精簡版編譯任務來編譯專案,編譯完專案後會生成一份站點地圖,隨後開啟一個本地伺服器來預覽專案,使用這個命令可以很方便地進行開發,在預覽時會同時watch目錄和檔案的改動,並且提供了livereload功能,我們可以在預覽時任意修改檔案,都將實時地反映到頁面中,同時可以新建另一個視窗執行新增元件和頁面的操作,讓整個開發過程非常順暢,我們只需關注開發本身就好,不需要再關注其他事。

執行完編譯任務後,預設自動開啟瀏覽器,預覽站點地圖

Mock server

在進行專案預覽的同時,Athena同時提供了mock data的服務,我們可以配置相應的路由,以及路由介面對應的假資料,所有的介面請求會傳送到mock server上,在mock server中可以選擇將請求代理到假資料平臺還是代理到線上介面,這樣就可以脫離後端進行開發聯調了,以此實現資料的前後端分離。

專案部署

在開發預覽完後,通過命令 $ ath publish 就可以將專案釋出到配置好的測試機上,釋出同時支援ftp、sftp以及http形式。

元件維護

我們通過元件化的手段已經將我們的專案進行元件化了,這樣我們經過業務迭代積累,產出很多業務公共元件,但在以往的專案開發中,公共元件的更新與維護一直很受限制,而且有哪些公共元件、公共元件長什麼樣子,只能依靠口口相傳或者手工維護的文件。所以在Athena中我們加入了元件平臺,在元件平臺上統一展示各個業務的公共元件,而得益於本地工具,元件平臺不需要人工干預維護,我們可以在本地通過命令 $ ath widget-publish [widgetName] 命令來釋出一個元件到元件平臺,這樣其他人就可以立即在元件平臺進行元件的預覽,而其他人若想使用該元件時,在本地通過命令ath widget-load [widgetId] 就可以下載該元件到自己的模組目錄下了。

這樣元件的維護更加自動化,公共元件的使用也更加方便了。

元件釋出

元件下載

自身優化

為了提升開發效率,Athena做了一些優化操作

精簡專案預覽時的任務

在開發時進行專案預覽時,會執行精簡版的編譯任務,剔除了類似檔案壓縮、雪碧圖生成、模板抽離處理等耗時的操作,只保留核心、必須的編譯任務,這樣可以極大地減少編譯時間,提升開發的效率。

預覽時監聽細化

在開發進行預覽時,會對所有檔案的改動進行監聽,而針對每一類檔案都有非常細化的操作,當檔案改動時只會執行改檔案所需要的編譯任務,而不會進行整體編譯,這樣可以很好地提升開發效率。例如改動某一元件的CSS檔案,則只會針對該檔案執行一些相關的CSS操作。

同時得益於所有檔案依賴關係的記錄,在監聽時會根據依賴關係進行檔案編譯,例如某sass檔案中引入了另一個sass庫檔案,修改這個sass庫檔案的時候,會根據引用關係表同時更新到所有引用到這個sass檔案的檔案,這樣專案檔案更新及時,讓開發流程更加流暢。

編譯快取

在圖片壓縮和sass編譯時,開啟檔案快取,將已經編譯過且沒有改動的檔案過濾掉,不再編譯,大幅提升編譯速度。

釋出快取

設定釋出過濾,根據檔案md5過濾掉已經發布過的檔案,提升釋出速度。

技術選型

Athena本地工具早期技術選型是 Yeoman + Gulp 的方式,但後來由於安裝、更新非常麻煩,命令太長很難打的原因,我們改成了自己開發一個全域性安裝包的方式,編譯核心使用的還是 Gulp 的 vinyl-fs 來實現檔案流處理,通過 ES6 Promise 來進行編譯流程控制,最小以頁面為單位,經過一系列編譯任務,最後產出編譯好的檔案。

管理平臺

效能優化一直是前端工程師探索的課題,很多時候就是資源的分配問題,也就是資源管理。為了更好地配合本地構建工具來管理資源,我們搭建了管理平臺。我們來看下,結合本地構建工具和管理平臺,工作流程變成了怎樣?

工作流程

  1. 在管理平臺上建立專案,輸入專案名稱和預覽機,以及選擇相應的模板等;
  2. 在終端執行ath app指令,工具會優先拉取遠端伺服器的專案資訊來初始化專案,如果沒有獲取到相關資訊,就會在本地生成專案,並將專案資訊上報給伺服器;
  3. 專案初始化後,就可以建立模組、頁面、元件了;
  4. 在編碼過程中,可通過ath server預覽頁面;
  5. 在本地通過後,可執行ath publish將程式碼釋出到開發機或者預覽機。

在上面的publish指令中,工具會掃描所有檔案,執行程式碼檢查,掃描頁面檔案,獲取元件依賴關係,根據元件依賴關係進行檔案合併,然後會進行樣式處理、js處理以及圖片的處理,根據配置是否進行md5重新命名檔案,組裝html,插入樣式、js和圖片,最後將編譯好的檔案釋出到相應的機器。在整個過程裡面,會生成資源關係依賴表,最終會將資源關係表及編譯後的檔案上傳至管理平臺。

除此之外,每個指令的操作都會上報給管理平臺。管理平臺收到資料後,會對資料進行處理,最終可以在平臺上看到專案相關的資訊。

整體工作流程圖如下:

從上面的工作流程中,我們可以看到,管理平臺需要有資料統計、資源管理以及專案管理的功能。整體架構圖如下:

資料統計

資料統計包含專案操作日誌,主要是用於統計團隊每個成員具體的操作,方便專案成員檢視專案程式碼變更;另一部份是統計樣式表、指令碼以及圖片的壓縮資料,用於顯示工具給我們專案帶來的提升。

以下是操作日誌統計:

資源管理

資源管理是管理平臺的核心,主要分為4個部分:模組展示、依賴關係、元件預覽和許可權控制。這部分功能主要通過本地構建工具提供的資源關係表來完成。

模組展示

模組展示,用於記錄專案具體包含哪些模組以及模組具體的資訊。在平常開發中,我們的專案會分為許多模組,不同的模組有不同的人來開發和維護。當專案越大的時候,可以通過管理平臺清晰地看到模組具體的資訊。

依賴關係

依賴關係,主要是html、css、js和圖片相互之間的關係。通過分析資源關係依賴表,可以獲取到各個資源被引用的情況以及線上版本的情況。當線上環境採用md5來做資源管理時,我們不是很清晰地知道靜態資源對應線上哪個版本的資源,而有了這個依賴關係表,當出現問題時,我們可以更快地定位到具體的資源。

元件管理

我們採用元件來拼湊頁面,當專案越大時,元件越多,那麼如何管理元件成為了一個棘手的問題。比如說,有一些比較老的冗餘元件,我們不確定是否為其他頁面所引用,那麼就不能愉快地刪除它。有了元件管理,可以清晰地知道元件的被呼叫情況,就可以對元件做相應的操作。

元件管理,結合元件平臺來使用,在管理平臺上引用元件地址預覽元件,同時可以獲取到元件被引用以及引用資源(如css、js、圖片)的相關情況。

我們的元件分為兩種,一類是通過ath w自動建立的,通過ath pu提交到管理平臺的,在管理平臺上進行元件的相關分析和編譯,得到元件的資訊,這類元件主要是跟業務繫結的;另一類是通過ath widget-publish提交到元件平臺的,由元件平臺進行相關處理,這類元件是通用元件,與業務無關,用於展示給開發以及相關業務方看的。

在元件平臺上可以預覽與編輯相關的元件,通過與設計師約定相關的設計規範來促使元件達到儘可能地複用,進而減少設計師的工作量,提升我們的工作效率。

元件提交到元件平臺

通過ath widget-publish指令將元件提交到元件平臺,元件平臺會對元件原始碼進行編譯,將元件名稱md5、元件歸類以及元件版本記錄等等。

從元件平臺上下載元件

通過ath widget-load指令將元件下載到本地,當本地構建工具向元件平臺發起請求時,會帶上元件名稱,元件平臺會將原始碼進行編譯,將元件名稱重新命名,並且相應地替換原始碼中的元件名稱,同時記錄元件的被引用記錄。

許可權控制

許可權控制,專案中存在公共元件模組,公共元件比較穩定,比如說輪播元件、選項卡元件等等,這部分程式碼一般比較少變動,可由少部分人來更新和維護,所以加入了許可權控制機制,保證公共元件的穩定性。

專案管理

我們在使用本地構建工具時,需要配置多個引數,比如主機資訊、選擇模版等,在命令列環境下有些不直觀。為了簡化這個操作,管理平臺提供了專案建立的功能,同時提供了模版建立的功能。

在專案資訊、模組資訊以及元件資訊發生變更的時候,為了第一時間能夠通知專案成員更新,加入了訊息通知的功能,目前通過傳送郵件的方式,後期可以加入微信提醒的功能。

技術選型

管理平臺前端採用React+Redux的方式,後端採用Express+MongoDB,整體技術選型如下:

假資料服務

存在的問題

在平常的開發中,經常需要前後端聯調,但是在專案開始之初,很多介面並沒有提供,在以前的開發模式下,需要等待後端提供介面或者自己先定義介面,前端開發的進度可能會受影響。

Mock資料平臺

為了不影響前端開發的進度,我們搭建了Mock資料平臺,通過與後端協商資料格式,自定義資料介面,這樣子就可以做到前後端分離,讓前端獨立於後端進行開發。

Mock資料平臺基於mockjs搭建而成,通過簡單的mock語法來生成資料。

Mock資料平臺目前有如下功能:

  1. 建立模擬資料,使之符合各種場景;
  2. 生成json資料介面,支援CORS以及jsonp。

寫在最後

本次分享首先講述了我們在業務膨脹、人員不斷增加的背景下遇到的專案開發上的問題,並提出了我們自己對於這些問題思考總結後得出的解決方案與思路,最後產出適合我們團隊、業務的開發工具—— Athena。希望我們的方案能給大家帶來一定的借鑑作用。

相關文章