通過link的preload進行內容預載入

Preload 作為一個新的web標準,旨在提高性能和為web開發人員提供更細粒度的載入控制。Preload使開發者能夠自定義資源的載入邏輯,且無需忍受基於腳本的資源載入器帶來的性能損失。

<link> 標籤的rel屬性preload 可以在頁面中的header部分中申明一些資源的獲取請求,可以指定某些資源在頁面載入完成之後即可需要。對於這種即可需要的資源,我們希望在頁面載入的生命周期中更早的階段去獲取,在瀏覽器的主渲染機制進入前就進行預載入。這種方式可以使資源更早的得到並載入,且不會阻塞頁面的初步渲染,進而提示網頁的性能。下面就通過使用link 的preload機制來實現。

一、基礎使用

link 標籤最常用的形式就是用來載入CSS文件。

<link rel="stylesheet" href="index.css" />

但是這裡我們也可以使用 preload 作為 rel 的屬性值。這種方法就是將 link 塞入到一個預載入容器中,這個預載入器也可以用於我們需要的各種任意的資源。為了完成預載入,我們還需要指定 href 和as 屬性,指定需要唄預載入的資源路徑和類型。

下面是一個簡單的例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>preload</title>
  <link rel="preload" href="style.css" as="script" />
  <link rel="preload" href="main.js" as="script" />

  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <h1>Hello world</h1>
  <script src="main.js"></script>
</body>
</html>

這裡,我們先預載入了CSS和JS文件,在後面的頁面渲染中,一旦有使用到他們,就可以立即使用,這個例子可能看起來不是很明顯,但是預載入的好處是可以更清晰直觀的得到展示,對於更大的文件來說,比如在CSS中引用的資源,圖片或者字體,會更加的明顯。

preload 還有很多優勢,使用 as 來指定將要預載入的內容類型,可以使瀏覽器:

  • 更精準的優化資源載入的優先順序;
  • 匹配未來的載入需求,在適當的情況下,重複利用同一資源;
  • 為資源應用正確的 內容安全策略
  • 為資源設置正確的 Accept 請求頭。

有哪些類型的資源可以被預載入?

  • audio: 音頻文件
  • video: 影片文件
  • document: 一個將要被嵌入到 <frame> 或者 <iframe> 內部的 html 文檔
  • embed: 一個將要被嵌入到 <embed> 內部的資源
  • fetch: 哪些將要通過 fetch 和 XHR 請求來獲取的資源,比如一個 ArrayBuffer 或 JSON 文件
  • font: 字體文件
  • image: 圖片文件
  • object: 一個將會被嵌入到 <embed> 內部的文件
  • script: JavaScript 文件
  • style: 樣式表
  • track: WebVTT 文件
  • worker: 一個JavaScript 的 web worker 或者 shared worker

注意:這裡可以進一步閱讀 link-element-extensions  來了解關於這些屬性值,以及在其他 preload 方案中的特性細節,同時,關於 as 屬性的有效值的完整列表是有 Fetch 方案來制定的,可以查看 concept-request-destination 來了解更多。

 

二、包含一個 MIME 類型

link 元素還可以接受一個 type 屬性,這個屬性可以包含該元素所指向資源的 MIME 類型。在瀏覽器進行預載入的時候,瀏覽器將使用 type 屬性來判斷它是否支援這一資源,如果瀏覽器支援這一資源,才會開始預載入,否則對其忽略。

下面是個簡單的實例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>preload</title>
  <link rel="preload" href="media.mp4" as="video" type="video/mp4" />
</head>
<body>
  <h1>Hello world</h1>
  <video controls>
    <source src="media.mp4" type="video/mp4" />
    <source src="media.webm" type="video/webm" />
    <p>Your browser doesn't support HTML5 video.</p>
  </video>
</body>
</html>

在這個例子中,支援 MP4 格式的瀏覽器將會預載入並私用 mp4 資源,以使得影片播放器的表現儘可能流暢,而對於不支援mp4 的瀏覽器也能仍然能夠載入影片的 webm 資源,但是無法體驗到預載入帶來的好處。

 

三、獲取跨域

如果你已經有了一個可以正確工作的 CORS 設置,那麼你同樣可以預載入那些跨域的資源,只需要在 link 標籤中設置好 crossorigin 屬性即可。

注意:如果是預載入字體文件,那麼即使是非跨域的情況下,也需要設置這一屬性。具體的細節可以參考 font-fetching-requirements

例如以下的實例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>preload</title>
  <link rel="preload" href="fonts/font.eot" as="font" type="application/vnd.ms-fontobject" crossorigin="anonymous">
  <link rel="preload" href="fonts/font.woff2" as="font" type="font/woff2" crossorigin="anonymous">
  <link rel="preload" href="fonts/font.woff" as="font" type="font/woff" crossorigin="anonymous">
  <link rel="preload" href="fonts/font.ttf" as="font" type="font/ttf" crossorigin="anonymous">
  <link rel="preload" href="fonts/font.svg" as="font" type="image/svg+xml" crossorigin="anonymous">
</head>
<body>
  <h1>Hello world</h1>
</body>
</html>

type 屬性可以確保瀏覽器只獲取自己支援的資源。

 

四、結合媒體查詢的載入

link 標籤還有一個非常棒的屬性,是能夠接受一個 media 屬性,它可以接受 媒體類型 或者有效的 媒體查詢 作為屬性值,能夠使你響應式的預載入。

