1. 程式人生 > >掘金陰明:後端渲染實踐

掘金陰明:後端渲染實踐

Vue.js、React.js 及 Angular.js 等等前端開發框架引入了 UI = framework(State) 的前端程式設計邏輯,大範圍降低了前端業務開發的難度,尤其是面向複雜前端應用。而這些優質開源框架的普及、多端業務統一、前後端分離的需求讓越來越多的架構設計將大部分業務邏輯寫在了前端。

但是,純前端產品也有著它的問題。上述的幾個前端框架都支援了後端渲染的功能,從而融合了前後端的問題。如何有效地整合現有前端邏輯實現後端渲染、如何優化後端渲染效能、如何實現伺服器流式吐內容更快地渲染頁面的經驗,會成為新一代 Web 開發的方向,提高前端業務開發的效率。在由七牛雲主辦的 ECUG 十週年盛會上,陰明為大家帶來了他的實踐分享。


陰明(掘金聯合創始人、CEO)

前端框架的繁榮及成熟

從百家爭鳴到三足鼎立


圖 1 

這是從網上找到的前端的狀態(圖 1 ),每一個顏色均是某一個前端庫的分類。前端的世界就是如此,需要在一群的選項中選擇一個,並且要跟其他的選項 PK 。

如圖 1 所示,方框的部分寫具體的業務程式碼,例如早期的 jQuery。Prototype 曾經完成了 2000 年內有複雜業務程式碼的前端,寫了大量的頁面,傳統的後臺 admin 等都是這樣。再往上 Ember 比較適合業務穩定的系統使用,因為它一直堅持著向前相容,它不像新的庫,如果出了一個新版本基本上需要推倒重寫;而 Backbone 是寫比較複雜頁面的一個庫, Angular 、React 等等。

在這麼繁雜的前端中,單純寫前端業務有很多選擇。曾看到一個評論:“ 2016 年,你完成一個巨簡單的業務,就需要 TypeScript 寫程式碼,用 Fetch 發起非同步請求,所有的程式碼編譯程 ES6 ……”用了幾十個庫完成一個非常簡單的問題。那麼,在這樣的前端生態下,它一定會是繁榮的,如果不繁榮,不會有很多人在這裡做事情。

Web 技術和 JavaScript 到達各個領域

  • **後端:**Node.js 在業務開發中已經比較廣泛使用,而且 v8 效能較好。

  • 移動:最常用的 Hybrid ,React  Native ,NativeScript ,Weex 。

  • **桌面:**Electron,nw.js 來實現 Web 端的應用,其實都是網頁。

  • **VR:**WebVR ,A-Frame ,WebGL 

  • **硬體:**Cylon.js ,Tessel ,Johnny-Five

  • **資料視覺化:**d3.js ,vis.js ,HighCharts ,Charts

因為 JavaScript 本身的程式碼,學習陡峭程度非常低,入門門檻低,並且網頁端需求大,因此 JavaScript 異常繁榮。慢慢地,JavaScript 的效能越來越好,有更多人使用,進而寫 JavaScript 的人想用 JavaScript 寫更多的東西,一步步邁到了各個技術生態。

三足鼎立:Vue.js 、Angular.js 、React.js

2016 年,從繁雜的生態、無盡的爭吵和選擇當中, Web 開發中的 Vue.js 、Angular.js 、React.js 這三個框架初露端倪,各佔據一片江山。所說的三足鼎立有一個前提,並不是它們在社群裡有多麼火或者人們都愛用,而是這些庫是否被當下最新的應用直接用在自己的業務程式碼當中。

Angular.js 在 Google 已經被推了很多年,支援了 Google 本身及很多公司的大型業務程式碼。React.js 是 Facebook 支援的專案,它已經被用在很多線上的業務程式碼中,而且這些業務程式碼每天在承接著幾億的訪問量。Vue.js 本身最開始是 Evan You 獨立開發者開源的專案,之後 Alibaba、餓了麼等公司都開始充分使用,現在阿里的 Weex 也借鑑了 Vue 的架構邏輯。

每個框架甚至都有了自己的技術生態

