張雲龍,全民TV 研發負責人,曾任阿里巴巴移動事業羣主任工程師,百度高級研發工程師。php
本文轉載自:前端工程——基礎篇 · Issue #10 · fouber/blogcss
喂喂喂,那個切圖的,把頁面寫好就發給研發工程師套模板吧。
你好,切圖仔。前端
不知道你的團隊如何定義前端開發,據我所知,時至今日仍然有不少團隊會把前端開發歸類爲產品或者設計崗位,雖然身份之爭多少有些無謂,但我對這種偏見仍是心存芥蒂,醞釀了許久,決定寫一個系列的文章,試着從工程的角度系統的介紹一下我對前端,尤爲是Web前端的理解。jquery
只要咱們還把本身的工做看做爲一項軟件開發活動,那麼我相信讀過下面的內容你也必定會有所共鳴。git
現現在前端可謂一應俱全,產品形態五花八門,涉獵極廣,什麼高大上的基礎庫/框架,拽炫酷的宣傳頁面,還有屌炸天的小遊戲……不過這些一兩個文件的小項目並不是是前端技術的主要應用場景,更具商業價值的則是複雜的Web應用,它們功能完善,界面繁多,爲用戶提供了完整的產品體驗,多是新聞聚合網站,多是在線購物平臺,多是社交網絡,多是金融信貸應用,多是音樂互動社區,也多是視頻上傳與分享平臺……github
從本質上講,全部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應用時,前端將面臨更多的工程問題,好比:
擴展閱讀:大公司裏怎樣開發和部署前端代碼?
這些無疑是一系列嚴肅的系統工程問題。
前面講的三個階段雖然相比曾經「茹毛飲血」的時代進步很多,但用於支撐第四階段的多人合做開發以及精細的性能優化彷佛還欠缺點什麼。
到底,缺什麼呢?
讀過《人月神話》的人應該都據說過,軟件工程沒有銀彈。沒錯,前端開發一樣沒有銀彈,但是如今是連™鉛彈都沒有的年月!(剛有了BB彈,摔)
前端從來以「簡單」著稱,在前端開發者羣體中,小而美的價值觀佔據着主要的話語權,甚至成爲了某種信仰,想與其餘人交流一下工程方面的心得,獲得的迴應每每都是兩個字:過重。
重你妹!你的腦容量只有4K嗎?
工程方案其實也能夠小而美!只不過它的小而美不是指代碼量,而是指「規則」。找到問題的根源,用最少最簡單明瞭的規則制定出最容易遵照最容易理解的開發規範或工具,以提高開發效率和工程質量,這一樣是小而美的典範!
2011年我有幸參與到 FIS 項目中,與百度衆多大中型項目的前端研發團隊共同合做,不斷探索實踐前端開發的工程化解決方案,13年離開百度去往UC,面對徹底不一樣的產品形態,不一樣的業務場景,不一樣的適配終端,甚至不一樣的網絡環境,過往的方法論仍然可以快速落地,爲多個團隊的不一樣業務場景量身定製出合理的前端解決方案。
這些經歷讓我明悟了一個道理:
進入第四階段,咱們只需作好兩件事就能大幅提高前端開發效率,而且兼顧運行性能,那就是——組件化開發與資源管理。
分治的確是很是重要的工程優化手段。在我看來,前端做爲一種GUI軟件,光有JS/CSS的模塊化還不夠,對於UI組件的分治也有着一樣迫切的需求:
如上圖,這是我所信仰的前端組件化開發理念,簡單解讀一下:
其中第二項描述的就近維護原則,是我以爲最具工程價值的地方,它爲前端開發提供了很好的分治策略,每一個開發者都將清楚的知道,本身所開發維護的功能單元,其代碼必然存在於對應的組件目錄中,在那個目錄下能找到有關這個功能單元的全部內部邏輯,樣式也好,JS也好,頁面結構也好,都在那裏。
組件化開發具備較高的通用性,不管是前端渲染的單頁面應用,仍是後端模板渲染的多頁面應用,組件化開發的概念都能適用。組件HTML部分根據業務選型的不一樣,能夠是靜態的HTML文件,能夠是前端模板,也能夠是後端模板:
不一樣的技術選型決定了不一樣的組件封裝和調用策略。
基於這樣的工程理念,咱們很容易將系統以獨立的組件爲單元進行分工劃分:
因爲系統功能被分治到獨立的模塊或組件中,粒度比較精細,組織形式鬆散,開發者之間不會產生開發時序的依賴,大幅提高並行的開發效率,理論上容許隨時加入新成員認領組件開發或維護工做,也更容易支持多個團隊共同維護一個大型站點的開發。
結合前面提到的模塊化開發,整個前端項目能夠劃分爲這麼幾種開發概念:
以上5種開發概念以相對較少的規則組成了前端開發的基本工程結構,基於這些理念,我眼中的前端開發就成了這個樣子:
整個Web應用由頁面組成
頁面由組件組成
一個組件一個目錄,資源就近維護
組件可組合,
組件的JS可依賴其餘JS模塊,
CSS可依賴其餘CSS單元
綜合上面的描述,對於通常中小規模的項目,大體能夠規劃出這樣的源碼目錄結構:
若是項目規模較大,涉及多個團隊協做,還能夠將具備相關業務功能的頁面組織在一塊兒,造成一個子系統,進一步將整個站點拆分出多個子系統來分配給不一樣團隊維護,針對這種狀況後面我會單開文章詳細介紹。
以上架構設計歷經許多不一樣公司不一樣業務場景的前端團隊驗證,收穫了不錯的口碑,是行之有效的前端工程分治方案。
吐槽:我本人很是反對某些前端團隊將前端開發劃分爲「JS開發」和「頁面重構」兩種崗位,更傾向於組件粒度的開發理念,對GUI軟件開發的分工規劃應該以功能爲單位,而不是開發語言;對開發者的技術要求也應該是掌握完整的端內技術。
第二件事:「智能」靜態資源管理
上面提到的模塊化/組件化開發,僅僅描述了一種開發理念,也能夠認爲是一種開發規範,假若你承認這規範,對它的分治策略產生了共鳴,那咱們就能夠繼續聊聊它的具體實現了。
很明顯,模塊化/組件化開發以後,咱們最終要解決的,就是模塊/組件加載的技術問題。然而前端與客戶端GUI軟件有一個很大的不一樣:
前端是一種遠程部署,運行時增量下載的GUI軟件
前端應用沒有安裝過程,其所需程序資源都部署在遠程服務器,用戶使用瀏覽器訪問不一樣的頁面來加載不一樣的資源,隨着頁面訪問的增長,漸進式的將整個程序下載到本地運行,「增量下載」是前端在工程上有別於客戶端GUI軟件的根本緣由。
上圖展現了一款界面繁多功能豐富的應用,若是採用Web實現,相信也是不小的體量,若是用戶第一次訪問頁面就強制其加載全站靜態資源再展現,相信會有不少用戶由於失去耐心而流失。根據「增量」的原則,咱們應該精心規劃每一個頁面的資源加載策略,使得用戶不管訪問哪一個頁面都能按需加載頁面所需資源,沒訪問過的無需加載,訪問過的能夠緩存複用,最終帶來流暢的應用體驗。
這正是Web應用「免安裝」的魅力所在。
由「增量」原則引伸出的前端優化技巧幾乎成爲了性能優化的核心:
這些優化方案無不圍繞着如何將增量原則作到極致而展開。
因此我以爲:
第四階段前端開發最迫切須要作好的就是在基礎架構中貫徹增量原則。
相信這種貫徹不會隨着時間的推移而改變,在可預見的將來,不管在HTTP1.x仍是HTTP2.0時代,不管在ES5亦或者ES6/7時代,不管是AMD/CommonJS/UMD亦或者ES6 module時代,不管端內技術如何變遷,咱們都有足夠充分的理由要作好前端程序資源的增量加載。
正如前面說到的,第三階段前端工程缺乏點什麼呢?我以爲是在其基礎架構中缺乏這樣一種「智能」的資源加載方案。沒有這樣的方案,很難將前端應用的規模發展到第四階段,很難實現落地前面介紹的那種組件化開發方案,也很難讓多方合做高效率的完成一項大型應用的開發,並保證其最終運行性能良好。在第四階段,咱們須要強大的工程化手段來管理」玩具般簡單「的前端開發。
在個人印象中,Facebook是這方面探索的偉大先驅之一,早在2010年的Velocity China大會上,來自Facebook的David Wei博士就爲業界展現了他們使人驚豔的靜態網頁資源管理和優化技術。
David Wei博士在當年的交流會上提到過一些關於Facebook的一些產品數據:
這是一個狀態爆炸的問題,將全部狀態乘起來,整個網站的資源組合方式會達到幾百萬種之多(去重以後統計大概有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.css" } }
而資源加載框架則提供一些資源引用的API,讓開發者根據id來引用資源,替代靜態的script/link標籤來收集、去重、按需加載資源。調用這些接口時,框架經過查表來查找資源的各項信息,並遞歸查找其依賴的資源的信息,而後咱們能夠在這個過程當中實現各類性能優化算法來「智能」加載資源。
根據業務場景的不一樣,加載框架能夠在瀏覽器中用JS實現,也能夠是後端模板引擎中用服務端語言實現,甚至兩者的組合,不一而足。
這種設計很快被驗證具備足夠的靈活性,可以完美支撐不一樣團隊不一樣技術規範下的性能優化需求,前面提到的按需加載、延遲加載、預加載、請求合併、文件指紋、CDN部署、Bigpipe、Quickling、BigRender、首屏CSS內嵌、HTTP 2.0服務端推送等等性能優化手段均可以很容易的在這種架構上實現,甚至能夠根據性能日誌自動進行優化(Facebook已實現)。
由於有了資源表,咱們能夠很方便的控制資源加載,經過各類手段在運行時計算頁面的資源使用狀況,從而得到最佳加載性能。不管是前端渲染的單頁面應用,仍是後端渲染的多頁面應用,這種方法都一樣適用。
此外,它還很巧妙的約束了構建工具的職責——只生成資源表。資源表是很是通用的數據結構,不管什麼業務場景,其業務代碼最終均可以被掃描爲相同結構的表數據,並標記資源間的依賴關係,有了表以後咱們只需根據不一樣的業務場景定製不一樣的資源加載框架就好了,今後完全告別一個團隊維護一套工具的時代!!!
恩,如你所見,雖然完全告別了一個團隊一套工具的時代,但彷佛又進入了一個團隊一套框架的時代。其實仍是有差異的,由於框架具備很大的靈活性,並且不那麼黑盒,採用框架實現資源管理相比構建更容易調試、定位和升級變動。
深耕靜態資源加載框架能夠帶來許多收益,並且有足夠的靈活性和健壯性面向將來的技術變革,這個咱們留做後話。
回顧一下前面提到過的前端工程三個階段:
如今補充上第四階段:
因爲先天缺陷,前端相比其餘軟件開發,在基礎架構上更加迫切的須要組件化開發和資源管理,而解決資源管理的方法其實一點也不復雜:
一個通用的資源表生成工具 + 基於表的資源加載框架
近幾年來各類你聽到過的各類資源加載優化策略大部分均可以在這樣一套基礎上實現,而這種優化對於業務來講是徹底透明的,不須要重構的性能優化——這不正是咱們一直所期盼的嗎?正如魏小亮博士所說:咱們能夠把優秀的人集中起來去優化加載。
如何選型技術、如何定製規範、如何分治系統、如何優化性能、如何加載資源,當你從切圖開始轉變爲思考這些問題的時候,我想說:
你好,工程師!