前端開發模式:被動編譯和主動編譯

發表於2015-10-13

最近,梳理了一下公司的前端開發體系,準備給已經工作一年多的原有體系引入一些新的開發模式,其實也不算新了,只是對於我們一直採用的模式來說,是兩個完全不同的方向,以前,我崇尚簡單,一直按照簡單易用的理念構建了公司現有的前端開發體系,現在隨著人數的增多和業務的複雜度提升,感覺是時候引入一些差異化的開發體系了。

說來說去有點繞的慌,上面一段中說的 傳統的開發模式,在團隊內部我們叫做 被動編譯,而新的開發方式,在團隊內部叫做 主動編譯。

被動編譯

可能很多人對於被動編譯都不是很熟悉,因為這種開發方式現在並不流行,不過貌似也從來沒流行過。

所謂 被動編譯。核心理念是:所有原始碼都是在被訪問的時候才會去編譯。

這裡的“原始碼”,指的是 jade,less,js,coffee甚至jsx之類的預處理語言程式碼,在我們公司前端專案中,約定了使用這些預處理語言來組織程式碼,他們普遍的特點是 簡單而強大,而對於一個團隊來說,大家趨向於一種語言和模式是非常重要的,所以這裡我們不討論這些預處理的語法優劣,只介紹我們是如何運用它們的。

在大搜車,一個前端開發的開發和釋出流程是這樣的:寫jade,寫less,寫js —> 瀏覽器訪問jade對應的路徑 —> 瀏覽器顯示編譯後的html —> html中的css的內容其實是直接來自less的編譯 —> js之類的也經過了一個服務的處理 —> 但是看原始碼,裡面只有jade,less,jsx等。alt事實上,被動編譯的關鍵是 一個小型伺服器,我們在內部稱之為 ads,它事實上是一個帶有動態功能的靜態伺服器。前端寫的jade,less,js都會託管在這個伺服器的靜態目錄裡,然後瀏覽器的請求,每次都會經過這個ads伺服器,伺服器內部的邏輯會判斷當前請求的url應該對應到本地何種資源的哪個原始檔上去,然後讀取這個原始檔,在程式中直接呼叫編譯器,將編譯結果返回給瀏覽器,中間不生成中間檔案。

為什麼我們一直堅持 被動編譯 這種開發模式呢?
  1. 統一整個團隊的開發環境,甚至是前處理器的選型,甚至是程式碼的風格。

看上去有點剝奪自由發揮的嫌疑,但是對於程式設計師的問題來說,總是難以滿足各方的,其實統一帶來的一大問題就是不夠靈活,但是靈活帶來的問題也是不夠統一,然後不夠統一造成的問題會引起一些其他的問題,例如人和人之間的溝通成本,跨專案開發的痛苦,前人寫的程式碼難以維護,不同的專案配置不同的環境耗費掉的時間成本。 這個問題,我們都是從團隊角度出發,對個人的開發自有做了一部分犧牲,通過ads,我們把預處理的語言規範化了,把專案目錄規範化了,甚至把預處理的使用方式也規範化了(不嫩使用高階特性)。

  1. 開發人員不關心環境,不需要配置。

在經歷過五六年的開發經驗之後,很多開發中碰到的問題都給了我深刻影響,深知有些東西看起來很美好,但是放到現實中就是另外一回事,在開發中這類事情很多,例如模組化,解耦,重構,服務化這些事情,在某些方面,是很好的改變,但是做到某種程度,效果就開始適得其反,很多問題就開始放大化了,而這些問題往往很多開發都選擇性忽視或者壓根沒有考慮過。具體就不展開了。

其實ads的一大好處(也有可能是壞處)就是 無需配置。一個開發者入職後,需要做的事情,下載ads,啟動,然後clone我們的前端程式碼庫,找到資料夾,開始寫程式碼,ok,本地瀏覽器裡可以實時訪問了。不需要配置路徑,不需要配置需要打包編譯的檔案,不需要執行命令,不需要監聽檔案變化。

然後,繼續,開發完成後,要釋出測試了,怎麼操作呢,很簡單,把檔案都提交到git,然後等一會會,測試環境自動生效了。為什麼能做到這個效果呢?最重要的一點,ads的專案檔案不需要編譯,不管在本地還是測試還是線上,在你的電腦上在我的電腦上,他都是原始檔的狀態躺在你的硬碟裡,只有瀏覽器訪問的時候才會去編譯檔案。所以,釋出測試其實是個很簡單的操作,把檔案從你本地copy到測試環境的靜態目錄,而釋出線上其實是一樣的,把檔案從測試copy到線上目錄。彷彿他們是靜態檔案,而事實上他們是動態的預處理程式碼。alt

  1. 實現靈活的動態應用。

舉幾個例子

一個是combo,很常見的需求,我們的程式碼可以隨意combo,事實上支援任何檔案無限combo,當combo的是css的時候,其實是把所有less原始碼合併編譯的結果,而對於js,則是合併然後壓縮。

