用 Webpack 解決應用性能問題


1. 影響頁面加載時長的 Top3 因素

  1. 頁面初載時,加載大量 JavaScript 腳本;
  2. 頁面初載時,加載大量 CSS 文件;
  3. 頁面初載時,加載大量網絡資源;

頁面加載的越久,頁面不可交互的時間就越長,用戶的體驗就越差。


2. 頁面優化的目標

  1. 頁面初載時,未壓縮的 JavaScript 腳本大小: <=200KB ;
  2. 頁面初載時,未壓縮的 CSS 資源大小: <=100KB ;
  3. HTTP 協議下,請求資源數: <=6 個 ;
  4. HTTP/2 協議下,請求資源數: <=20 個 ;
  5. 90% 的代碼覆蓋率(僅允許 10% 的未使用代碼);

只有 Chrome 能夠查看你的代碼覆蓋率。

遵從這個目標,應用將能在任何平台(PC,Mobile Phone...)都擁有良好的性能:

  1. 未來的網絡世界在移動端;
  2. 平均每個移動端網站需要花費 14 秒時間達到可交互狀態;
  3. 加載的代碼越少,頁面達到可交互的時間越短;

3. 查看代碼覆蓋率

  1. 打開 Chrome Dev Tool;
  2. 按下 Cmd + Shift + P or Ctrl + Shift + P ;
  3. 輸入 Coverage ,選擇第一個出現的選項:

Xnip2019-06-18_10-48-49.jpg

  1. 點擊面板上的 reload 按鈕,查看整個應用 JavaScript 的代碼覆蓋率:

Xnip2019-06-18_10-50-46.jpg


4. 如何做到?

(1)代碼分割(code splitting)

什么是 code splitting?
將部分代碼在構建時轉變為異步加載的過程。

(1.1)代碼分割原理

代碼分割的核心是異步加載資源,而異步加載功能使用到 stage 3 規范:whatwg/loader。
import() 允許你在瀏覽器端運行時動態獲取資源,雖然它存在以下一些問題:

  1. 安全問題;
  2. 瀏覽器支持問題;

(1.2)代碼分割的兩種類型

  1. 靜態的;
  2. “動態的”(在 webpack 中,並不是真的動態):“動態”是指你能夠在代碼運行時決定應該引入什么 JavaScript 模塊;

(1.3)靜態代碼分割

何時使用靜態的代碼分割?
  1. **你正在使用一個非常大的庫或框架 **:如果在頁面初始化時你不需要使用它,就不要在頁面初載時加載它;
  2. **任何臨時的資源 **:指不在頁面初始化時被使用,被使用后又會立即被銷毀的資源,例如模態框,對話框,tooltip等(任何一開始不顯示在頁面上的東西都可以有條件的加載);
  3. 路由 :既然用戶不會一下子看到所有頁面,那么只把當前頁面相關資源給用戶就是個明智的做法;

代碼示例
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)
  })
})

注意:

  1. 在 Vue 中,可以直接使用 import() 關鍵字,在 React 中,使用 react-loadable 完成同樣的事;
  2. 每當你使用動態代碼分割時,函數將返回一個 Promise 對象;

(1.4)“動態”代碼分割

何時使用“動態”代碼分割?
  1. **A/B Test **:你不需要在代碼中引入不需要的 UI 代碼;
  2. **主題 **:動態加載相應的主題;
  3. 為了方便 :本質上,你可以用靜態代碼分割代替“動態”代碼分割,但是后者比前者擁有更少的代碼量;

代碼示例
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'
)

注意 :當你確保你的異步代碼在未來一定會用到時,再開啟該功能。


5. 參考鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM