天天看點

前端工程——基礎篇

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

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

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

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

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

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

前端工程——基礎篇

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

曆史悠久的前端開發,始終像是放養的野孩子,原始如斯,不免讓人慨歎!如果你想學習前端可以來這個群,首先是291,中間是851,最後是189,裡面可以學習和交流,也有資料可以下載下傳。

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

第一階段:庫/架構選型

前端工程——基礎篇

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

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

前端工程——基礎篇

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

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

前端工程——基礎篇

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

很多人覺得子產品化開發的工程意義是複用,我不太認可這種看法,在我看來,子產品化開發的最大價值應該是分治,是分治,分治!(重說三)。  不管你将來是否要複用某段代碼,你都有充分的理由将其分治為一個子產品。

JS子產品化方案很多,AMD/CommonJS/UMD/ES6 Module等,對應的架構和工具也一大堆,說起來很煩,大家自行百度吧;CSS子產品化開發基本都是在less、sass、stylus等預處理器的import/mixin特性支援下實作的。(web前端學習交流Q群:291851189 禁止閑聊,非喜勿進!)

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

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

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

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

- 大體量:多功能、多頁面、多狀态、多系統; 

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

- 高性能:CDN部署、緩存控制、檔案指紋、緩存複用、請求合并、按需加載、同步/異步加載、移動端首屏CSS内嵌、HTTP 2.0服務端資源推送

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

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

到底,缺什麼呢?

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

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

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

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

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

這些經曆讓我明悟了一個道理:  進入第四階段,我們隻需做好兩件事就能大幅提升前端開發效率,并且兼顧運作性能,那就是——元件化開發與資源管理。

第一件事:元件化開發 

分治的确是非常重要的工程優化手段。在我看來,前端作為一種GUI軟體,光有JS/CSS的子產品化還不夠,對于UI元件的分治也有着同樣迫切的需求:

前端工程——基礎篇

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

- 頁面上的每個 獨立的 可視/可互動區域視為一個元件; 

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

- 由于元件具有獨立性,是以元件與元件之間可以 自由組合; 

- 頁面隻不過是元件的容器,負責組合元件形成功能完整的界面; 

- 當不需要某個元件,或者想要替換元件時,可以整個目錄删除/替換。

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

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

前端工程——基礎篇
不同的技術選型決定了不同的元件封裝和調用政策。

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

前端工程——基礎篇

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

結合前面提到的子產品化開發,整個前端項目可以劃分為這麼幾種開發概念:
前端工程——基礎篇

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

前端工程——基礎篇
前端工程——基礎篇
前端工程——基礎篇
前端工程——基礎篇

綜合上面的描述,對于一般中小規模的項目,大緻可以規劃出這樣的源碼目錄結構:

前端工程——基礎篇

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

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

吐槽:我本人非常反對某些前端團隊将前端開發劃分為“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中的一個靜态管理方案Celerit,以及相關的說明],看它的描述很像是Facebook靜态資源管理系統的一個mini版!

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

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

靜态資源管理系統 = 資源表 + 資源加載架構

多麼優雅的實作啊!

資源表是一份資料檔案(比如JSON),是項目中所有靜态資源(主要是JS和CSS)的建構資訊記錄,通過建構工具掃描項目源碼生成,是一種k-v結構的資料,以每個資源的id為key,記錄了資源的類别、部署路徑、依賴關系、打包合并等内容,比如:

而資源加載架構則提供一些資源引用的API,讓開發者根據id來引用資源,替代靜态的script/link标簽來收集、去重、按需加載資源。調用這些接口時,架構通過查表來查找資源的各項資訊,并遞歸查找其依賴的資源的資訊,然後我們可以在這個過程中實作各種性能優化算法來“智能”加載資源。

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

前端工程——基礎篇

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

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

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

前端工程——基礎篇
恩,如你所見,雖然徹底告别了一個團隊一套工具的時代,但似乎又進入了一個團隊一套架構的時代。其實還是有差别的,因為架構具有很大的靈活性,而且不那麼黑盒,采用架構實作資源管理相比建構更容易調試、定位和更新變更。

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

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

第二階段:簡單建構優化

第三階段:JS/CSS子產品化開發

現在補充上第四階段:

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

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

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

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

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

你好,工程師!

繼續閱讀