二是可以動態生成css,這個在多主題的專案中很有用。因為我們的css都是less寫成的,當在less中定義一個變數的時候,可以在url裡傳入這個變數的初始值,然後,整個主題就會變成另一個顏色。同理,可以傳入很多東西,就可以實現很多靈活性的功能。

alt

大家可能會問一些問題,其實我們自己也提出一些問題:
  1. 效能不會有問題麼?

這個問題其實很簡單,我們所有線上靜態資源都是經過cdn的,所以這個問題就不是問題了,一個檔案只有第一次被訪問的時候才會被動態編譯,之後結果就快取在cdn服務商那裡了,下一次請求就不會到我們的ads線上伺服器來。所以我們的線上服務其實承載的訪問還是很少的,也不會有效能問題。針對cdn更新還需要有一套時間戳重新整理的功能。

  1. 如何處理時間戳問題?

在我們的線上伺服器上,維護了一個時間戳檔案,每個檔案在經過釋出系統的時候,都會去這個檔案裡更新一下自己的時間戳為當前檔案的hash值。然後在ads記憶體中我們會定時更新這個時間戳檔案到記憶體,每次渲染jade的時候,都會帶入一個方法,這個方法套起來的資原始檔在渲染jade的時候會自動帶上他對應的時間戳,這樣jade裡的資源都可以自動更新時間戳了,其實開發人員無需關心這個事情,一切都是自動的。 對於其他引用靜態資源的服務,需要在他們服務中實現一個類似的維護的時間戳鍵值對,然後在渲染模板的時候自動渲染出時間戳。不過這種服務已經很少了,我們現在大多數系統都是前後端分離的,前端頁面都是放在ads託管的專案中,由jade編寫。

alt

其實被動編譯以及這套開發環境支撐大搜車的環境已經有1年之久,也算經得起考驗,目前專案中的程式碼還是很清晰的,也沒有出過比較大的問題,大家用的時間長了,基本忘了編譯那一套了,甚至把jade,less當成了自然而然的終端語言使用,已經到了不用不舒服斯基的狀態。

不過隨著前端技術的發展,專案中必然要引入更高階的編譯特性了,這時候被動編譯開始顯得有些力不從心了。

例如我司大規模使用的AngularJS,React的打包編譯,使用ES6的專案,需要引入webpack,browserify之類工具的專案。用被動編譯其實有很多限制,對於這些模式來說已經hold不住了,於是我們想,是時候引入主動編譯的模式了。

主動編譯

所謂主動編譯,其實就是在本地打包好,然後把結果檔案上傳到伺服器的開發方式。

主動編譯一般需要引入流程控制工具,例如grunt,gulp之類的,目前用gulp的比較多,而我們最終研究過之後也選用了gulp,一個是gulp據說效能和配置都比較簡單,二是之前公司有專案用grunt,後面的配置變得越來越亂,吃個教訓。

其實之前準備採用百度的fis的,因為一直吹的比較厲害嘛,我一直以為fis是一個所謂“工程化”水平比較高的工具,於是跟團隊說去研究下,然後仔細看了下他本身自帶的一些功能,發現都很基礎啊,其實ads中都有這些功能,而且可能更優。不過fis可以通過外掛實現很多擴充套件功能,不過對於一些常用的打包工具都搜不到封裝的外掛,還且外掛質量堪憂,實在是不宜採用作為生產工具。最後只能放棄了。

在我看來,fis還是比較基礎的,適合那些沒有自己的現成的開發流程管理工具的團隊,對於我們團隊來說,功能跟ads有所重疊,而且又不是一個路數。其實我們想著引入主動編譯,不是想顛覆ads,而是主要用來處理比較重的前端專案。把這些比較重的前端專案從之前的靜態專案中抽離出來,給他們賦予獨立的專案構建工具。而普通的頁面,裡面的靜態資源,仍然使用原有的開發方式。

最後,我們選擇了gulp,至於gulp之類的工具,這裡其實也不想詳細展開,文章很多。

不過在選用主動編譯的模式的時候,我們仍然很注重一些風險,因為在我看來,可以引入很多外掛,使開發變得更靈活,但是更不可控了。

所以我們初步做了一些約定:

  1. 有一個統一的示例專案,所有專案由此衍生。
  2. 只允許使用常用外掛,禁止使用一些奇怪的(例如把js先合併壓縮然後打進html裡的外掛)
  3. 示例專案中,針對不同場景,封裝成一個個小的子配置,如果要使用某功能,直接引入,最好不要自己寫配置,也不需要自己寫配置。
  4. 目錄規範,配置規範,都文件化,按照規範來。

其實這些東西都沒有太多技術含量的,沒做覺得玄乎,做了覺得也就那些東西。最重要的還是能夠解決實際問題,然後評估好可能帶來的風險。每個團隊可能都會有些差異,大家看看就好。

相關文章