[轉載]我們是如何做好前端工程化和靜態資源管理
隨著互聯網的發展,我們的業務也日益變得更加復雜且多樣化起來,前端工程師也不再只是做簡單的頁面開發這麽簡單,我們需要面對的十分復雜的系統性問題,例如,業務愈來愈復雜,我們要如何清晰地梳理;團隊人員愈來愈多,我們要如何更好地進行團隊協作;功能愈來愈多,我們要如何保證頁面的性能不至於下降,等等。所有的這些都可以歸結為如何提升開發體驗和性能問題。
提升開發體驗
我們主要從以下三個方面來提升我們的開發體驗。
規範化
當團隊人員不斷擴充時,我們需要制定統一的規範來對平時的開發工作做出一定約束和指導。統一的規範包括前端的代碼規範,根據規範定義好一套代碼檢查的規則,在代碼提交的時候進行檢查,讓開發人員知道自己的代碼情況。
同時,根據以往的開發經驗,我們制定了統一的項目框架,根據業務功能不同,將一個項目(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所屬的模塊,如果是本模塊,可以不傳例如
1 2 3 4 5 6 7 | <%= 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
來進行編譯流程控制,最小以頁面為單位,經過一系列編譯任務,最後產出編譯好的文件。
管理平臺
性能優化一直是前端工程師探索的課題,很多時候就是資源的分配問題,也就是資源管理。為了更好地配合本地構建工具來管理資源,我們搭建了管理平臺。我們來看下,結合本地構建工具和管理平臺,工作流程變成了怎樣?
工作流程
- 在管理平臺上創建項目,輸入項目名稱和預覽機,以及選擇相應的模板等;
- 在終端執行ath app指令,工具會優先拉取遠程服務器的項目信息來初始化項目,如果沒有獲取到相關信息,就會在本地生成項目,並將項目信息上報給服務器;
- 項目初始化後,就可以創建模塊、頁面、組件了;
- 在編碼過程中,可通過ath server預覽頁面;
- 在本地通過後,可執行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數據平臺目前有如下功能:
- 創建模擬數據,使之符合各種場景;
- 生成json數據接口,支持CORS以及jsonp。
寫在最後
本次分享首先講述了我們在業務膨脹、人員不斷增加的背景下遇到的項目開發上的問題,並提出了我們自己對於這些問題思考總結後得出的解決方案與思路,最後產出適合我們團隊、業務的開發工具—— Athena。希望我們的方案能給大家帶來一定的借鑒作用。
感謝您的閱讀,本文由 凹凸實驗室 版權所有。如若轉載,請註明出處:凹凸實驗室(https://aotu.io/notes/2016/07/19/A-little-exploration-of-front-end-engineering/)[轉載]我們是如何做好前端工程化和靜態資源管理