應(yīng)用場(chǎng)景
簡(jiǎn)單來(lái)說(shuō)病蛉,骨架屏(skeleton screen) 就是一個(gè)頁(yè)面從html 下載完成到 js 下載完成b并且執(zhí)行數(shù)據(jù)渲染這兩個(gè)時(shí)間點(diǎn)之間暫時(shí)渲染頁(yè)面基本結(jié)構(gòu)的方案挽唉。
而且這個(gè)骨架屏優(yōu)化鸭蛙,是有一定場(chǎng)景的,包括且不限于以下幾種情況:
- 有懶加載機(jī)制的SPA路由
- 多頁(yè)面程序的首頁(yè)渲染
- SPA 中的非懶加載路由铃岔,但是數(shù)據(jù)量很大涉馁,完全load 并渲染數(shù)據(jù)需要花較長(zhǎng)時(shí)間
上圖形象地解釋了兩個(gè)多頁(yè)面程序之間的切換门岔,用Skeleton Screen 去優(yōu)化用戶(hù)觀(guān)感的方案。
Skeleton 渲染是在前端項(xiàng)目編譯時(shí)完成的谨胞,與之相對(duì)應(yīng)的是組件在瀏覽器runtime實(shí)時(shí)渲染成DOM固歪,從技術(shù)上講就是在服務(wù)器端預(yù)先把組件布局和數(shù)據(jù)渲染成html 字符串并且注入html 文件中。這需要服務(wù)器端渲染(ssr) 的支持胯努。如react 和 vue.js 這樣依賴(lài)虛擬DOM 的前端框架天然是支持服務(wù)器端渲染的。因?yàn)橹恍枰粋€(gè) JavaScript 在服務(wù)器端的執(zhí)行環(huán)境(例如V8 引擎)就可以輕易創(chuàng)建虛擬Dom并且映射為DOM tree逢防∫杜妫基于虛擬dom的服務(wù)器端渲染,最早起源于react忘朝,可以參考 strikingly 的技術(shù)博客
服務(wù)器端渲染
服務(wù)器端渲染作為skeleton screen的基本技術(shù)棧灰署,可以參考各前端框架的官方文檔钉赁,例如 vue-ssr guide. 主要是依賴(lài) vue-server-renderer 這個(gè)庫(kù)來(lái)實(shí)現(xiàn)
拿vue.js 組件來(lái)說(shuō)屿愚,在node.js 中渲染為 HTML 字符串可以簡(jiǎn)單分為幾步:
// 第 1 步:創(chuàng)建一個(gè) Vue 實(shí)例
const Vue = require('vue')
const app = new Vue({
// el: 是不需要的,因?yàn)橐坏┰O(shè)置目標(biāo) el孤钦,就會(huì)涉及document 操作
template: `<div>Hello World</div>`
})
// 第 2 步:創(chuàng)建一個(gè) renderer
const renderer = require('vue-server-renderer').createRenderer()
// 第 3 步:將 Vue 實(shí)例渲染為 HTML
renderer.renderToString(app, (err, html) => {
if (err) throw err
console.log(html)
// => <div data-server-rendered="true">Hello World</div>
})
上面的官方DEMO 很簡(jiǎn)單悦昵,直接在nodejs 腳本文件中定義vue 組件肴茄。但實(shí)際應(yīng)用中我們的組件一般都比較復(fù)雜,可能是一個(gè)入口 app.vue但指,依賴(lài)了好幾個(gè)組件寡痰,所以要用另外的方式去引入 new Vue 產(chǎn)生的 vm 對(duì)象. 這個(gè)vm 對(duì)象作為 renderer.renderToString 函數(shù)的第一個(gè)參數(shù)是最核心的。實(shí)際上通過(guò)跟蹤 vue-server-renderer 的源碼棋凳,ssr 還是依靠vue組件的render 函數(shù)來(lái)完成大部分工作 拦坠,render 函數(shù)是 vue 組件的核心函數(shù)。
| installSSRHelpers(component);
renderToString -> createRenderFunction -> | normalizeRender(component); // get component render function
| renderNode(component._render(), true, context);
// call component render to VNode.
幾個(gè)坑
- document not defined
服務(wù)器端渲染并不等同于在服務(wù)器端啟動(dòng)一個(gè)瀏覽器進(jìn)程剩岳,所以無(wú)法獲取瀏覽器窗體中的window贞滨,document 等全局對(duì)象。一旦服務(wù)器端渲染的js 腳本中涉及到document 操作拍棕,就會(huì)報(bào)這個(gè) document not defined 的錯(cuò)誤晓铆。
所以要么就在vue 組件中或者所有依賴(lài)包中用到document 的地方都要做檢測(cè),要么就在服務(wù)器端渲染之前給global 對(duì)象加上document 定義 github issue
- unexpected token =
在vue-server-renderer 的執(zhí)行過(guò)程中莫湘,提供了把vue 組件渲染成html 片段并且插入某個(gè) template HTML 中的API尤蒿, 在創(chuàng)建Renderer 的時(shí)候傳入一個(gè)template。后來(lái)發(fā)現(xiàn)ssr 的 template 中是不能包含類(lèi)似這種 <%= ddd > 符號(hào)的幅垮,也就是template 必須得符合vue 的template 語(yǔ)法腰池。
// 最后ssr 就會(huì)輸出 Vue 組件的內(nèi)容,并且注入到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) => {
// 頁(yè)面模板中 {{ title }} 將會(huì)是 "Hello"
})
具體參考 ssr 使用頁(yè)面模板
因?yàn)橛械暮蠖顺绦騿T習(xí)慣了 asp 或者 jsp 的template 語(yǔ)法,以 <%= 等作為數(shù)據(jù)插值表達(dá)式的開(kāi)頭示弓,會(huì)引起 vue ssr 的編譯失敗讳侨。
寫(xiě)在后面
綜上,這次我只是簡(jiǎn)單分析記錄了下 Skeleton 骨架屏實(shí)現(xiàn)的第一部分奏属,就是server side render跨跨,這是預(yù)先渲染的技術(shù)前提。實(shí)際上囱皿,這種后端拼接Html 字符串的活在php年代勇婴,python server中,甚至node.js server 中我們?cè)缇透蛇^(guò)了(ejs 或者 jade)嘱腥。本質(zhì)上都是拼接Html 字符串耕渴,提高SEO 和首屏響應(yīng)。
在Github 上有許多基于服務(wù)器端渲染的靜態(tài)網(wǎng)站生成器或者 博客工具齿兔,可以實(shí)現(xiàn)比較好的瀏覽效果橱脸, 例如:vuepress.
關(guān)于骨架屏的實(shí)現(xiàn),業(yè)內(nèi)已經(jīng)有很成熟的方案分苇,既有基于 ssr 的添诉,也有直接基于瀏覽器內(nèi)核起個(gè)進(jìn)程預(yù)渲染的,完全不用vue 的ssr 技術(shù)棧医寿。下一次再具體分享下后者
參考閱讀:
vue ssr guide
餓了么升級(jí)PWA
vue.js 開(kāi)發(fā)系列二:render 函數(shù)
essential-guide-to-improve-seo-in-single-page-application-vuejs