ajax div 赋值重新渲染_优化向:单页应用多路由预渲染指南
前言
Ajax 技術的出現,讓我們的 Web 應用能夠在不刷新的狀態下顯示不同頁面的內容,這就是單頁應用。在一個單頁應用中,往往只有一個 html 文件,然后根據訪問的 url 來匹配對應的路由腳本,動態地渲染頁面內容。單頁應用在優化了用戶體驗的同時,也給我們帶來了許多問題,例如 SEO 不友好、首屏可見時間過長等。服務端渲染(SSR)和預渲染(Prerender)技術正是為解決這些問題而生的。
閱讀本文,你能夠了解到什么是預渲染、預渲染與服務端渲染的異同以及預渲染在?Vue.js 項目中的使用。
服務端渲染與預渲染
一些概念
客戶端渲染:用戶訪問 url,請求 html 文件,前端根據路由動態渲染頁面內容。關鍵鏈路較長,有一定的白屏時間;
服務端渲染:用戶訪問 url,服務端根據訪問路徑請求所需數據,拼接成 html 字符串,返回給前端。前端接收到 html 時已有部分內容;
預渲染:構建階段生成匹配預渲染路徑的 html 文件(注意:每個需要預渲染的路由都有一個對應的 html)。構建出來的 html 文件已有部分內容。
下圖簡單展示了客戶端渲染、服務端渲染和預渲染的請求流程。
本文示例使用 vue-cli 生成,點擊這里查看示例。dist?目錄是啟用了預渲染的打包目錄,dist2?目錄則是普通客戶端渲染的打包目錄。通過對比目錄中的文件,你可以對預渲染有個初步的了解。若你還是不知道什么是預渲染,不妨先通讀全文。
共同點
針對單頁應用,服務端渲染和預渲染共同解決的問題:
SEO:單頁應用的網站內容是根據當前路徑動態渲染的,html 文件中往往沒有內容,網絡爬蟲不會等到頁面腳本執行完再抓取;
弱網環境:當用戶在一個弱環境中訪問你的站點時,你會想要盡可能快的將內容呈現給他們。甚至是在 js 腳本被加載和解析前;
低版本瀏覽器:用戶的瀏覽器可能不支持你使用的 js 特性,預渲染或服務端渲染能夠讓用戶至少能夠看到首屏的內容,而不是一個空白的網頁。
預渲染能與服務端渲染一樣提高 SEO 優化,但前者比后者需要更少的配置,實現成本低。弱網環境下,預渲染能更快地呈現頁面內容,減少頁面可見時間。
不適合的場景
那什么場景下不適合使用預渲染呢:
個性化內容:對于路由是 /my-profile 的頁面來說,預渲染就失效了。因為頁面內容依據看它的人而顯得不同;
經常變化的內容:如果你預渲染一個游戲排行榜,這個排行榜會隨著新的玩家記錄而更新,預渲染會讓你的頁面顯示不正確直到腳本加載完成并替換成新的數據。這是一個不好的用戶體驗;
成千上萬的路由:不建議預渲染非常多的路由,因為這會嚴重拖慢你的構建進程。
Prerender SPA Plugin
prerender-spa-plugin 是一個 webpack 插件用于在單頁應用中預渲染靜態 html 內容。因此,該插件限定了你的單頁應用必須使用 webpack 構建,且它是框架無關的,無論你是使用 React 或 Vue 甚至不使用框架,都能用來進行預渲染。本文示例基于 Vue.js 2.0 + vue-router。
下文會從生成項目講起,然后看下沒有配置預渲染前的樣子,再配置預渲染進行構建,對比前后的差別。
生成項目
首先生成一個項目并安裝依賴。
vue init webpack vue-prerender-democd vue-prerender-demo && npm install復制代碼
組件開發過程我們不關注,具體可以查看示例源代碼。開發完成視圖如下。
路由配置
這是一個新聞應用的頁面,包括了最新、最熱兩個列表頁和一個文章頁。路由配置如下。
new Router({mode: 'history',
routes: [
{
path: '/',
component: Home,
children: [
{
path: 'new',
alias: '/',
component: () => import('@/components/New')
},
{
path: 'hot',
component: () => import('@/components/Hot')
}
]
},
{
path: '/article/:id',
component: Article
}
]
})復制代碼
預渲染的單頁應用路由需要使用 History 模式而不是 Hash 模式。原因很簡單,Hash 不會帶到服務器,路由信息會丟失。vue-router 啟用 History 模式參考這里。
History 模式需要后臺配置支持,最簡單的是通過 nginx 配置 try_files 指令。
location / {try_files $uri $uri/ /index.html;
}復制代碼
沒有配置預渲染前
配置完成后執行構建?npm run build,根據 nginx 配置,現在無論訪問哪個路由都會返回 dist/index.html。
訪問 / 路由。
可以看到,在 Fast 3G 網絡下,首屏可見時間是 4.34s,頁面至少在加載下面文件后才能被看到。
html
app.css?- 樣式
manifest.js?- webpack manifest
vendor.js?- 第三方庫
app.js?- 業務邏輯
0.js?- 路由分包文件
其中 vendor 文件包含了引用的第三方庫,文件規模較大。加載文件多,增加了白屏時間。所以,最有效的優化方案是減少首屏依賴文件。這里開始配置預渲染。
預渲染配置
安裝 prerender-spa-plugin,安裝時件略長,因為其依賴了 phantomjs,請耐心等待。
npm install prerender-spa-plugin --save-dev復制代碼我們只在生產環境中進行預渲染,修改 build/webpack.prod.conf.js,在配置插件的地方加入如下代碼。
var path = require('path')var PrerenderSpaPlugin = require('prerender-spa-plugin')
{
// ...
plugins: [
// ...
new PrerenderSpaPlugin(
// 輸出目錄的絕對路徑
path.join(__dirname, '../dist'),
// 預渲染的路由
[ '/new', '/hot' ]
)
]
}復制代碼
實例化 PrerenderSpaPlugin 需要至少兩個參數,第一個參數是單頁應用的輸出目錄,第二個參數指定預渲染的路由,這里執行了兩個路由?/new?和?/hot。執行構建?npm run build。
預渲染效果
訪問?/new?路由。
同樣在 Fast 3G 網絡下,首屏可見時間縮短至 2.30s。事實上,只要加載 html 和 app.css 文件,頁面內容就能看到了。
dist│ index.html
│
├─hot
│ index.html
│
├─new
│ index.html
│
└─static復制代碼
對比構建完成目錄,可以發現預渲染的目錄多了兩個文件?new/index.html,?hot/index.html。
查看?new/index.html
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>vue-prerender-demotitle><link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"><link href="/static/css/app.23611ac69a9fa48640e3bad8ceeab7bf.css" rel="stylesheet"><script type="text/javascript" charset="utf-8" async="" src="/static/js/0.41194d76e86bbf547b16.js">script>head><body><div id="app"><div><div class="mu-appbar mu-paper-1"><div class="left"><i class="mu-icon material-icons">homei>div><div class="mu-appbar-title"><span>新聞span>div><div class="right">div>div>...div>div><script type="text/javascript" src="/static/js/manifest.4410c20c250c68dac5bc.js">script><script type="text/javascript" src="/static/js/vendor.d55f477df6e96ccceb5c.js">script><script type="text/javascript" src="/static/js/app.f199467bd568ee8a197a.js">script>body>html>復制代碼
相比 index.html, new/index.html 中的??是有內容的,且??中多了當前路由分包的 js 文件。其余部分跟 index.html 一樣。雖然有多個 html,但從 /new 跳轉到其他路由時,還是單頁內跳轉的,不會有新的 html 請求。
根據上面配置的 nginx 規則,路由對應的返回文件分別是:
/ -> index.html/new -> new/index.html
/hot -> hot/index.html
/article/:id -> index.html復制代碼
其中,/new?和?/hot?路由返回的 html 包含了對應路由的內容,從而實現預渲染。沒有配置預渲染的路由跟原來一樣,還是訪問 /index.html,請求腳本,動態渲染。
預渲染達到了類似服務端渲染的效果。區別在于預渲染發生在構建時,服務端渲染發生在服務器處理請求時。
prerender-spa-plugin 原理
那么 prerender-spa-plugin 是如何做到將運行時的 html 打包到文件中的呢?原理很簡單,就是在 webpack 構建階段的最后,在本地啟動一個 phantomjs,訪問配置了預渲染的路由,再將 phantomjs 中渲染的頁面輸出到 html 文件中,并建立路由對應的目錄。
查看 prerender-spa-plugin 源碼 prerender-spa-plugin/lib/phantom-page-render.js。
// 打開頁面page.open(url, function (status) {
...
// 沒有設置捕獲鉤子時,在腳本執行完捕獲
if (
!options.captureAfterDocumentEvent &&
!options.captureAfterElementExists &&
!options.captureAfterTime
) {
// 拼接 html
var html = page.evaluate(function () {
var doctype = new window.XMLSerializer().serializeToString(document.doctype)
var outerHTML = document.documentElement.outerHTML
return doctype + outerHTML
})
returnResult(html) // 捕獲輸出
}
...
})復制代碼
最佳實踐
指定捕獲鉤子
默認情況下 html 會在腳本執行完被捕獲并輸出。你也可以指定一些鉤子,html 將會在特定時機被捕獲。
var path = require('path')var PrerenderSpaPlugin = require('prerender-spa-plugin')
{
// ...
plugins: [
// ...
new PrerenderSpaPlugin(
path.join(__dirname, '../dist'),
[ '/new', '/hot' ],
{
// 監聽到自定事件時捕獲
// document.dispatchEvent(new Event('custom-post-render-event'))
captureAfterDocumentEvent: 'custom-post-render-event',
// 查詢到指定元素時捕獲
captureAfterElementExists: '#content',
// 定時捕獲
captureAfterTime: 5000
}
)
]
}復制代碼
預渲染骨架屏
本文實例中更多是變化的數據,時效性要求比較高,不太適合預渲染的場景。如果想用預渲染來減少白屏時間,讓頁面反饋更及時的話,可以預渲染骨架屏。
<template><div>
<new-list v-if="news.length > 0">new-list>
<new-list-skeleton>new-list-skeleton>
div>
template>復制代碼
請求 news 數據需要一定時間,所以插件在腳本執行完捕獲的一般就是骨架屏。如果你想更靈活地指定捕獲時機,可以使用自定義事件鉤子,在組件掛載且請求數據前捕獲。
{mounted () {
document.dispatchEvent(new Event('sketelon-render-event'))
fetchNews()
}
}復制代碼
訪問頁面時,用戶首先看到預渲染的骨架屏(左圖),等待 js 加載完成后,再拉取數據渲染出正確的內容。
代理完整路徑
如果你配置了引用資源鏈接為帶域名的完整路徑。
// config/index.jsmodule.exports = {
build: {
...
assetsPublicPath: '//www.example.com/'
},
...
}復制代碼
那么構建時需要將域名代理到本地,否則 prerender-spa-plugin 捕獲的將會是線上的代碼。
127.0.0.1 www.example.com復制代碼預渲染根路由
通常情況下,動態路由如 /users/:id 不會配置預渲染,因為你沒法枚舉出所有的 User ID。訪問動態路由時,服務器會返回根路由 / 的 html,所以根路由也不適合做預渲染。但根路由往往是一個網站的首頁,是訪問量最大的一個路由。通過一些 nginx 可以解決這個問題。
location = / {try_files /home/index.html /index.html;
}
location / {
try_files $uri $uri/ /index.html;
}復制代碼
用戶訪問 / 路由,實際上是訪問了 /home/index.html,用 router 中配置的 /home 作為首頁。/index.html 可以作為其他沒有匹配到路由的響應。
結語
預渲染是實現成本較低,效果提升明顯的性能優化方案。預渲染有它適合的場景,當你的頁面內容變化不大,又想讓它更快地呈現給用戶時,試試預渲染吧。
?? 看完兩件事
如果你覺得這篇內容對你挺有啟發,我想邀請你幫我兩個小忙:
點個「在看」,讓更多的人也能看到這篇內容(喜歡不點在看,都是耍流氓 -_-)
關注公眾號「Vue社區」,每周重點攻克一個前端面試重難點,
公眾號后臺回復「電子書」即可免費獲取 27本 精選的前端電子書!
回復「100」免費獲取 100本 最棒的前端電子書!
總結
以上是生活随笔為你收集整理的ajax div 赋值重新渲染_优化向:单页应用多路由预渲染指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 爱情唯美的句子119个
- 下一篇: 涤纶是什么材质类型(涤纶面料有哪些优缺点