三個庫三足鼎立的原因是它們本身都有一套自己的生態。例如 React.js ,最早底下的 Server  Side  APIs 、GraphQL 、Flux 層怎麼樣把靜態資料狀態管理系統管好,再到 React 層本身頁面樣式,再到 Virtual  Dom 和 Native  Code ,它的技術量不多,如果深入其中,學習週期也不長,但是它本身蔓延出了一條生態。如果有朝一日它把中間層做到足夠好,上下層對接很多東西,React 會成為一個比較大的技術生態。

Why  Vue.js

我們為什麼選擇 Vue.js,這是一個很幸運、很偶然的選擇。掘金用 Vue.js 是在 0.12 版本,現在已經是 2.15 版本。當時選擇最早版本的時候,掘金只有 4 個人。Vue.js 發展到現在,可以看到是一個增長非常瘋狂的專案,從一開始的個人開源,到現在許多大公司使用,這和那些有大公司支援的開源庫有了非常大的區別。到現在,Vue 在 NPM 上每月有超過 22 萬次下載,這是很高的量。

為什麼用 Vue.js ?

第一次使用 Vue.js 的時候,公司想做促銷活動,寫一個問答宣傳頁面,當時微信還沒有禁止這樣的傳播,我做了一個“算算你值多少錢”的應用,當時腦子裡有幾個庫。考慮到自己比較瞭解 Vue.js ,就試著用 Vue.js 來開發。後來發現從有這個想法到開發完只用了四個小時,包括 UI 層、頁面層、微博分享、微信分享,開發小東西的速度超乎了想象,但那時候還沒有準備拿它來寫整個網站的業務邏輯。

Vue.js 到了 1.0 ,它是一個前端的 MVVM 的框架,看到一個網頁端的介面,它出現這樣的樣式一定是因為它背後有資料。而 MVVM 框架最大的特點是樣式隨著資料變化而變化,資料和 UI 層的同步是框架本身自動完成的,這是 Vue.js 在當時幫我們解決的一個問題。Vue 到了 1.0 , MVVM 的框架適合掘金當時的業務開發需求。


圖 2

發展到 2.0,很多人說 Vue.js 已經很火了,很多人真正願意用它的原因是這張圖(圖 2 ),它是一個漸進式前端解決方案。分了五層很重的東西,不是打包型的,而是一個把它拆散了,每一層根據需求會加的東西。最早期人們用 Vue.js 的需求,這是一段前端的業務邏輯,希望用宣告式語言 Declarative 把這段業務描述清楚,因此就可以用 Vue.js 最簡單的業務邏輯、最簡單的庫把 Vue.js 這個庫加進來,便可以完成前端業務裡面的互動。從資料層到 UI 層的變化,特別簡單的一個功能。但是前端應用更復雜一點,這個頁面有很多元件,可以根據自己的需求去定義 Component ,可以用組建化的邏輯編寫業務邏輯,這是第二層。但是發現這個東西很複雜,一個頁面已經不能實現,要分好幾個頁面。可以用另外一個援引的庫,就像 Routing 加進來,有了前端路由。

現在發展這個業務越來越複雜,因為這個業務正好代表了公司自己的發展,剛開始掘金只是單純的 MVVM ,後來有了前端路由,再後來發現,這個頁面已經複雜到類似於小應用,小應用一定會帶來狀態管理。在這個網站上,所有的應用都要同步當下登入的使用者,這時必須需要狀態管理,掘金便開始進行大規模狀態管理。

前端已經複雜到需要完整的一套技術體或者自動化工具,來生產 Build 測試、釋出等等,還要前端分包,這個頁面是純前端應用,不斷地開啟新的頁面,其實它都是從後端再拿一個新的 js 出來,每一段頁面都是自己的 js ,這樣能提高效能,按需拿取頁面的邏輯,這個時候分包就一定要用工業化的邏輯來實現。再往後走,可能會有一些測試、單元、程式碼的東西,它是一套整個的構建工具。

這就是一套流程,對於剛開始的開發者可能用特別簡單的 Vue.js 程式碼寫一個特別帥的主頁,能動一動,彈一彈,後來可以根據自己的需求修改,頁面可以更復雜,可以寫成元件化的、寫客戶端路由等等。這一套漸進式的系統,使得幾乎每一個業務在用 Vue.js 的時候都有一個對標點,一個網站的對標點可能是在客戶端流這一層,可能一個網站的對標點是在擴充套件工具。因此,一個人基於自己要做的業務,可以按照不同的深度去使用,而且在不同的深度之下不會有效能或者學習路徑陡峭的問題,這就是人們喜歡用 Vue.js 的真實原因。

