歡迎光臨
每天分享高質量文章

前端工程——基礎篇

作者:張雲龍(@前端農民工)

網址:https://github.com/fouber/blog/blob/master/201508/01.md#

喂喂喂,那個切圖的,把頁面寫好就發給研發工程師套模板吧。

你好,切圖仔。

不知道你的團隊如何定義前端開發,據我所知,時至今日仍然有很多團隊會把前端開發歸類為產品或者設計崗位,雖然身份之爭多少有些無謂,但我對這種偏見還是心存芥蒂,醞釀了許久,決定寫一個系列的文章,試著從工程的角度系統的介紹一下我對前端,尤其是Web前端的理解。

只要我們還把自己的工作看作為一項軟體開發活動,那麼我相信讀過下麵的內容你也一定會有所共鳴。

前端,是一種GUI軟體

現如今前端可謂包羅萬象,產品形態五花八門,涉獵極廣,什麼高大上的基礎庫/框架,拽炫酷的宣傳頁面,還有屌炸天的小游戲……不過這些一兩個檔案的小專案並非是前端技術的主要應用場景,更具商業價值的則是複雜的Web應用,它們功能完善,界面繁多,為用戶提供了完整的產品體驗,可能是新聞聚合網站,可能是在線購物平臺,可能是社交網絡,可能是金融信貸應用,可能是音樂互動社區,也可能是視頻上傳與分享平臺……

從本質上講,所有Web應用都是一種運行在網頁瀏覽器中的軟體,這些軟體的圖形用戶界面(Graphical User Interface,簡稱GUI)即為前端。

如此複雜的Web應用,動輒幾十上百人共同開發維護,其前端界面通常也頗具規模,工程量不亞於一般的傳統GUI軟體:

儘管Web應用的複雜程度與日俱增,用戶對其前端界面也提出了更高的要求,但時至今日仍然沒有多少前端開發者會從軟體工程的角度去思考前端開發,來助力團隊的開發效率,更有甚者還對前端保留著”如玩具般簡單“的刻板印象,日復一日,刀耕火種。

歷史悠久的前端開發,始終像是放養的野孩子,原始如斯,不免讓人慨嘆!

前端工程的三個階段

現在的前端開發倒也並非一無所有,回顧一下曾經經歷過或聽聞過的專案,為了提升其前端開發效率和運行性能,前端團隊的工程建設大致會經歷三個階段:

第一階段:庫/框架選型

前端工程建設的第一項任務就是根據專案特征進行技術選型。

基本上現在沒有人完全從0開始做網站,哪怕是政府專案用個jquery都很正常吧,React/Angularjs等框架橫空出世,解放了不少生產力,合理的技術選型可以為專案節省許多工程量這點毋庸置疑。

第二階段:簡單構建優化

選型之後基本上就可以開始敲碼了,不過光解決開發效率還不夠,必須要兼顧運行性能。前端工程進行到第二階段會選型一種構建工具,對代碼進行壓縮,校驗,之後再以頁面為單位進行簡單的資源合併。

前端開發工程化程度之低,常常出乎我的意料,我之前在百度工作時是沒有多少概念的,直到離開大公司的溫室,去到業界與更多的團隊交流才發現,能做到這個階段在業界來說已然超出平均水平,屬於“具備較高工程化程度”的團隊了,查看網上形形色色的網頁原始碼,能做到最基本的JS/CSS壓縮的Web應用都已跨入標準互聯網公司行列,不難理解為什麼很多前端團隊對於前端工程構建的認知還僅停留在“壓縮、校驗、合併”這種程度。

第三階段:JS/CSS模塊化開發

分而治之是軟體工程中的重要思想,是複雜系統開發和維護的基石,這點放在前端開發中同樣適用。在解決了基本開發效率運行效率問題之後,前端團隊開始思考維護效率,模塊化是目前前端最流行的分治手段。

很多人覺得模塊化開發的工程意義是復用,我不太認可這種看法,在我看來,模塊化開發的最大價值應該是分治,是分治,分治!(重說三)。

不管你將來是否要復用某段代碼,你都有充分的理由將其分治為一個模塊。

