摘要:沒錯,正如你所看到的, IE 瀏覽器目前並不支持這一特性,但這並不意味着你的異步加載功能在 IE 瀏覽會失效(那太可怕了 ‍♂️),實際上,Webpack 底層幫你將異步加載的代碼抽離成一份新的文件,並在你需要時通過 JSONP 的方式去獲取文件資源,因此,你可以在任何瀏覽器上實現代碼的異步加載,並且在將來所有瀏覽器都實現 import() 方法時平滑過渡,cool。代碼分割技術的核心是 「異步加載資源」 ,可喜的是,瀏覽器允許我們這麼做,W3C stage 3 規範:whatwg/loader  對其進行了定義:你可以通過 import() 關鍵字讓瀏覽器在程序執行時異步加載相關資源。

者|李斌(空堂) 

品|阿里巴巴新零售淘系技術部

導讀: 經常有同學感嘆不知道怎麼優化項目,大家不妨嘗試下在項目中引入代碼分割的方式提升性能。

Web 應用性能優化的關鍵

關於 Web 應用性能優化,有一點是毫無疑問的:「頁面加載越久,用戶體驗就越差」。我們幾乎可以說 Web 應用性能優化的關鍵之處就在於: 減少頁面初載時,所需加載資源的「數量」和「體積」。

那麼當所需加載的資源數量到達多少或資源大小小於多少,我們纔可以自信地宣稱我們的 Web 應用擁有出色的性能呢?

下面是我給出的一個參考值,該參考值考慮到了移動端與國外等多種訪問環境:

  1. 頁面初載時,所有未壓縮的 JavaScript 腳本大小:<=200KB;

  1. 頁面初載時,所有未壓縮的 CSS 資源大小:<=100KB;

  1. HTTP 協議下,請求資源數:<=6 個;

  1. HTTP/2 協議下,請求資源數:<=20 個 ;

  1. 90%的代碼利用率(也就是說,僅允許 10% 的未使用代碼);

或許你會覺得這個標準有點過於苛刻了,是有一點點,但爲了創建出高性能的 Web 應用,你的實際資源加載情況應該儘可能靠近這個目標。

如何查看代碼利用率

也許你注意到了,我們上一節最後提到的一個指標是「代碼利用率」,你可能是第一次聽說這個概念,這裏我解釋一下它的計算方式:

代碼利用率 = 你頁面中實際被執行的代碼 / 你頁面中引入的代碼 * 100%

你可能會困惑在實際開發中如何得到這個值,別擔心,通過使用 Chrome 開發者工具(很遺憾,目前只有 Chrome 支持這一功能),你就可以迅速對你的 Web 應用進行分析,得到當前頁面下的代碼利用率狀態,步驟如下:

  1. 打開 Chrome Dev Tool;

  1. 按下 Cmd + Shift + P or Ctrl + Shift + P ;

  1. 輸入 Coverage,並選擇第一個出現的選項;

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

提高代碼使用率的關鍵技術 — 代碼分割(code splitting)

▐   什麼是「代碼分割」(code splitting)?

代碼分割是指,將腳本中無需立即調用的代碼在代碼構建時轉變爲異步加載的過程。

在 Webpack 構建時,會避免加載已聲明要異步加載的代碼,異步代碼會被單獨分離出一個文件,當代碼實際調用時被加載至頁面。

▐   代碼分割的原理

代碼分割技術的核心是 「異步加載資源」 ,可喜的是,瀏覽器允許我們這麼做,W3C stage 3 規範:whatwg/loader  對其進行了定義:你可以通過 import() 關鍵字讓瀏覽器在程序執行時異步加載相關資源。

沒錯,正如你所看到的, IE 瀏覽器目前並不支持這一特性,但這並不意味着你的異步加載功能在 IE 瀏覽會失效(那太可怕了 ‍♂️),實際上,Webpack 底層幫你將異步加載的代碼抽離成一份新的文件,並在你需要時通過 JSONP 的方式去獲取文件資源,因此,你可以在任何瀏覽器上實現代碼的異步加載,並且在將來所有瀏覽器都實現 import() 方法時平滑過渡,cool!:+1:

▐   代碼分割的類型

代碼分割可以分爲「靜態分割」和「“動態”分割」兩種方式, 注意這裏打了引號的 “動態”,因爲實際上它並不意味着異步調用的代碼是 “動態” 生成的,我們之後會看到 Webpack 是如何做到這一點的,在那之前,讓我們先看看「靜態代碼分割」。