Vue.js 原理

Vue.js 不支援 IE8 及其以下,它只支援 IE9 以上,因為 IE9 支援了 ES2015 。比如說 A 是一個 Object ,每次輸出 A 到 B 的時候,一定會先呼叫一次 getter ,相當於獲取了任何一個數據被改變的時候的那個事件,並且對於這個事件可以進行相關的處理。


圖 3

這是一段業務(圖 3 ),這個業務可能基於相關的 Object 的資料,因為有 setter 函式在這裡控制,因此可以生成一個 watcher 。面對每一段業務程式碼,這個 watcher 都會關注所有相關的資料,以至於這些相關的資料發生任何的變動,它都會調動 setter 。setter 會告訴 watcher ,watcher 知道跟這段道路相關的資料發生變化了,發生變化之後就會去 Component  Render  Function,把新的資料的樣式給前端樣式,這樣完成了從資料層變化,到告訴 watcher ,watcher 再告訴 Render  Fenction,最後把前端 UI 變了這樣的邏輯。它並沒有用高階的資料結構或者高階的演算法,它其實是用了 JavaScript 原生的一個屬性。

選擇 Vue.js 框架

選擇一個前端框架一定有很重要的原因:
* **開發效率:**Declarative Rendering ,前端開發寫這個業務邏輯會非常漂亮;

  • 程式碼維護:元件化 vue-loader ,可以在一個檔案中關於某個元件或者某個頁面寫出邏輯層、樣式層,可以寫在一個組建中,這是一個比較好的解決方案。

  • 速度效能:要能滿足需求,Vue.js 是遠快於 1.0 的。頁面渲染的時候可能不在意效能,但是到頁面複雜度的時候便會很在意效能,效能慢的時候會影響每一個頁面跳轉。

掘金 Vue.js 架構

每次做一個新的頁面或者新的業務都會這樣操作,後端要做自動渲染、自動更新,會有一套配置檔案來配置前端進行分包和不停載入,不停地把前端的業務融合在一起。在每一個頁面中最重要的一定是核心應用,在核心應用中每次首要考慮的是路由,對於整個產品或者小的功能點是否是有一些共用的狀態。

定義好核心的應用清楚情況下,在頁面裡面找基礎元件,並且把相關的基礎元件比較複雜地組合成一個公用模組。基礎元件在上層呼叫元件的時候,上層可以進行小的微調,但是這些元件的組合可能是有公用模組,模組的意思是在上層使用這個元件的時候,不可以再對這個元件進行任何的調整。再往下走是 Vuex ,也就是各個不同的分頁,這個分頁相關的業務邏輯,每次定義一個分頁,要把前端路由定義好,並且把分頁裡面需要的狀態拿好,把需要的元件和公用模組拉進來,這個頁面的業務及直接單獨寫即可。


圖 4 

這是掘金一套前端的架構(圖 4),但是前端架構相比於後端架構,往往簡單很多。

純前端應用的弊端及問題

相容問題

這三個庫( Vue.js ,  React.js ,  Angular.js: IE9+)都不支援 IE8 ,IE9 支援 80% 左右,偶然觸及到一些 Vue.js 很底層很極端的效能時,IE9 會掛掉,除此之外基礎性的還不錯。但是給企業端或者後端特別複雜的頁面,給工業用的 admin 頁面可能用的還是 IE6、7、8 的瀏覽器,還不太能覆蓋這部分的需求。

SEO

純前端應用,如果看 Google 或者百度拉出來的資料,Google 做了一個後端的 cache ,跑了一個小的 Chrome 核心在後端,它能拉取完全的純前端應用。而百度的機器一拉出來就是空的白頁面,什麼也沒有,並不是百度的技術達不到。

第一,可能是百度面對大多數的技術網站生態還沒有很多的純前端應用。

第二,在後端小核心用純前端應用去抓挺費效能的,覺得沒有必要加這一層。但是面對中國的環境, Google 的流量不少,但是也有百度的流量,掘金要支援百度的 SEO ,但是還有其他的 SEO ,國內的 SEO 其實都不太支援,搜狗支援,其他都不太支援純前端應用的抓取,對於內容型網站來講可能是一個坑。

