【PWA學習與實踐】(10)使用Resource Hint提升頁面加載性能與體驗

image

《PWA學習與實踐》系列文章已整理至gitbook - PWA學習手冊,文字內(nèi)容已同步至learning-pwa-ebook巩搏。轉載請注明作者與出處缚柳。

本文是《PWA學習與實踐》系列的第十篇文章宙刘。也許你還沒有聽說過或不了解Resource Hint患朱,但是通過本文,你會快速學習到這一件頁面加載性能利器旭斥。本系列相關demo的代碼都可以在github repo中找到容达。

PWA作為時下最火熱的技術概念之一,對提升Web應用的安全垂券、性能和體驗有著很大的意義花盐,非常值得我們?nèi)チ私馀c學習。對PWA感興趣的朋友歡迎關注《PWA學習與實踐》系列文章。


對之前的文章感興趣的話算芯,可以從這里找到:


引言

我們知道,在沒有緩存的情況下近速,無論是HTML诈嘿、javascript還是一些API數(shù)據(jù),頁面的每一個請求都需要從客戶端發(fā)起后經(jīng)由服務端返回削葱。在這種情況下奖亚,我們每一次涉及遠程請求的交互(打開一個頁面、查詢列表數(shù)據(jù)析砸、動態(tài)加載js腳本等)都會有網(wǎng)絡延遲昔字。如果我們能夠預測或指定頁面預先進行一些網(wǎng)絡操作,例如DNS解析或者預加載資源首繁,那么當我們在之后的操作中涉及到這部分資源時作郭,加載會更迅速,交互也會更加流暢弦疮。

當然夹攒,目前已經(jīng)有一些技術手段來幫助我們實現(xiàn)資源的預加載,例如常見的使用XMLHttpRequest來獲取資源并進行緩存胁塞。然而咏尝,這些技術都是應用層面的,并非Web標準啸罢,某些需求也無法準確實現(xiàn)编检。同時,在性能方面也存在著問題扰才。好在目前已有相關的Web標準(Resource Hint)涉及到這一部分允懂,通過它,可以在瀏覽器原生層面實現(xiàn)這些功能衩匣,同時提供性能保證累驮。下面我們來了解一下Resource Hint相關技術。

1. Resource Hint

Resource Hint是一系列相關標準舵揭,來告訴瀏覽器哪些源(origin)下的資源我們的Web App想要獲取谤专,哪些資源在之后的操作或瀏覽時需要被使用,以便讓瀏覽器能夠進行一些預先連接或預先加載等操作午绳。Resource Hint有DNS Prefetch置侍、Preconnect、Prefetch和Prerender這四種。

1.1. DNS Prefetch

當我們在注重前端性能優(yōu)化時蜡坊,可能會忽略了DNS解析杠输。然而DNS的解析也是有耗時的。在Chrome的Timing Breakdown Phase中秕衙,第三階段就是DNS查詢蠢甲。DNS Prefetch就是幫助我們告知瀏覽器,某個源下的資源在之后會要被獲取据忘,這樣瀏覽器就會(Should)盡早解析它鹦牛。

Resource Hint主要通過使用link標簽。rel屬性確定類型勇吊,href屬性則指定相應的源或資源URL曼追。DNS Prefetch可以像下面這樣使用:

<link rel="dns-prefetch" >

1.2. Preconnect

我們知道,建立連接不僅需要DNS查詢汉规,還需要進行TCP協(xié)議握手礼殊,有些還會有TLS/SSL協(xié)議,這些都會導致連接的耗時针史。因此晶伦,使用Preconnect可以幫助你告訴瀏覽器:“我有一些資源會用到某個源,可以幫我預先建立連接啄枕“颖瑁”

根據(jù)規(guī)范,當你使用Preconnect時射亏,瀏覽器大致做了如下處理:

  • 首先近忙,解析Preconnect的URL
  • 其次,根據(jù)當前l(fā)ink元素中的屬性進行cors的設置
  • 默認先將credential設為true智润;如果cors為Anonymous并且存在跨域及舍,則將credential置為false
  • 最后進行連接

使用Preconnect只需要將rel屬性設為preconnect即可:

<link rel="preconnect" >

當然,你也可以設置CORS

<link rel="preconnect"  crossorigin>

需要注意的是窟绷,標準并沒有硬性規(guī)定瀏覽器一定要(而是SHOULD)完成整個連接過程锯玛,瀏覽器可以視情況完成部分工作。

1.3. Prefetch

你可以把Prefetch理解為資源預獲取兼蜈。一般來說攘残,可以用Prefetch來指定在緊接著之后的操作或瀏覽中需要使用到的資源,讓瀏覽器提前獲取为狸。由于僅僅是提前獲取資源歼郭,因此瀏覽器不會對資源進行預處理,并且像CSS樣式表辐棒、JavaScript腳本這樣的資源是不會自動執(zhí)行并應用于當前文檔的病曾。

