我已經想不到一個好的關於前端分享的主題了,於是聯想到最近想要做的一件事,就想到了這個標題。或許這是一個好的主題,又或許這不是一個好的主題。但是至少我可以Share一下我的經驗:

基於Mustache模板引擎的前後臺渲染。

基於PreRender方式的Angular。js應用的後臺渲染

服務端渲染的React

開始之前,我希望即使你們需要後臺渲染,你們也應該前後端分離!由後臺來提供API資料,前端用自己的後臺來渲染頁面。聽上去有點繞,簡單的來說就是不要把大量的業務邏輯放前臺來,只把顯示邏輯放在前臺上。這樣一來,即使有一天我們換了新的前端,如移動應用,那麼我們的後臺也是可用的。。

為什麼需要前後端分離?

這是一個很古老的話題,對於大公司來說就是部門大了,需要拆分。因此開始之前,先提一下“康威定律”:

Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations。

換成中文,即:設計系統的組織,其產生的設計和架構等價於組織間的溝通結構。上圖

單頁面應用後臺渲染的三次實踐

單頁面應用後臺渲染的三次實踐

這張圖可以解釋相當多的軟體開發過程中的問題,而我們知道軟體開發的主要問題是溝通問題。組織結構影響了我們的溝通結構,進而影響了我們的軟體系統結構。好吧,我承認可能離題有點遠。不過,我想說的是組織結構可能不允許我們做出一些好的系統架構。

如我們在《RePractise前端篇: 前端演進史》中提到的那樣:我們已經有了一個桌面版網頁,然後我們打造了一個APP。然而,總有些客戶想在手機上瀏覽但是又不想下APP,我們就需要一個移動版。為什麼會這樣?因為使用者已經被養成了這樣的習慣,大部分的網站提到了桌面版、移動版、APP。要維護這樣的三個不同的系統,對於大部分的業務公司來說成本太高了。

於是,大部分公司來說解決方案就是 後臺 + 大前端 (桌面前端、移動Web、手機APP)。Angular和React就是為了解決這樣的問題,而出現了不同的解決方案——基於Angular。js的混合應用框架Ionic、以及React Native。不過在當前,我對React Native的共用UI還是持觀望態度。有人可能會提到Vue和Weex,但是我覺得並沒有那麼好用。或許是因為我接觸React比較早,我覺得Vue的語法四不像。

在這樣的情形下,我們只需要幾個後臺開發人員和幾個前端開發人員就可以完成系統的設計了。這種前端開發人員就是最近幾年人們“最想要”的。

前後臺渲染同一模板

我接觸的第一個SPA應用是一個基於Spring MVC和Backbone的移動網站,但是它比一般的SPA應該要複雜——由於SEO的緣故,它需要支援後臺渲染。

當搜尋引擎透過URL訪問我們的網站的時候,我們就需要返回相應的HTML。這意味著我們需要在後臺有對應的模板引擎來支援,而由於SPA的性質又決定了,這需要使用一個純前端的模板引擎。因此,我們並不能使用兩個模板引擎來做這件事,維護兩套模板註定會是一件痛苦的事,並且當時還沒有React這種模板引擎在。不過,後來我們發現維護兩種不同的渲染方式也是一件痛苦的事。因此,我們就會有了類似於下圖的架構:

單頁面應用後臺渲染的三次實踐

單頁面應用後臺渲染的三次實踐

我們在後臺使用Spring MVC作為基礎架構、Mustache作為模板引擎,和使用JSP作為模板引擎相比沒有多大的區別——由Controller去獲取對應的Model,再渲染給使用者。多數時候搜尋引擎都是依據Sitemap來進行索引的,所以我們的後臺很容易就可以處理這些請求。同樣的當使用者訪問相應的頁面的時候,也返回同樣的頁面內容。當完成頁面渲染的時候,就交由Backbone來處理相應的邏輯了。換句話來說,從這時候它就變成了一個單頁面應用。

儘管這是一個三年年前開始的專案,但是在今天看來,這種做法仍然相應地有趣: 大部分的單頁面應用只有一個首頁,並由HTTP伺服器(如Nginx)、Web框架(如Express、Koa)對路由做一些處理,可以讓使用者透過特定地URL訪問特定地頁面。而我們需要保證所有的使用者訪問地都是真實的頁面,既然JavaScript沒有載入完,使用者也能看到完整的頁面。

在這個專案裡,最大的挑戰就是如何保證後臺渲染和前臺渲染的業務邏輯是一樣的。如當我們想要針對不同的產品顯示不同的內容時,我們就需要在JavaScript中賦予一些邏輯,我們還需要在Java在有同樣的邏輯。相比於在同一個程式碼裡有桌面版、移動版來說,邏輯有更加複雜的趨勢——因為在這種情況下,我們只需要維護兩個不同的模板即可。而在SPA的情況下,我們要維護兩套邏輯。後來,這個框架交由下文中的React與響應式設計重寫。

在今天你仍然可以使用這樣的方式來渲染,JDK 1。8自帶了嵌入式JavaScript引擎Nashorn,完成支援ECMAScript 5。1規範以及一些擴充套件。

PreRender方式

在我們重新設計系統的時候,曾經考慮過類似的做法。將我們的所有頁面渲染成靜態的HTML,然後用爬蟲抓取我們的所有頁面,再上傳到AWS即可。當時我們諮詢了其他小組的做法,其中有一個小組正是採用了這種PreRender的方式——在本地執行起一個Server,由PhantomJS來渲染頁面,再儲存為對應的HTML。

PreRender就是預先渲染好HTML,並針對於爬蟲返回特定的HTML。(PS:不過作為一個很有經驗的SEO開發人員,我一點不喜歡這種作法。要知道Google有時候會模擬成真實的使用者,不帶有爬蟲的那些引數和標誌,去訪問頁面。如果你返回給Google的兩個頁面差異太大——可能是你忘記更新了頻率,那麼Google可能就會認為你在作弊。)

單頁面應用後臺渲染的三次實踐

單頁面應用後臺渲染的三次實踐

對於一般使用者來說就不會返回後臺渲染的結果了:

單頁面應用後臺渲染的三次實踐

單頁面應用後臺渲染的三次實踐

和上面的第一種情況相比,這種作法可以大大減少伺服器地負擔,並且可以直接交由CDN就可以了。這時我們只需要考慮要渲染哪些頁面即可,對於資料量比較少的網站來說這是一個不錯的做法,但是多了就不一樣了。

對於我們來說,有兩個問題:一個是速度的問題,他們有上萬條資料就需要近一天左右的時間來生成(渲染時間長),而我們有上百萬條資料。二是資料的實時問題,我們的產品資料每天都會更新一次。

React

對於使用React的開發人員來說,要處理後臺渲染就是一種更簡單的事,畢竟React中提供了一個方法叫 renderToString()。我們所要做的就是用Express或者Koa對路由進行處理,然後返回對應的內容即可:

單頁面應用後臺渲染的三次實踐

單頁面應用後臺渲染的三次實踐

然後,剩下的事都可以交由React來解決,就是這麼簡單。

因為在這個時候我們在前後臺使用的都是JavaScript,我們可以在這個地方直接實現對資料庫的操作,就會出現我們在開頭說到的前後臺分離的問題。這樣做並不合理,後臺只應該返回我們所需要的資料,並且它可以隨時被其他語言替換掉。

更多精彩內容請關注我的微信公眾號(搜尋Phodal即可)

單頁面應用後臺渲染的三次實踐

單頁面應用後臺渲染的三次實踐