1. 影響頁面加載時長的 Top3 因素
- 頁面初載時,加載大量 JavaScript 腳本;
- 頁面初載時,加載大量 CSS 文件;
- 頁面初載時,加載大量網絡資源;
頁面加載的越久,頁面不可交互的時間就越長,用戶的體驗就越差。
2. 頁面優化的目標
- 頁面初載時,未壓縮的 JavaScript 腳本大小: <=200KB ;
- 頁面初載時,未壓縮的 CSS 資源大小: <=100KB ;
- HTTP 協議下,請求資源數: <=6 個 ;
- HTTP/2 協議下,請求資源數: <=20 個 ;
- 90% 的代碼覆蓋率(僅允許 10% 的未使用代碼);
只有 Chrome 能夠查看你的代碼覆蓋率。
遵從這個目標,應用將能在任何平台(PC,Mobile Phone...)都擁有良好的性能:
- 未來的網絡世界在移動端;
- 平均每個移動端網站需要花費 14 秒時間達到可交互狀態;
- 加載的代碼越少,頁面達到可交互的時間越短;
3. 查看代碼覆蓋率
- 打開 Chrome Dev Tool;
- 按下
Cmd + Shift + P
orCtrl + Shift + P
; - 輸入
Coverage
,選擇第一個出現的選項:
- 點擊面板上的
reload
按鈕,查看整個應用 JavaScript 的代碼覆蓋率:
4. 如何做到?
(1)代碼分割(code splitting)
什么是 code splitting?
將部分代碼在構建時轉變為異步加載的過程。
(1.1)代碼分割原理
代碼分割的核心是異步加載資源,而異步加載功能使用到 stage 3 規范:whatwg/loader。import()
允許你在瀏覽器端運行時動態獲取資源,雖然它存在以下一些問題:
- 安全問題;
- 瀏覽器支持問題;
(1.2)代碼分割的兩種類型
- 靜態的;
- “動態的”(在 webpack 中,並不是真的動態):“動態”是指你能夠在代碼運行時決定應該引入什么 JavaScript 模塊;
(1.3)靜態代碼分割
何時使用靜態的代碼分割?
- **你正在使用一個非常大的庫或框架 **:如果在頁面初始化時你不需要使用它,就不要在頁面初載時加載它;
- **任何臨時的資源 **:指不在頁面初始化時被使用,被使用后又會立即被銷毀的資源,例如模態框,對話框,tooltip等(任何一開始不顯示在頁面上的東西都可以有條件的加載);
- 路由 :既然用戶不會一下子看到所有頁面,那么只把當前頁面相關資源給用戶就是個明智的做法;
代碼示例
import Listener from './listeners.js'
const getModal = () => import('./src/modal.js')
Listener.on('didSomethingToWarrentModalBeingLoaded', () => {
// Async fetching modal code from a separate chunk
getModal().then((module) => {
const modalTarget = document.getElementById('Modal')
module.initModal(modalTarget)
})
})
注意:
- 在 Vue 中,可以直接使用
import()
關鍵字,在 React 中,使用react-loadable
完成同樣的事; - 每當你使用動態代碼分割時,函數將返回一個 Promise 對象;
(1.4)“動態”代碼分割
何時使用“動態”代碼分割?
- **A/B Test **:你不需要在代碼中引入不需要的 UI 代碼;
- **主題 **:動態加載相應的主題;
- 為了方便 :本質上,你可以用靜態代碼分割代替“動態”代碼分割,但是后者比前者擁有更少的代碼量;
代碼示例
const getTheme = (themeName) => import(`./src/themes/${themeName}`)
// using `import()` 'dynamically'
if (window.feeling.stylish) {
getTheme('stylish').then((module) => {
module.applyTheme()
})
} else if (window.feeling.trendy) {
getTheme('trendy').then((module) => {
module.applyTheme()
})
}
這背后的原理是,webpack 將目錄中所有可以分離的 JavaScript 文件都生成了被稱為 contextModule
的模塊,所以本質上仍然是靜態的。
(2)魔術注釋
魔術注釋技術是為代碼分割技術服務的,它表現為在 import
關鍵字前使用指定注釋來控制 webpack 生成的分割后代碼片段。
代碼示例
import (
/* webpackChunkName: "my-chunk-name" */
'./footer'
)
同時,你需要在 webapck.config.js 中添加配置:
{
output: {
filename: "bundle.js",
chunkFilename: "[name].lazy-chunk.js"
}
}
(2.1)webpack Modes
webpack 提供了一些注釋讓我們能夠選擇異步加載的模塊應該被怎樣組織:
import (
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: lazy */
'./someModule'
)
webpackMode
的默認值為 lazy
,指所有異步模塊都會被單獨抽離成單一的 chunk,通過使用 lazy-once
值,可以將所有的異步加載模塊放在同一個 chunk 中。
(2.2)Prefetch & Preload
通過開啟 prefetch
,我們可以通過使用 <link rel="prefetch>"
的特性,讓瀏覽器在空閑時幫我們預先加載我們的異步資源,這有助於提升應用性能。
import(
/* webpackPrefetch: true */
'./someModule'
)
注意 :當你確保你的異步代碼在未來一定會用到時,再開啟該功能。