需要注意的是牍蜂,和DNS Prefetch、Preconnect使用不太一樣的地方是泰涂,Prefetch有一個as的可選屬性鲫竞,用來指定獲取資源的類型。由于不同的資源類型會具有不同的優(yōu)先級逼蒙、CSP从绘、請求頭等,因此該屬性很重要是牢。下表列出了一些常用資源的as屬性值:

資源使用者 寫法
<audio> <link rel=preload as=audio href=...>
<video> <link rel=preload as=video href=...>
<track> <link rel=preload as=track href=...>
<script>, Worker's importScripts <link rel=preload as=script href=...>
<link rel=stylesheet>, CSS @import <link rel=preload as=style href=...>
CSS @font-face <link rel=preload as=font href=...>
<img>, <picture>, srcset, imageset <link rel=preload as=image href=...>
SVG's <image>, CSS *-image <link rel=preload as=image href=...>
XHR, fetch <link rel=preload as=fetch crossorigin href=...>
Worker, SharedWorker <link rel=preload as=worker href=...>
<embed> <link rel=preload as=embed href=...>
<object> <link rel=preload as=object href=...>
<iframe>, <frame> <link rel=preload as=document href=...>
HTML <link rel=preload as=html href=...>

可以看到僵井,Prefetch的可選資源類型非常豐富,除了我們常用的scriptstyle妖泄,甚至還包括XHR、video艘策、img等蹈胡,基本涵蓋了Web中的各類資源。為了解決Prefetch中某些資源(例如XHR)的跨域問題朋蔫,可以為其應用CORS屬性罚渐。一個基本的Prefetch寫法也很簡單:

<link rel="prefetch" href="/my.little.script.js" as="script">

1.4. Prerender

上一部分我們講了Prefetch,而Prerender則是Prefetch的更進一步驯妄『刹ⅲ可以粗略地理解為“預處理”(預執(zhí)行)。

通過Prerender“預處理”的資源青扔,瀏覽器都會作為HTML進行處理源织。瀏覽器除了會去獲取資源,還可能會預處理(MAY preprocess)該資源微猖,而該HTML頁面依賴的其他資源谈息,像<script><style>等頁面所需資源也可能會被處理凛剥。但是預處理會由于瀏覽器或當前機器侠仇、網(wǎng)絡情況的不同而被不同程度地推遲。例如犁珠,會根據(jù)CPU逻炊、GPU和內(nèi)存的使用情況,以及請求操作的冪等性而選擇不同的策略或阻止該操作犁享。

注意余素,由于這些預處理操作的不可控性,當你只是需要能夠預先獲取部分資源來加速后續(xù)可能出現(xiàn)的網(wǎng)絡請求時炊昆,建議使用Prefetch溺森。當使用Prerender時慕爬,為了保證兼容性,目標頁面可以監(jiān)聽visibilitychange事件并使用document.visibilityState來判斷頁面狀態(tài)屏积。

When prerendering a document the user agent MUST set the document's visibilityState value to prerender. —— W3C Working Draft

Prerender的使用方式非常簡單医窿,與DNS Prefetch和Preconnect類似,指定rel屬性為prerender

<link rel="prerender" >

2. Resource Hint的具體使用方式

在上面的部分里炊林,我主要介紹了DNS Prefetch姥卢、Preconnect、Prefetch和Prerender這四種RHL(Resource Hint Link)渣聚,并且簡單介紹了如何在link中使用它們独榴。然而除了直接在HTML中加入對應link標簽外,還可以通過其他幾種方式觸發(fā)瀏覽器的Resource Hint奕枝。為了更加直觀棺榔,下面我們還是以圖書搜索這個demo為例來看看可以通過哪些方法來使用Resource Hint。

假設已經(jīng)為該demo添加詳情頁nextpage.html及其依賴的nextpage.js隘道,當點擊列表中的圖書時會進行跳轉症歇。

2.1. 文檔head中的link元素

這是Resource Hint最常用的一種方式,我們上面介紹的各種示例也就是使用的這種方式谭梗。例如想要指定Prefetch nextpage.js腳本可以這么寫:

<link rel="prefetch" href="./nextpage.js" as="script">

2.2. HTTP Link頭字段

可以通過Link HTTP header來使用Resource Hint忘晤。Link HTTP header和link元素是等價的。

The Link entity-header field provides a means for serialising one or more links in HTTP headers. It is semantically equivalent to the <LINK> element in HTML, as well as the atom:link feed-level element in Atom. —— RFC5988

Link主要由兩部分組成——URI-Referencelink-param激捏。URI-Reference相當于link元素中的href屬性设塔;link-param則包括了reltitle远舅、type等一系列元素屬性闰蛔,使用;分割。因此可以在響應頭中添加以下部分:

Link: </nextpage.js>; rel="prefetch"; as="script"

我們的demo使用了koa-static這個中間件图柏,只要做如下修改即可:

// app.js
app.use(serve(__dirname + '/public', {
    maxage: 1000 * 60 * 60,
    setHeaders: (res, path, stats) => {
        if (/index.html/.test(path)) {
            res.setHeader('Link', '</nextpage.js>; rel="prefetch"; as="script"');
        }
    }
}));

你會發(fā)現(xiàn)钞护,在訪問index.html時,瀏覽器就會向服務器請求nextpage.js這個頁面本身并“不需要”用到的資源爆办。

2.3. 向文檔動態(tài)添加link元素

link元素也支持我們通過js動態(tài)向文檔添加难咕。對于動態(tài)添加的RHL,瀏覽器也會對其應用Resource Hint策略距辆。添加link的方式和添加普通dom元素一致余佃。

var hint = document.createElement('link');
hint.rel = 'prefetch';
hint.as = 'script';
hint.href = '/nextpage.js';
document.head.appendChild(hint);

2.4. 改變已有l(wèi)ink元素的href屬性

當你改變頁面中原有RHL的href屬性(或者prefetch時的as屬性)時,會立即觸發(fā)對新資源的Resource Hint跨算。例如在如下代碼執(zhí)行后

var hint = document.querySelector('[rel="prefetch"]');
hint.href = './the.other.nextpage.js';

瀏覽器相當于接收到了新的Resource Hint“指示”爆土,并在合適的時機向服務端請求the.other.nextpage.js這個資源。注意诸蚕,當你修改as屬性時步势,也會觸發(fā)Resource Hint氧猬。

注意,如果你想通過修改已有link元素預獲取nextpage.html這個資源坏瘩,然后像下面這樣寫會觸發(fā)兩次請求盅抚。

var hint = document.querySelector('[rel="prefetch"]');
hint.as = 'html'; // 觸發(fā)第一次請求,再次請求./nextpage.js
hint.href = './nextpage.html'; // 請求./nextpage.html

2. Preload

既然提到了Resource Hint倔矾,那么不得不介紹一下與其類似的Preload妄均。在遇到需要Preload的資源時,瀏覽器會 立刻 進行預獲取哪自,并將結果放在內(nèi)存中丰包,資源的獲取不會影響頁面parse與load事件的觸發(fā)。直到再次遇到該資源的使用標簽時壤巷,才會執(zhí)行邑彪。

(Preload) Initiating an early fetch and separating fetching from resource execution.

例如下面這個HTML片段:

<head>
    <link rel="preload" href="./nextpage.js" as="script">
    <script type="text/javascript" src="./current.js"></script>
    <script type="text/javascript" src="./nextpage.js"></script>
<head>
image

瀏覽器首先會去獲取nextpage.js,然后獲取并執(zhí)行current.js胧华,最后寄症,遇到使用nextpage.js資源的script標簽時,將已經(jīng)獲取的nextpage.js執(zhí)行撑柔。由于我們會將script標簽置于body底部來保證性能瘸爽,因此可以考慮在head標簽中添加這些資源的Preload來加速頁面的加載與渲染您访。

更進一步铅忿,我們還可以監(jiān)聽Preload的情況,并觸發(fā)自定以操作

<script>
  function preloadFinished(e) { ... }
  function preloadError(e)  { ... }
</script>
<!-- listen for load and error events -->
<link rel="preload" href="app.js" as="script" onload="preloadFinished()" onerror="preloadError()">

正如在引言中所提到的灵汪,在過去如果我們想預加載一些資源都會用一些應用層面的技術手段檀训,但往往會遇到兩個問題:

  • 我們需要先獲取資源,然后在適當時執(zhí)行享言,但兩者并不易于分離
  • 無論哪種技術實現(xiàn)峻凫,都會帶來一定的性能與體驗損傷

Preload(包括前文提到的Prefetch等RHL)給我們帶來的價值就是從瀏覽器層面很好地將資源的加載與執(zhí)行分離了,并在瀏覽器層面來保證良好的性能體驗览露。

看到這里荧琼,也許你會疑惑,都是會預獲取資源差牛,都是資源的獲取與執(zhí)行分離命锄,那么Preload與Prefetch有什么區(qū)別呢?

這是它最容易與Prefetch混淆的地方偏化。在標準里有這么一段話解釋兩者區(qū)別:

The application can use the preload keyword to initiate <u>early, high-priority, and non-render-blocking</u> fetch of a CSS resource that can then be applied by the application at appropriate time