可以看下面的實例:

<head>
  <meta charset="utf-8">
  <title>Responsive preload example</title>

  <link rel="preload" href="bg-image-narrow.png" as="image" media="(max-width: 600px)">
  <link rel="preload" href="bg-image-wide.png" as="image" media="(min-width: 601px)">

  <link rel="stylesheet" href="main.css">
</head>

<body>
  <header>
    <h1>My site</h1>
  </header>

  <script>
    var mediaQueryList = window.matchMedia("(max-width: 600px)");
    var header = document.querySelector('header');

    if (mediaQueryList.matches) {
      header.style.backgroundImage = 'url(bg-image-narrow.png)';
    } else {
      header.style.backgroundImage = 'url(bg-image-wide.png)';
    }
  </script>
</body>

可以看到,link  上面包含了一個 media 屬性,因此,當符合該條件的情況下,資源才會被預載入。

 

五、動態載入

另一個慘景是,當你想要手動載入一個資源,但又不想立馬使用,我們可以使用腳本來動態生成預載入資源。

var preloadLink = document.createElement("link");
preloadLink.href = "myscript.js";
preloadLink.rel = "preload";
preloadLink.as = "script";
document.head.appendChild(preloadLink);

這就等於瀏覽器將預載入這個js文件,但並不會實際執行它。

如果想要執行,也可以通過腳本來控制:

var preloadedScript = document.createElement("script");
preloadedScript.src = "myscript.js";
document.body.appendChild(preloadedScript);

 

六、其他資源預載入機制

除了 preload 之外,還有一些別的預載入機制,但是都沒用 preload 更方便,更靈活。

1. <link rel=”prefetch”> 參考 prefetch 作用是告訴瀏覽器載入下一頁面所要用到的資源,因此該方法的優先順序非常低,也就是說該方法的作用是加速下一頁面的載入速度。

2. <link rel=”subresource”> 該方法只有Chrome支援,它的問題是開發者無法控制資源載入的優先順序,因此瀏覽器在處理此標籤時,優先順序很低,低到用了等於沒用。

 

七、兼容性

參考caniuse

很遺憾,IE11不支援,在firefox 中有一些局限性,僅可快取資源可以預載入。這包括以下值:script, style, image, video, audio, track, fetch, and font(不支援font/collection)

 

 

八、檢測特徵

前面所有的例子都是基於一種假設,那就是瀏覽器支援 preload ,至少實現了 樣式和腳本的預載入,但是如果假設不成立,一切都白搭。

為了判斷瀏覽器是否支援 preload,我們修改了 DOM 的規範從而能夠獲知 rel 支援那些值(是否支援 rel=『preload』)。

在MDN上沒有關於如何檢測瀏覽是否支援的程式碼,參考gitHub 上的一段程式碼

var DOMTokenListSupports = function(tokenList, token) {
  if (!tokenList || !tokenList.supports) {
    return;
  }
  try {
    return tokenList.supports(token);
  } catch (e) {
    if (e instanceof TypeError) {
      console.log("The DOMTokenList doesn't have a supported tokens list");
    } else {
      console.error("That shouldn't have happened");
    }
  }
};

var linkSupportsPreload = DOMTokenListSupports(document.createElement("link").relList, "preload");
if (!linkSupportsPreload) {
  // Dynamically load the things that relied on preload.
}

討論地址://github.com/w3c/preload/issues/7

 

九、Vue 中如何使用 preload

Vue-cli 中有提供相應的配置和使用方法,配合webpack的插件 preload-webpack-plugin 來使用。

 

十、能否使用 HTTP/2 Push 來完成 preload 的工作?

答案是不行,儘管這兩個有很多相似的地方,但這兩個最主要的還是功能互補,並不能相互取代。

HTTP/2 Push 的優勢是能夠主動推送資源給瀏覽器,也就是說,伺服器甚至不需要等到資源請求就能將資源推送給瀏覽器。

而 Preload 的優勢在於其載入過程是透明的,一旦資源載入完畢或出現異常,應用可以獲得事件通知。這一點是 HTTP/2 Push所不具備的。另外,Preload 還能載入第三方資源,但 HTTP/2 Push 不能。

此外,HTTP/2 Push 沒辦法將瀏覽器的快取和非全局 cookie (non-global cookie) 考慮進去。也就是說,伺服器推送的內容可能已經存在於客戶端的快取中,從而導致毫無意義的網路傳輸。(不過一份新的規範旨在解決該問題——cache digest specification,Github 上的 一個輕量級 Web服務H2O器實現了該功能,H2O在1.5版中引入了基於cookie 的cache-aware server push,原理是在首次 Server Push 完成後,在客戶端存一個指紋,服務端後續檢查到指紋存在時,先在指紋中查詢要 Push 的資源,沒查到才推送),但是非全局的 cookie就沒這麼好運了。對於這類型的資源,Preload 才是你的朋友。

Preload還有一個HTTP/2 Push 所不具備的能力是可以進行內容協商(content negotiation),也就是說如果你想通過 Client-Hints或者 HTTP 頭的 accept 資訊獲取最合適的資源格式,HTTP/2 Push 幫不了你。

 

參考:

//www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/

//developer.mozilla.org/zh-CN/docs/Web/HTML/Preloading_content