
作者|李斌(空堂)
出品|阿裡巴巴新零售淘系技術部
導讀:經常有同學感歎不知道怎麼優化項目,大家不妨嘗試下在項目中引入代碼分割的方式提升性能。
Web 應用性能優化的關鍵
關于 Web 應用性能優化,有一點是毫無疑問的:「頁面加載越久,使用者體驗就越差」。我們幾乎可以說 Web 應用性能優化的關鍵之處就在于:減少頁面初載時,所需加載資源的「數量」和「體積」。
那麼當所需加載的資源數量到達多少或資源大小小于多少,我們才可以自信地宣稱我們的 Web 應用擁有出色的性能呢?
下面是我給出的一個參考值,該參考值考慮到了移動端與國外等多種通路環境:
1、頁面初載時,所有未壓縮的 JavaScript 腳本大小:<=200KB;
2、頁面初載時,所有未壓縮的 CSS 資源大小:<=100KB;
3、HTTP 協定下,請求資源數:<=6 個;
4、HTTP/2 協定下,請求資源數:<=20 個 ;
5、90%的代碼使用率(也就是說,僅允許 10% 的未使用代碼);
或許你會覺得這個标準有點過于苛刻了,是有一點點,但為了建立出高性能的 Web 應用,你的實際資源加載情況應該盡可能靠近這個目标。
如何檢視代碼使用率
也許你注意到了,我們上一節最後提到的一個名額是「代碼使用率」,你可能是第一次聽說這個概念,這裡我解釋一下它的計算方式:
代碼使用率 = 你頁面中實際被執行的代碼 / 你頁面中引入的代碼 * 100%
你可能會困惑在實際開發中如何得到這個值,别擔心,通過使用 Chrome 開發者工具(很遺憾,目前隻有 Chrome 支援這一功能),你就可以迅速對你的 Web 應用進行分析,得到目前頁面下的代碼使用率狀态,步驟如下:
1、打開 Chrome Dev Tool;
2、按下 Cmd + Shift + P or Ctrl + Shift + P ;
3、輸入 Coverage,并選擇第一個出現的選項;
4、點選面闆上的 reload 按鈕,檢視整個應用 JavaScript 的代碼使用率;
提高代碼使用率的關鍵技術 — 代碼分割(code splitting)
▐ 什麼是「代碼分割」(code splitting)?
代碼分割是指,将腳本中無需立即調用的代碼在代碼建構時轉變為異步加載的過程。
在 Webpack 建構時,會避免加載已聲明要異步加載的代碼,異步代碼會被單獨分離出一個檔案,當代碼實際調用時被加載至頁面。
▐ 代碼分割的原理
代碼分割技術的核心是「異步加載資源」,可喜的是,浏覽器允許我們這麼做,W3C stage 3 規範:whatwg/loader 對其進行了定義:你可以通過 import() 關鍵字讓浏覽器在程式執行時異步加載相關資源。
沒錯,正如你所看到的, IE 浏覽器目前并不支援這一特性,但這并不意味着你的異步加載功能在 IE 浏覽會失效(那太可怕了 🤦♂️),實際上,Webpack 底層幫你将異步加載的代碼抽離成一份新的檔案,并在你需要時通過 JSONP 的方式去擷取檔案資源,是以,你可以在任何浏覽器上實作代碼的異步加載,并且在将來所有浏覽器都實作 import() 方法時平滑過渡,cool!👍
▐ 代碼分割的類型
代碼分割可以分為「靜态分割」和「“動态”分割」兩種方式,注意這裡打了引号的 “動态”,因為實際上它并不意味着異步調用的代碼是 “動态” 生成的,我們之後會看到 Webpack 是如何做到這一點的,在那之前,讓我們先看看「靜态代碼分割」。
靜态代碼分割
靜态代碼分割是指:在代碼中明确聲明需要異步加載的代碼。
下面 👇 的代碼說明了我們應該如何使用這一技術:
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 對象。
⚠️ 注意:在 Vue 中,可以直接使用 import() 關鍵字做到這一點,而在 React 中,你需要使用 react-loadable 去完成同樣的事。
最後,讓我們談談何時使用靜态代碼分割技術,這一技術适合以下的場景:
- 你正在使用一個非常大的庫或架構:如果在頁面初始化時你不需要使用它,就不要在頁面初載時加載它;
- 任何臨時的資源:指不在頁面初始化時被使用,被使用後又會立即被銷毀的資源,例如模态框,對話框,tooltip 等(任何一開始不顯示在頁面上的東西都可以有條件的加載);
- 路由:既然使用者不會一下子看到所有頁面,那麼隻把目前頁面相關資源給使用者就是個明智的做法;
好了,現在你掌握了靜态代碼分割技術,現在讓我們看看什麼是「“動态代”代碼分割」技術。
動态代碼分割
動态代碼分割是指:在代碼調用時根據目前的狀态,「動态地」異步加載對應的代碼塊。
下面 👇 的代碼說明了它具體是如何被實作的:
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 子產品),是以無論你最終聲明了調用哪個檔案,本質上就和靜态代碼分割一樣,在請求一個早已準備好的,靜态的檔案。
下面是一些使用 “動态” 代碼分割技術的場景:
- A/B Test:你不需要在代碼中引入不需要的 UI 代碼;
- 加載主題:根據使用者的設定,動态加載相應的主題;
- 為了友善 :本質上,你可以用靜态代碼分割代替「動态」代碼分割,但是後者比前者擁有更少的代碼量;
魔術注釋
魔術注釋是由 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 令我們可以使用與 相同的特性。讓浏覽器會在 Idle 狀态時預先幫我們加載所需的資源,善用這個技術可以使我們的應用互動變得更加流暢。
import(
/* webpackPrefetch: true */
'./someModule'
)
⚠️ 注意:你確定你的代碼在未來一定會用到時,再開啟該功能。
小結
至此,我們講解了所有有關 Code Splitting 的知識,并告訴你了一些神奇的「魔法注釋」讓你對分割後的代碼有更多的掌控,希望你能将上面的技術靈活運用在你的項目中,開發出更加激動人心,如絲般順滑的應用!
Good Luck!🙌
關注「淘系技術」微信公衆号,一個有溫度有内容的技術社群~