與Prefetch相比脐恩,Preload會強制瀏覽器立即獲取資源,并且該請求具有較高的優(yōu)先級(mandatory and high-priority)侦讨,因此建議對一些當前頁面會馬上用到資源使用Preload驶冒;相對的苟翻,Prefetch的資源獲取則是可選與較低優(yōu)先級的,其是否獲取完全取決于瀏覽器的決定骗污,適用于預獲取將來可能會用到的資源崇猫。

為了節(jié)省不必要的帶寬消耗,如果Preload的資源在3s內(nèi)沒有被使用身堡,Chrome控制臺會出現(xiàn)類似下圖的警告邓尤。這時你就需要仔細思考,該資源是否有必要Preload了贴谎。

image

更多Preload與Prefetch的細節(jié)差異可以看這里 —— Preload, Prefetch And Priorities in Chrome汞扎。

3. 寫在最后

本文介紹了如何使用Resource Hint(以及Preload)來提升頁面加載性能與體驗,簡單來說:

  • DNS Prefetch 可以幫助我們進行DNS預查詢擅这;
  • Preconnect 可以幫助我們進行預連接澈魄,例如在一些重定向技術中,可以讓瀏覽器和最終目標源更早建立連接仲翎;
  • Prefetch 可以幫助我們預先獲取所需資源(并且不用擔心該資源會被執(zhí)行)痹扇,例如我們可以根據(jù)用戶行為猜測其下一步操作,然后動態(tài)預獲取所需資源溯香;
  • Prerender 則會更進一步鲫构,不僅獲取資源,還會預加載(執(zhí)行)部分資源玫坛,因此如果我們Prerender下一個頁面结笨,打開該頁面時會讓用戶感覺非常流暢;
  • Preload 則像是 Prefetch的升級版湿镀,會強制立即高優(yōu)獲取資源炕吸,非常適合Preload(盡早獲取)一些關鍵渲染路徑中的資源勉痴。

雖然赫模,大部分PWA相關資料中并不會提及Resource Hint,但是正如我在第一篇文章中提到的

PWA本身其實是一個概念集合蒸矛,它不是指某一項技術瀑罗,而是通過一系列的Web技術與Web標準來優(yōu)化Web App的安全、性能和體驗雏掠。

Resource Hint顯然符合這一點斩祭。

我們不應該將PWA局限在Service Worker離線緩存、提醒通知這些常見的PWA內(nèi)容中磁玉,希望讀者也能開闊思維停忿,理解PWA背后的概念與思想。因此蚊伞,在后續(xù)文章中我也會介紹前端存儲(sessionStorage/localStorage/indexDB)席赂、HTTP/2.0以及PWA進展等相關內(nèi)容吮铭。

在下一篇里,我們會一起來學習Google開源的PWA離線工具集 —— workbox颅停。通過workbox谓晌,我們可以學習<u>各類離線策略</u>,并且了解一些生產(chǎn)環(huán)境中需要考慮的問題癞揉。部分開源PWA解決方案也是基于workbox進行封裝的纸肉。

《PWA學習與實踐》系列

參考資料

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芥牌,隨后出現(xiàn)的幾起案子烦味,更是在濱河造成了極大的恐慌,老刑警劉巖壁拉,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谬俄,死亡現(xiàn)場離奇詭異,居然都是意外死亡弃理,警方通過查閱死者的電腦和手機溃论,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痘昌,“玉大人钥勋,你說我怎么就攤上這事】睾海” “怎么了笔诵?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵返吻,是天一觀的道長姑子。 經(jīng)常有香客問我,道長测僵,這世上最難降的妖魔是什么街佑? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮捍靠,結果婚禮上沐旨,老公的妹妹穿的比我還像新娘。我一直安慰自己榨婆,他們只是感情好磁携,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著良风,像睡著了一般谊迄。 火紅的嫁衣襯著肌膚如雪闷供。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天统诺,我揣著相機與錄音歪脏,去河邊找鬼。 笑死粮呢,一個胖子當著我的面吹牛婿失,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播啄寡,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼豪硅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了挺物?” 一聲冷哼從身側響起舟误,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姻乓,沒想到半個月后嵌溢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蹋岩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年赖草,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剪个。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡秧骑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扣囊,到底是詐尸還是另有隱情乎折,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布侵歇,位于F島的核電站骂澄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏惕虑。R本人自食惡果不足惜坟冲,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一钦幔、第九天 我趴在偏房一處隱蔽的房頂上張望王暗。 院中可真熱鬧,春花似錦侧到、人聲如沸伟叛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至紊遵,卻和暖如春雹锣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背癞蚕。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工蕊爵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桦山。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓攒射,卻偏偏與公主長得像,于是被迫代替她去往敵國和親恒水。 傳聞我的和親對象是個殘疾皇子会放,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容