速度

初始的拉取速度,如果是網頁的話,拉一個 HTML ,內容拿到了,開始往下看。掘金網站的真實情況,速度還好,該出來的東西一秒之內都能出來,但是第一次拉一個 HTML ,再拉一個 js ,再拉資料,再渲染頁面,頁面出來再拉分別的資料。其實這套流程中,在 HTML 拉出一批小的資料出來。如果很追求效能極致的人是不太能接受的,而且永遠無法解決。因此,如果很在意初始頁面第一次 loading 速度的人,可能這裡會有問題。掘金現在已經有問題了,網站會在一個月內內容型頁面會變成完全後端渲染。

URL <=> Content Cache

純前端應用能夠做到的極致是每一個資源都有一個 URL ,但是純前端應用很大的一個問題是:並不是每一個資源都有固定的 URL ,大多數的頁面都沒有一個固定的 URL ,這樣使得 cache 很難做。

每個頁面都要定義分頁的相關邏輯,大多數的開發者如果沒有到達工業化或者產品沒有到達一定的資料量級,寫得很亂,並沒有做到每一個頁面鬥毆自己的 URL 無,主流的 Cache  URL 模式很難執行。但是當產品不斷地優化,優化到一定的情景一定開始要提速的時候,純前端應用就會遇到極大的問題。

Vue.js 2.0 後端渲染

A Simple Vue.js Program

Virtual DOM

經常聽說 Virtual DOM 很厲害,其實 Virtual DOM 就是把 HTML 用 JavaScript 來表現,它不是任何特殊的技術,沒有任何的功能點,可以用 HTML 來表達一段 DOM ,也可以拿 JavaScript 來表現一段 DOM 。最大的不同點在於,多了一層把 JavaScript 定義的 Virtual  DOM 渲染成真實 DOM 的這套業務邏輯。比如,這是一個 Virtual  DOM ,先把這個 Object 裡面再加一個 ul ,可以用 Virtual  DOM 來實現,為什麼說它的效能好呢?因為在瀏覽器環境中,HTML 或者 DOM 的直接運算非常慢,但是 JavaScript 運算很快。

Render Function 


圖 5 

有了 Virtual  DOM 這一層用 JavaScript 代表 DOM 之後,用 Render Function 把 DOM 再刷出去即可。因此,Render Function 也是 2.0 實現的,1.0 只能定義頁面和邏輯,它來幫你做一切,而 2.0 之後可以用 Render Function ,這是一段把 Virtual  DOM 變成 DOM 的邏輯(圖 5 )。

最大的價值在於,因為有 Render Function ,把 JavaScript 變成真實 DOM 這個函式,同樣把後端能理解的 Object 在後端提前用 Render Function 輸出 HTML ,這樣後端就已經把它輸出來了,直接 Drive 給前端,這個頁面就已經有了。也可以把一個 JavaScript 表達的 DOM 輸出成真實的 HTML 給前端,後端渲染就完成了。

Stream

只要在 Vue 業務包在一個 function call 中並接上 Window  contex,伺服器 renderer 拿到相關業務 js 檔案吐出內容。Vue.js 2.0 支援 Stream 後但流式資料,在 HTML 完整生成之前的向前端吐資料。

後端渲染 Nuxt.js 的開發實踐

Vue.js 最基礎的後端渲染,如果對於這樣一個業務,每個公司都要根據自己的業務程式碼做一套後端渲染的邏輯,這不太可能。對於通用解決方案,一定是有更好的庫,感謝有人造輪子。剛開始做後端渲染的時候是沒有輪子的,掘金後端渲染都是自己寫的,現在如果有輪子會好些。

開源支援

Vue 的生態繁榮,很大一部分來源於整個生態周邊環境的支援,比如腳手架、元件化、路由、狀態管理、 Ajax 、前端開發工具、前端元件庫、後端渲染。在 Vue 的前端方案上,中國已經比國外強,開發質量很高。後端渲染,遲早會有一個很牛的庫出來幫大家,很可惜之前沒有,但是最後有了,叫做 Nuxt.js 。