JS模塊化方案很多,AMD/CommonJS/UMD/ES6 Module等,對應的框架和工具也一大堆,說起來很煩,大家自行百度吧;CSS模塊化開發基本都是在less、sass、stylus等前處理器的import/mixin特性支持下實現的。

雖然這些技術由來已久,在如今這個“言必及React”的時代略顯落伍,但想想業界的絕大多數團隊的工程化落後程度,放眼望去,毫不誇張的說,能達到第三階段的前端團隊已屬於高端行列,基本具備了開發維護一般規模Web應用的能力。

然而,做到這些就夠了麽?Naive!

第四階段

前端是一種技術問題較少、工程問題較多的軟體開發領域。

當我們要開發一款完整的Web應用時,前端將面臨更多的工程問題,比如:

  • 大體量:多功能、多頁面、多狀態、多系統;

  • 大規模:多人甚至多團隊合作開發;

  • 高性能:CDN部署、快取控制、檔案指紋、快取復用、請求合併、按需加載、同步/異步加載、移動端首屏CSS內嵌、HTTP 2.0服務端資源推送。

這些無疑是一系列嚴肅的系統工程問題。

前面講的三個階段雖然相比曾經“茹毛飲血”的時代進步不少,但用於支撐第四階段的多人合作開發以及精細的性能優化似乎還欠缺點什麼。

到底,缺什麼呢?

沒有銀彈

讀過《人月神話》的人應該都聽說過,軟體工程 沒有銀彈。沒錯,前端開發同樣沒有銀彈,可是現在是連™鉛彈都沒有的年月!(剛有了BB彈,摔)

前端歷來以“簡單”著稱,在前端開發者群體中,小而美的價值觀占據著主要的話語權,甚至成為了某種信仰,想與其他人交流一下工程方面的心得,得到的回應往往都是兩個字:太重。

重你妹!你的腦容量只有4K嗎?

工程方案其實也可以小而美!只不過它的小而美不是指代碼量,而是指“規則”。找到問題的根源,用最少最簡單明瞭的規則制定出最容易遵守最容易理解的開發規範或工具,以提升開發效率和工程質量,這同樣是小而美的典範!

2011年我有幸參與到 FIS 專案中,與百度眾多大中型專案的前端研發團隊共同合作,不斷探索實踐前端開發的工程化解決方案,13年離開百度去往UC,面對完全不同的產品形態,不同的業務場景,不同的適配終端,甚至不同的網絡環境,過往的方法論仍然能夠快速落地,為多個團隊的不同業務場景量身定製出合理的前端解決方案。

這些經歷讓我明悟了一個道理:

進入第四階段,我們只需做好兩件事就能大幅提升前端開發效率,並且兼顧運行性能,那就是——組件化開發與資源管理。

第一件事:組件化開發

分治的確是非常重要的工程優化手段。在我看來,前端作為一種GUI軟體,光有JS/CSS的模塊化還不夠,對於UI組件的分治也有著同樣迫切的需求:

如上圖,這是我所信仰的前端組件化開發理念,簡單解讀一下:

  1. 頁面上的每個 獨立的 可視/可交互區域視為一個組件;

  2. 每個組件對應一個工程目錄,組件所需的各種資源都在這個目錄下就近維護;

  3. 由於組件具有獨立性,因此組件與組件之間可以 自由組合;

  4. 頁面只不過是組件的容器,負責組合組件形成功能完整的界面;

  5. 當不需要某個組件,或者想要替換組件時,可以整個目錄刪除/替換。

其中第二項描述的就近維護原則,是我覺得最具工程價值的地方,它為前端開發提供了很好的分治策略,每個開發者都將清楚的知道,自己所開發維護的功能單元,其代碼必然存在於對應的組件目錄中,在那個目錄下能找到有關這個功能單元的所有內部邏輯,樣式也好,JS也好,頁面結構也好,都在那裡。

組件化開發具有較高的通用性,無論是前端渲染的單頁面應用,還是後端模板渲染的多頁面應用,組件化開發的概念都能適用。組件HTML部分根據業務選型的不同,可以是靜態的HTML檔案,可以是前端模板,也可以是後端模板:

不同的技術選型決定了不同的組件封裝和呼叫策略。

