大規(guī)模的前端組件化與模塊化
本文根據(jù)Andrew Betts在QCon北京2014大會(huì)上的主題演講內(nèi)容整理而成。
Andrew Betts是英國金融時(shí)報(bào)實(shí)驗(yàn)室(FT Labs)的負(fù)責(zé)人,同時(shí)也是一位PHP和JavaScript程序員。他的團(tuán)隊(duì)致力于研發(fā)試驗(yàn)性質(zhì)的Web技術(shù)并發(fā)布相關(guān)產(chǎn)品——比如金融時(shí)報(bào)Web App. 在加入金融時(shí)報(bào)實(shí)驗(yàn)室之前,Andrew創(chuàng)建了Web咨詢公司Assanka,為諸如News International, The Economist Group and the FT這樣的客戶打造創(chuàng)新性的Web項(xiàng)目。
今天的話題是大規(guī)模的前端組件化與模塊化。首先,先介紹一下FT在研發(fā)方面面臨的挑戰(zhàn):
不同的服務(wù)如搜索、內(nèi)容、廣告、應(yīng)用,都是由不同的團(tuán)隊(duì)來開發(fā),團(tuán)隊(duì)之間的溝通較少。造成的結(jié)果就是,整個(gè)服務(wù)的靈活性和可維護(hù)性越來越差,系統(tǒng)變得越來越復(fù)雜之后,新人進(jìn)來的學(xué)習(xí)難度很大。隨著整個(gè)軟件開發(fā)生命周期變得越來越復(fù)雜,新功能的集成變得越來越難;更糟糕的是,隨著移動(dòng)設(shè)備的流行,研發(fā)團(tuán)隊(duì)不得不把同樣一套邏輯分別在桌面端和移動(dòng)端各自實(shí)現(xiàn)一次。這也是全世界的軟件研發(fā)團(tuán)隊(duì)面對的挑戰(zhàn)。
為了應(yīng)對這些問題,F(xiàn)T Labs開始推行幾條前端的開發(fā)理念,目前已經(jīng)獲得比較好的效果。
***條理念是:“活的”風(fēng)格指南。“活的”風(fēng)格指南也可以理解為代碼即文檔,文檔即示例。這里舉一個(gè)例子,比如Facebook的React項(xiàng)目。你去看React項(xiàng)目的介紹頁,這個(gè)頁面本身就是一個(gè)React的推薦實(shí)現(xiàn)。
當(dāng)然像React這樣的項(xiàng)目,說明頁面的實(shí)現(xiàn)是一個(gè)方面,此外還有另一個(gè)重點(diǎn):組件化的開發(fā)方式。正如React不是一個(gè)框架——它提倡的是無框架,因?yàn)槿魏慰蚣艿囊攵紩?huì)增加額外的復(fù)雜度和學(xué)習(xí)成本。組件(Web components)則不同,它一旦開發(fā)出來,就是一個(gè)隨時(shí)可以很方便的調(diào)用的功能;而且,不同的團(tuán)隊(duì)可以同時(shí)進(jìn)行不同組件的開發(fā)而互不干擾。Web組件是一個(gè)正在快速發(fā)展中的特性,目前瀏覽器對它的支持還不***,不過也就是1-2年的時(shí)間,現(xiàn)在應(yīng)該要為未來做準(zhǔn)備。對于Web開發(fā)而言,向前兼容要比向后兼容更加重要。
組件化的使用在我們處理歷史網(wǎng)站的過程中節(jié)省了大量的工作。FT有超過600個(gè)域名需要維護(hù),很多網(wǎng)頁從互聯(lián)網(wǎng)時(shí)代早期就開始運(yùn)作。對于這些遺留頁面,要全都重寫以適應(yīng)新的瀏覽器是代價(jià)高昂、不值得的,但你又要盡可能的讓它們能夠正常顯示。我們用組件來進(jìn)行局部替換以解決這個(gè)問題。比如某個(gè)老頁面上有一個(gè)圖庫展示,現(xiàn)在的瀏覽器不能顯示了,你就批量把這種老舊的圖庫展示代碼替換成新的圖庫組件代碼即可。
另外還有一點(diǎn)很重要,就是擁抱模塊化,避免在代碼中嵌入依賴關(guān)系。做開發(fā)這行兒一個(gè)很重要的覺悟就是:你要相信,你現(xiàn)在寫出來的這些代碼,等兩年之后,你自己都會(huì)不想去看它。模塊化會(huì)讓你的生命更簡單。
對于瀏覽器兼容性,正如剛才所說,我們的建議是跟著***的瀏覽器功能走。不過這里面也有一個(gè)分界點(diǎn),就是所謂core experience和primary experience的分界點(diǎn),并盡可能的將分界點(diǎn)向擴(kuò)大core experience的方向推進(jìn)。對于NoJavaScript的處理,我們的經(jīng)驗(yàn)是,基本上所有人的瀏覽器都會(huì)支持JS的,盡可以放心大膽的用。
說到這里,我要進(jìn)入今天的重頭了,那就是FT的Origami這個(gè)項(xiàng)目。Origami這個(gè)項(xiàng)目要做定義的話可以說是一套規(guī)范,是一組文檔化的***實(shí)踐,同時(shí)搭配Registry這套工具,以方便所有人以***實(shí)踐建立規(guī)范統(tǒng)一的服務(wù)和組件。
這套系統(tǒng)的構(gòu)成大體上包括一套closure compiler,browserfy(+debowerfy&brfs),commonjs,sass(用于做css模塊化),taskrunner(基于grunt),以及bower。系統(tǒng)在設(shè)計(jì)上遵循幾個(gè)原則:
- 編譯時(shí)納入所有依賴
- 去中心化、分布式,比如我們的git repo是分散的,沒有一個(gè)所謂的core或common的codebase
- 內(nèi)置命名和封裝的規(guī)則
對于上面提到的分界點(diǎn),Origami是這樣處理的:core的部分需要保證在用戶環(huán)境對JavaScript支持差或不支持的情況仍然能夠完成基本內(nèi)容的呈現(xiàn)、搜索引擎的抓取等。這個(gè)分界點(diǎn)在Origami當(dāng)中通過 if (querySelector in document) 來實(shí)現(xiàn)判定。
Registry作為工具,會(huì)做以下幾個(gè)事情:
- 掃描所有已知的git服務(wù)器
- 給版本標(biāo)簽建索引
- 給每個(gè)模塊的每個(gè)版本做build
- 把每個(gè)模塊的所有版本收集、整理到一個(gè)模塊頁面上
Registry可以說是我們Web服務(wù)的一個(gè)黃頁。我們把這個(gè)黃頁放在公開的互聯(lián)網(wǎng)上,這樣所有人都可以上去協(xié)作,每個(gè)人都可以查看每個(gè)組件的每個(gè)版本,它們的說明和相關(guān)文檔,實(shí)現(xiàn)的樣板等。如果有的功能還沒做完或者沒啟用,可以打上一個(gè)not implemented的flag。
大家可以在Registry上隨意查看我們的各個(gè)組件,比如header,footer,調(diào)色板,按鈕等。這些組件調(diào)用起來很簡單,以調(diào)色板為例,只需要
- <link rel="stylesheet" href="http://build.origami.ft.com/bundles/css?modules=o-colors@^2.3.8" />
- <script src="http://build.origami.ft.com/bundles/js?modules=o-colors@^2.3.8"'></script>
這樣的兩行代碼即可調(diào)用任意模塊的任意版本。
我們的build服務(wù)可以按需build不同模塊的任意組合,之后還進(jìn)行打包、壓縮、優(yōu)化等處理并通過CDN分發(fā)(經(jīng)過了GZIP處理)。調(diào)用的時(shí)候可以指定調(diào)用指定的版本,或者自動(dòng)調(diào)用***版。有了這套系統(tǒng)后,開發(fā)者創(chuàng)建原型變得非常簡單了。
下面我介紹一下我們遇到的一些零碎問題,以及我們是如何處理的。
首先,有關(guān)polyfill的加載,如何才能讓polyfill僅在需要的時(shí)候才加載?我們的處理辦法是在模塊的metadata中進(jìn)行聲明,哪些是required,哪些是optional,以modernizr測試名來進(jìn)行控制。
然后,如何在支持JS但是支持的不好的瀏覽器中顯示noscript當(dāng)中的內(nèi)容?我們的辦法是定義一個(gè)o-nojs-fallback類的div,通過類的visibility來控制呈現(xiàn)。
對于hover的處理,我們用了一個(gè)o-hoverable的組件來控制。
對于資源加載,為了確保每個(gè)組件都知道其他資源的URL地址,我們專門有一個(gè)o-asset組件。
***想說的是,將我們的這些工作公開在互聯(lián)網(wǎng)上,我們從中收獲了很多。