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