基於這樣的工程理念,我們很容易將系統以獨立的組件為單元進行分工劃分:

由於系統功能被分治到獨立的模塊或組件中,粒度比較精細,組織形式鬆散,開發者之間不會產生開發時序的依賴,大幅提升並行的開發效率,理論上允許隨時加入新成員認領組件開發或維護工作,也更容易支持多個團隊共同維護一個大型站點的開發。

結合前面提到的模塊化開發,整個前端專案可以劃分為這麼幾種開發概念:

名稱 說明 舉例
JS模塊 獨立的演算法和資料單元 瀏覽器環境檢測(detect),網絡請求(ajax),應用配置(config),DOM操作(dom),工具函式(utils),以及組件里的JS單元
CSS模塊 獨立的功能性樣式單元 柵格系統(grid),字體圖標(icon-fonts),動畫樣式(animate),以及組件里的CSS單元
UI組件 獨立的可視/可交互功能單元 頁頭(essay-header),頁尾(footer),導航欄(nav),搜索框(search)
頁面 前端這種GUI軟體的界面狀態,是UI組件的容器 首頁(index),串列頁(list),用戶管理(user)
應用 整個專案或整個站點被稱之為應用,由多個頁面組成

以上5種開發概念以相對較少的規則組成了前端開發的基本工程結構,基於這些理念,我眼中的前端開發就成了這個樣子:

示意圖 描述
整個Web應用由頁面組成
頁面由組件組成
一個組件一個目錄,資源就近維護
組件可組合,
組件的JS可依賴其他JS模塊,
CSS可依賴其他CSS單元

綜合上面的描述,對於一般中小規模的專案,大致可以規划出這樣的原始碼目錄結構:

如果專案規模較大,涉及多個團隊協作,還可以將具有相關業務功能的頁面組織在一起,形成一個子系統,進一步將整個站點拆分出多個子系統來分配給不同團隊維護,針對這種情況後面我會單開文章詳細介紹。

以上架構設計歷經許多不同公司不同業務場景的前端團隊驗證,收穫了不錯的口碑,是行之有效的前端工程分治方案。

吐槽:我本人非常反對某些前端團隊將前端開發劃分為“JS開發”和“頁面重構”兩種崗位,更傾向於組件粒度的開發理念,對GUI軟體開發的分工規劃應該以功能為單位,而不是開發語言;對開發者的技術要求也應該是掌握完整的端內技術。

第二件事:“智慧”靜態資源管理

上面提到的模塊化/組件化開發,僅僅描述了一種開發理念,也可以認為是一種開發規範,倘若你認可這規範,對它的分治策略產生了共鳴,那我們就可以繼續聊聊它的具體實現了。

很明顯,模塊化/組件化開發之後,我們最終要解決的,就是模塊/組件加載的技術問題。然而前端與客戶端GUI軟體有一個很大的不同:

前端是一種遠程部署,運行時增量下載的GUI軟體

前端應用沒有安裝過程,其所需程式資源都部署在遠程服務器,用戶使用瀏覽器訪問不同的頁面來加載不同的資源,隨著頁面訪問的增加,漸進式的將整個程式下載到本地運行,“增量下載”是前端在工程上有別於客戶端GUI軟體的根本原因。

上圖展示了一款界面繁多功能豐富的應用,如果採用Web實現,相信也是不小的體量,如果用戶第一次訪問頁面就強制其加載全站靜態資源再展示,相信會有很多用戶因為失去耐心而流失。根據“增量”的原則,我們應該精心規劃每個頁面的資源加載策略,使得用戶無論訪問哪個頁面都能按需加載頁面所需資源,沒訪問過的無需加載,訪問過的可以快取復用,最終帶來流暢的應用體驗。

這正是Web應用“免安裝”的魅力所在。

由“增量”原則引申出的前端優化技巧幾乎成為了性能優化的核心,有加載相關的按需加載、延遲加載、預加載、請求合併等策略;有快取相關的瀏覽器快取利用,快取更新、快取共享、非改寫式發佈等方案;還有複雜的BigRender、BigPipe、Quickling、PageCache等技術。這些優化方案無不圍繞著如何將增量原則做到極致而展開。

