使用預取和預加載是網站性能和用戶體驗提升的一個很好的途徑,本文介紹了使用 prefetch 和 prefetch 進行預取和預加載的方法,並使用 webpack 進行實現
Link 的鏈接類型
<link>
標簽的 rel
屬性可以定義鏈接類型,prefetch
是其中的一種,與 href
配合使用可以預取或預加載對應資源
<link rel="prefetch" herf="URL">
preload
是另外一種類型,同樣用 href 定義資源地址,但其處理預取外,還會對資源進行解析,所以還要增加屬性 as,說明資源的類型
<link rel="preload" href="URL" as="MIME_TYPE">
預取資源
prefetch
表示用戶在接下來的瀏覽中(例如在下一個頁面),有可能用到對應資源,提示瀏覽器要在閑時獲取對應資源
先新建文件夾 prefetch-preload-demo(本文所有代碼將在此創建),安裝相關依賴,並新建文件夾 static
mkdir prefetch-preload-demo
cd prefetch-preload-demo
npm init -y
npm i -D http-server
mkdir static
在 static 中創建 prefetch.html
, main.js
和 script.js
prefetch.html
定義了一個 rel
為 prefetch
的鏈接
<html>
<head>
<title>Prefetch</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="prefetch" href="script.js">
</head>
<body>
<script src="main.js"></script>
</body>
</html>
main.js
創建了一個按鈕,並綁定了點擊事件
let button = document.createElement('button');
button.innerHTML = 'Add Script';
button.addEventListener('click', e => {
let script = document.createElement("script");
script.src = "script.js";
document.head.appendChild(script);
});
document.body.appendChild(button);
script.js
只是簡單的打印了一下
console.log('script run');
運行服務器(也可在 package.json
中增加 server
腳本)
npx http-server
訪問 http://localhost:8080 並導航至 static
中,點擊 prefetch.html,或者直接訪問線上頁面,初始狀態下,查看控制台的網絡選項卡下的內容如下(不要勾選 Disable Cache
,點擊右側齒輪,勾選 Use large request rows
)
script.js
被 fetch 下來,size 列的兩個數字,275 B 表示下載的字節大小,0 B 表示解析的字節大小(即目前並沒有解析)- 控制台是空的,即腳本沒有運行
點擊頁面上的 Add Script
,會在頁面增加地址為 script.js
的 <script>
標簽,此時網絡選項卡會增加以下內容
- 下載字節量為
(prefetch cache)
,即直接從預取緩存獲取資源,下面的解析后的字節不再為 0 - 控制台打印出腳本中的調試內容,即這時腳本才被解析並運行
預加載資源
preload
表示用戶在當前的瀏覽中(往往是在當前頁面),極有可以可能用到對應資源,提示瀏覽器要優先獲取對應資源
將 prefetch.html 的 link 標簽的 prefetch 改為 preload
,並增加資源類型 as
為 script
,即得 preload.html
<link rel="preload" href="script.js" as="script">
訪問本地服務器對應的 prefetch.html
,或者直接訪問線上頁面,初始狀態下,查看控制台的網絡選項卡下的內容如下
script.js
被優先下載, size 列的解壓字節不再為 0,即preload
除了把腳本下載了下來,還進行了解析- 控制台目前仍為空,即腳本雖然被解析,但並沒有運行
點擊 Add Script
,網絡選項卡並沒有增加任何記錄,但是控制台輸出了腳本的打印內容
- 因為腳本已經解析完成,所以連從緩存獲取都不需要了,直接運行即可
- 如果沒有在 3 秒內點擊
Add Script
,控制台會進行警告,因為沒有及時使用應該優先加載的資源
The resource https://chanvinxiao.com/demo/html/script.js was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate
as
value and it is preloaded intentionally.
webpack 的相關處理
運行以下命令安裝相關依賴,並新建文件夾 src
npm i -D webpack webpack-cli html-webpack-plugin preload-webpack-plugin@3.0.0-beta.4
mkdir src
- PreloadWebpackPlugin 的當前版本 2.x 與 webpack 當前版本 4.x 不兼容,所以需要指定版本號為最新的 3.x beta
將 main.js
與 script.js
復制到 src 中,並將 main.js
的點擊事件處理更新為
button.addEventListener('click', e => {
import(/* webpackChunkName: "script" */ './script.js');
});
- import() 為動態加載腳本,webpack 會生成類似以上動態創建
script
標簽的代碼 - import 里的注釋為特殊含義的魔法注釋,如果不設置 webpackChunkName,加載的腳本將被按數字次序命名
增加 webpack.config.js
如下
const HtmlWebpackPlugin = require('html-webpack-plugin');
const PreloadWebpackPlugin = require('preload-webpack-plugin');
module.exports = {
entry: './src/main.js',
plugins: [
new HtmlWebpackPlugin({
filename: 'preload.html'
}),
new PreloadWebpackPlugin()
]
}
- HtmlWebpackPlugin 將自動生成相應的 html 文件,默認為 index.html,這里通過設置
filename
選項更改 - PreloadWebpackPlugin 為 HtmlWebpackPlugin 的插件,默認為其動態加載資源增加鏈接類型為
preload
的link
標簽,其as
的值可根據后綴自動判斷
PreloadWebpackPlugin 也支持 prefetch,需要增加 rel
選項為 prefetch
new HtmlWebpackPlugin({
filename: 'prefetch.html'
}),
new PreloadWebpackPlugin({
rel: 'prefetch'
})
不過要同時生成 preload.html 和 prefetch.html,需要在對應的 PreloadWebpackPlugin 中設置 excludeHtmlNames
排除對方,否則會同時產生 preload 和 prefetch 的 link 標簽
new HtmlWebpackPlugin({
filename: 'preload.html'
}),
new HtmlWebpackPlugin({
filename: 'prefetch.html'
}),
new PreloadWebpackPlugin({
excludeHtmlNames: ['prefetch.html']
}),
new PreloadWebpackPlugin({
rel: 'prefetch',
excludeHtmlNames: ['preload.html']
})
構建文件(也可在 package.json
中增加 build
腳本)
npx webpack
dist
文件夾中將生成 prefetch.html 和 preload.html,訪問本地服務器對應地址,即可得到與以上靜態頁面同樣的效果
總結
此文使用靜態頁面和 webpack 打包兩種方式演示了預取和預加載的實現,完整代碼見 GitHub,主要技術點如下:
- ELEMENT.appendChild 動態創建腳本
- import() 動態加載腳本並設置魔法注釋
- html-webpack-plugin 及其插件的配置