應用場景
簡單來說,骨架屏(skeleton screen) 就是一個頁面從html 下載完成到 js 下載完成並且執行資料渲染這兩個時間點之間暫時渲染頁面基本結構的方案。
就我的理解,骨架屏優化是有一定場景的,包括且不限於以下幾種情況:
- 有懶載入機制的SPA路由
- 多頁面程式的首頁渲染
- SPA 中的非懶載入路由,但是資料量很大,完全load 並渲染資料需要花較長時間
上圖形象地解釋了兩個多頁面程式之間的切換,用Skeleton Screen 去優化使用者觀感的方案。
Skeleton 渲染是在前端專案編譯時完成的,與之相對應的是元件在瀏覽器runtime實時渲染成DOM,從技術上講就是在伺服器端預先把元件佈局和資料渲染成html 字串並且注入html 檔案中。這需要伺服器端渲染(ssr) 的支援。如react 和 vue.js 這樣依賴虛擬DOM 的前端框架天然是支援伺服器端渲染的。因為只需要一個 JavaScript 在伺服器端的執行環境(例如V8 引擎)就可以輕易建立虛擬Dom並且對映為DOM tree。基於虛擬dom的伺服器端渲染,最早起源於react,可以參考 strikingly 的技術部落格
伺服器端渲染
伺服器端渲染作為skeleton screen的基本技術棧,可以參考各前端框架的官方文件,例如 vue-ssr guide. 主要是依賴 vue-server-renderer 這個庫來實現
拿vue.js 元件來說,在node.js 中渲染為 HTML 字串可以簡單分為幾步:
// 第 1 步:建立一個 Vue 例項
const Vue = require('vue')
const app = new Vue({
// el: 是不需要的,因為一旦設定目標 el,就會涉及document 操作
template: `<div>Hello World</div>`
})
// 第 2 步:建立一個 renderer
const renderer = require('vue-server-renderer').createRenderer()
// 第 3 步:將 Vue 例項渲染為 HTML
renderer.renderToString(app, (err, html) => {
if (err) throw err
console.log(html)
// => <div data-server-rendered="true">Hello World</div>
})
複製程式碼
上面的官方DEMO 很簡單,直接在nodejs 指令碼檔案中定義vue 元件。但實際應用中我們的元件一般都比較複雜,可能是一個入口 app.vue,依賴了好幾個元件,所以要用另外的方式去引入 new Vue 產生的 vm 物件. 這個vm 物件作為 renderer.renderToString 函式的第一個引數是最核心的。實際上通過跟蹤 vue-server-renderer 的原始碼,ssr 還是依靠vue元件的render 函式來完成大部分工作 ,render 函式是 vue 元件的核心函式。
| installSSRHelpers(component);
renderToString -> createRenderFunction -> | normalizeRender(component); // get component render function
| renderNode(component._render(), true, context);
// call component render to VNode.
複製程式碼
幾個坑
- document not defined
伺服器端渲染並不等同於在伺服器端啟動一個瀏覽器程式,所以無法獲取瀏覽器窗體中的window,document 等全域性物件。一旦伺服器端渲染的js 指令碼中涉及到document 操作,就會報這個 document not defined 的錯誤。
所以要麼就在vue 元件中或者所有依賴包中用到document 的地方都要做檢測,要麼就在伺服器端渲染之前給global 物件加上document 定義 github issue
- unexpected token =
在vue-server-renderer 的執行過程中,提供了把vue 元件渲染成html 片段並且插入某個 template HTML 中的API, 在建立Renderer 的時候傳入一個template。後來發現ssr 的 template 中是不能包含類似這種 <%= ddd > 符號的,也就是template 必須得符合vue 的template 語法。
// 最後ssr 就會輸出 Vue 元件的內容,並且注入到template 中 註釋 <!--vue-ssr-outlet--> 的地方
const renderer = createRenderer({
template: require('fs').readFileSync('./index.template.html', 'utf-8')
})
const context = { title: 'Hello' }
renderer.renderToString(app, context, (err, html) => {
// 頁面模板中 {{ title }} 將會是 "Hello"
})
複製程式碼
具體參考 ssr 使用頁面模板。 因為有的後端程式設計師習慣了 asp 或者 jsp 的template 語法,以 <%= 等作為資料插值表示式的開頭,會引起 vue ssr 的編譯失敗。
寫在後面
綜上,這次我只是簡單分析記錄了下 Skeleton 骨架屏實現的第一部分,就是server side render,這是預先渲染的技術前提。實際上,這種後端拼接Html 字串的活在php年代,python server中,甚至node.js server 中我們早就幹過了(ejs 或者 jade)。本質上都是拼接Html 字串,提高SEO 和首屏響應。
在Github 上有許多基於伺服器端渲染的靜態網站生成器或者 部落格工具,可以實現比較流暢的瀏覽體驗, 例如:vuepress.
關於骨架屏的實現,業內已經有很成熟的方案,既有基於 ssr 的,也有直接基於瀏覽器核心起個程式預渲染的,完全不用vue 的ssr 技術棧。下一次再具體分享下後者
參考閱讀:
essential-guide-to-improve-seo-in-single-page-application-vuejs