所以我覺得:

第四階段前端開發最迫切需要做好的就是在基礎架構中貫徹增量原則。

相信這種貫徹不會隨著時間的推移而改變,在可預見的未來,無論在HTTP1.x還是HTTP2.0時代,無論在ES5亦或者ES6/7時代,無論是AMD/CommonJS/UMD亦或者ES6 module時代,無論端內技術如何變遷,我們都有足夠充分的理由要做好前端程式資源的增量加載。

正如前面說到的,第三階段前端工程缺少點什麼呢?我覺得是在其基礎架構中缺少這樣一種“智慧”的資源加載方案。沒有這樣的方案,很難將前端應用的規模發展到第四階段,很難實現落地前面介紹的那種組件化開發方案,也很難讓多方合作高效率的完成一項大型應用的開發,並保證其最終運行性能良好。在第四階段,我們需要強大的工程化手段來管理”玩具般簡單“的前端開發。

在我的印象中,Facebook是這方面探索的偉大先驅之一,早在2010年的Velocity China大會上,來自Facebook的David Wei博士就為業界展示了他們令人驚艷的靜態網頁資源管理和優化技術。

David Wei博士在當年的交流會上提到過一些關於Facebook的一些產品資料:

  • Facebook整站有10000+個靜態資源;

  • 每個靜態資源都有可能被翻譯成超過100種語言版本;

  • 每種資源又會針對瀏覽器生成3種不同的版本;

  • 要針對不同帶寬的用戶做5種不同的打包方法;

  • 有3、4個不同的用戶組,用於小批次體驗新的產品功能;

  • 還要考慮不同的送達方法,可以直接送達,或者通過iframe的方式提升資源並行加載的速度;

  • 靜態資源的壓縮和非壓縮狀態可切換,用於除錯和定位線上問題

這是一個狀態爆炸的問題,將所有狀態乘起來,整個網站的資源組合方式會達到幾百萬種之多(去重之後統計大概有300萬種組合方式)。支撐這麼大規模前端專案運行的底層架構正是魏博士在那次演講中分享的Static Resource Management System(靜態資源管理系統),用以解決Facebook專案中有關前端工程的3D問題(Development,Deployment,Debugging)。

那段時間 FIS 專案正好遇到瓶頸,當時的FIS還是一個用php寫的task-based構建工具,那時候對於前端工程的認知度很低,覺得前端構建不就是幾個壓縮優化校驗打包任務的組合嗎,寫好流程調度,就針對不同需求寫插件唄,看似非常簡單。但當我們支撐越來越多的業務團隊,接觸到各種不同的業務場景時,我們深刻的感受到task-based工具的粗糙,團隊每天疲於根據各種業務場景編寫各種打包插件,構建邏輯異常複雜,隱隱看到不可控的跡象。

我們很快意識到把基礎架構放到構建工具中實現是一件很愚蠢的事,試圖依靠構建工具實現各種優化策略使得構建變成了一個巨大的黑盒,一旦發生問題,定位起來非常困難,而且每種業務場景都有不同的優化需求,構建工具只能通過靜態分析來優化加載,具有很大的局限性,單頁面/多頁面/PC端/移動端/前端渲染/後端渲染/多語言/多皮膚/高級優化等等資源加載問題,總不能給每個都寫一套工具吧,更何況這些問題彼此之間還可以有多種組合應用,工具根本寫不過來。

Facebook的做法無疑為我們亮起了一盞明燈,不過可惜它並不開源(不是技術封鎖,而是這個系統依賴FB體系中的其他方面,通用性不強,開源意義不大),我們只能嘗試挖掘相關信息,網上對它的完整介紹還是非常非常少,分析facebook的前端代碼也沒有太多收穫,後來無意中發現了facebook使用的專案管理工具phabricator中的一個靜態管理方案Celerity,以及相關的說明,看它的描述很像是Facebook靜態資源管理系統的一個mini版!

簡單看過整個系統之後發現原理並不複雜(小而美的典範),它是通過一個小工具掃描所有靜態資源,生成一張資源表,然後有一個PHP實現的資源管理框架(Celerity)提供了資源加載接口,替代了傳統的script/link等靜態的資源加載標簽,最終通過查表來加載資源。