靜態代碼分割

靜態代碼分割是指:在代碼中明確聲明需要異步加載的代碼。

下面 :point_down: 的代碼說明了我們應該如何使用這一技術:

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)  })})
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)  
      })
  }
)

正如你所看到的: 每當你調用一個聲明瞭異步加載代碼的變量時,它總是返回一個 Promise 對象。

:warning: 注意:在 Vue 中,可以直接使用 import() 關鍵字做到這一點,而在 React 中,你需要使用 react-loadable 去完成同樣的事。

最後,讓我們談談何時使用靜態代碼分割技術,這一技術適合以下的場景:

1. 你正在使用一個非常大的庫或框架:

如果在頁面初始化時你不需要使用它,就不要在頁面初載時加載它;

2. 任何臨時的資源:指不在頁面初始化時被使用,被使用後又會立即被銷燬的資源,例如模態框,對話框,tooltip 等(任何一開始不顯示在頁面上的東西都可以有條件的加載);

3. 路由:既然用戶不會一下子看到所有頁面,那麼只把當前頁面相關資源給用戶就是個明智的做法;

好了,現在你掌握了靜態代碼分割技術,現在讓我們看看什麼是「“動態代”代碼分割」技術。

動態代碼分割

動態代碼分割是指:在代碼調用時根據當前的狀態,「動態地」異步加載對應的代碼塊。

下面 :point_down: 的代碼說明了它具體是如何被實現的:

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 會在構建時將你聲明的目錄下的所有可能分離的代碼都抽象爲一個文件(這被稱爲 contextModule 模塊),因此無論你最終聲明瞭調用哪個文件,本質上就和靜態代碼分割一樣,在請求一個早已準備好的,靜態的文件。

下面是一些使用 “動態” 代碼分割技術的場景:

1. A/B Test:你不需要在代碼中引入不需要的 UI 代碼;

2. 加載主題:根據用戶的設置,動態加載相應的主題;

3. 爲了方便 :本質上,你可以用靜態代碼分割代替「動態」代碼分割,但是後者比前者擁有更少的代碼量;

魔術註釋

魔術註釋是由 Webpack 提供的,可以爲代碼分割服務的一種技術。通過在 import 關鍵字後的括號中使用指定註釋,我們可以對代碼分割後的 chunk 有更多的控制權,讓我們看一個例子:

// index.js
import (
  /* webpackChunkName: “my-chunk-name” */
  './footer'
)

同時,也要在 webpack.config.js 中做一些改動:

 
// webpack.config.js
{
  output: {
    filename: “bundle.js”,
    chunkFilename: “[name].lazy-chunk.js”
  }
}

通過這樣的配置,我們可以對分離出的 chunk 進行命名,這對於我們 debug 而言非常方便。

▐    Webpack Modes

除了上面提到過得 webpackChunkName 註釋外,Webpack 還提供了一些其他註釋讓我們能夠對異步加載模塊擁有更多控制權,例如下方這個例子:

 
import (
  /* webpackChunkName: “my-chunk-name” */
  /* webpackMode: lazy */
  './someModule'
)

webpackMode 的默認值爲 lazy 它會使所有異步模塊都會被單獨抽離成單一的 chunk,若設置該值爲 lazy-once,Webpack 就會將所有帶有標記的異步加載模塊放在同一個 chunk 中。

▐    Prefetch or Preload

通過添加 webpackPrefetch 魔術註釋,Webpack 令我們可以使用與 <link rel=“prefetch”> 相同的特性。 讓瀏覽器會在 Idle 狀態時預先幫我們加載所需的資源,善用這個技術可以使我們的應用交互變得更加流暢。

 
import(
  /* webpackPrefetch: true */
  './someModule'
)

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

小結

至此,我們講解了所有有關 Code Splitting 的知識,並告訴你了一些神奇的「魔法註釋」讓你對分割後的代碼有更多的掌控,希望你能將上面的技術靈活運用在你的項目中,開發出更加激動人心,如絲般順滑的應用!

Good Luck!:raised_hands:

你累死累活做業務,績效還不怎麼樣,我只能幫你到這了……

我在阿里招前端,我該怎麼幫你?

在看點這裏

相關文章