Nuxt.js 是一個類似於 Next.js(React)的開源後端渲染庫,它支援的並不是後端渲染這一層的業務,它做了一套通解,想要用 Vue 的業務去開發,但同時支援 code-splitting 、generation 等不同的配置檔案,它都會有一套不錯的解決方案生成。但是大家都是後端的高手,最終可能不願意用別人的解決方案。但是像比較偏前端的人來講,它的基礎解決方案已經解決很大問題了。

Nuxt.js 檔案結構

它裡面有幾個基礎的檔案定義,其中最重要的是 nuxt.config.js 。把分包打包的邏輯封裝到底層,這是現在最大的問題,因為有功能在這一層會做測試、靜態的傳輸和儲存,這也是為什麼掘金不能直接去用 Nuxt 完成後端渲染,還是要自己寫。最重要的是 Asssets 基礎業務程式碼和第三方程式碼的儲存檔案,即 Vue 裡面不同頁面的這套邏輯。把一個頁面放在 pages 裡面之後,就不用專門定義,它會自動繫結好。

Nuxt.config.js

head 定義的是後端渲染這套業務的時候,在網頁端的 head 裡面放哪些基礎資料,比如 meta 等資料,以及 link 裡面有哪些靜態檔案需要特別注意的,如何援引於其他資源,比如 css 裡面掘金是從 assets 裡面拿出來的,它的分頁之間的切換,純前端應用不需要看到頁面裡面有一個 loading 的感覺,它解決切換時候的動效,把它封裝得很漂亮。

pages

對於 Vue 來講,把它的 template 側寫在一個 export 的檔案裡面,layout 、transition 和 scrollToTop 是純前端應用都會遇到的問題,這套頁面用的是哪個 layout 展示?在頁面切換之間是否要有動畫效果?以及在純前端應用中每次頁面之間切換是否要滾到最上面?因為它是一個單純的頁面,如果不設定滾到最上面,會發現跳到另外一個頁面還是在中間的位置,但是在瀏覽器來看其實是在一個網頁裡面,沒有跳到新的網頁,它把通用的需求封裝得很漂亮。validate 是解檢測 url 的,middleware 是一些其他的功能,可以再加進去。這裡面最好的事情是 head ,在純前端應用中會有不同的頁面,在每個頁面中 title 一定會變,單獨頁面裡面移動端的展示模式和特殊的配置檔案等等,這一套東西以前都得單獨來寫,每一個頁面都得單獨解決,而現在通解來實現了,而且通解沒有做得太深,有時候開源庫定義得太死,可活動性太差,但是它定義好的東西都是所有人需要的。

Async  Data

拉資料,從遠端拉資料,再渲染頁面。

Vuex/Fetch

Fetch 和 data 幾乎是一樣,唯一的不同在於 data 這個函式是頁面渲染出來的,拉資料的時候在渲染頁面的更多樣式。開啟一個頁面,Fetch 要先把頁面拉回來,這個頁面才會跳轉。為什麼要 Fetch ?因為對於後端渲染來講,一定是在後端渲染,一定是先把資料拉回來,才能把頁面生成,才能投給前端。所以,Fetch 函式是用後端渲染很重要的一個環節。

Vuex/nuxtServerlnit

Vuex 就是一個狀態管理器,也就是一個前端應用所有的資料都需要的地方。而這裡需要什麼呢?所有的後端頁面也需要使用者認證,並且把使用者資料給前端,但是對於純後端應用生成頁面稍微有點難,但是在 Vuex 裡面定義好所有頁面都需要公用這塊邏輯,並且用 nuxtServerInit 提前在後端也把這個需求、這個解取好,用這一套完整定義可以使得前端、後端再輸出頁面,不管是前端輸出的還是後端渲染好的,都可以同步獲得這個資料,並且完成這部分業務。它解決了非常大的業務邏輯,如果讓自己寫,程式碼量少說也得四五百行左右,它解決得非常好,掘金把原始碼拿出來看明白,把這段原始碼應用到產品裡。

總結

前端框架雖好,但是還是需要後端渲染。Vue.js 後端渲染技術層已算成熟。Nuxt.js 等庫優化了後端渲染的實現效率。互動型產品適合前端應用,內容型產品適合後端應用。