雖然沒有真正看過FB的那套系統,但眼前的這個小小的框架給了當時的我們足夠多的啟示:

靜態資源管理系統 = 資源表 + 資源加載框架

多麼優雅的實現啊!

資源表是一份資料檔案(比如JSON),是專案中所有靜態資源(主要是JS和CSS)的構建信息記錄,通過構建工具掃描專案原始碼生成,是一種k-v結構的資料,以每個資源的id為key,記錄了資源的類別、部署路徑、依賴關係、打包合併等內容,比如:

{

“a.js”: {

“url”: “/static/js/a.5f100fa.js”,

“dep”: [ “b.js”, “a.css” ]

},

“a.css”: {

“url”: “/static/css/a.63cf374.css”,

“dep”: [ “button.css” ]

},

“b.js”: {

“url”: “/static/js/b.97193bf.js”

},

“button.css”: {

“url”: “/static/css/button.de33108.js”

}

}

而資源加載框架則提供一些資源取用的API,讓開發者根據id來取用資源,替代靜態的script/link標簽來收集、去重、按需加載資源。呼叫這些接口時,框架通過查表來查找資源的各項信息,並遞迴查找其依賴的資源的信息,然後我們可以在這個過程中實現各種性能優化演算法來“智慧”加載資源。

根據業務場景的不同,加載框架可以在瀏覽器中用JS實現,也可以是後端模板引擎中用服務端語言實現,甚至二者的組合,不一而足。

這種設計很快被驗證具有足夠的靈活性,能夠完美支撐不同團隊不同技術規範下的性能優化需求,前面提到的按需加載、延遲加載、預加載、請求合併、檔案指紋、CDN部署、Bigpipe、Quickling、BigRender、首屏CSS內嵌、HTTP 2.0服務端推送等等性能優化手段都可以很容易的在這種架構上實現,甚至可以根據性能日誌自動進行優化(Facebook已實現)。

因為有了資源表,我們可以很方便的控制資源加載,通過各種手段在運行時計算頁面的資源使用情況,從而獲得最佳加載性能。無論是前端渲染的單頁面應用,還是後端渲染的多頁面應用,這種方法都同樣適用。

此外,它還很巧妙的約束了構建工具的職責——只生成資源表。資源表是非常通用的資料結構,無論什麼業務場景,其業務代碼最終都可以被掃描為相同結構的表資料,並標記資源間的依賴關係,有了表之後我們只需根據不同的業務場景定製不同的資源加載框架就行了,從此徹底告別一個團隊維護一套工具的時代!!!

恩,如你所見,雖然徹底告別了一個團隊一套工具的時代,但似乎又進入了一個團隊一套框架的時代。其實還是有差別的,因為框架具有很大的靈活性,而且不那麼黑盒,採用框架實現資源管理相比構建更容易除錯、定位和升級變更。

深耕靜態資源加載框架可以帶來許多收益,而且有足夠的靈活性和健壯性面向未來的技術變革,這個我們留作後話。

總結

回顧一下前面提到過的前端工程三個階段:

  • 第一階段:庫/框架選型

  • 第二階段:簡單構建優化

  • 第三階段:JS/CSS模塊化開發

現在補充上第四階段:

  • 第四階段:組件化開發與資源管理

由於先天缺陷,前端相比其他軟體開發,在基礎架構上更加迫切的需要組件化開發和資源管理,而解決資源管理的方法其實一點也不複雜:

一個通用的資源表生成工具 + 基於表的資源加載框架

近幾年來各種你聽到過的各種資源加載優化策略大部分都可以在這樣一套基礎上實現,而這種優化對於業務來說是完全透明的,不需要重構的性能優化——這不正是我們一直所期盼的嗎?正如魏小亮博士所說:我們可以把優秀的人集中起來去優化加載。

如何選型技術、如何定製規範、如何分治系統、如何優化性能、如何加載資源,當你從切圖開始轉變為思考這些問題的時候,我想說:

你好,工程師!

前端工程其實是一個很大的話題,開發僅是其中的一部分。

赞(0)

分享創造快樂