JS?加載性能Tree?Shaking優(yōu)化詳解
正文
隨著 web 應(yīng)用復(fù)雜性增加,JS 代碼文件的大小也在不斷的攀升,截住 2021年9月,在 httparchive 上有統(tǒng)計(jì)顯示——在移動(dòng)設(shè)備上 JS 傳輸大小大約為 447 KB,桌面端 JS 傳輸大小大約為 495 KB,注意這僅僅是在網(wǎng)絡(luò)中傳輸?shù)?JS 文件大小,JS 的實(shí)際大小要比傳輸大小大很多。
上圖是下載和運(yùn)行JavaScript的過(guò)程。
即使 JS 的傳輸大小被壓縮為 300 KB,但仍然有 900 KB 的 JS 代碼需要被瀏覽器解析、編譯和執(zhí)行。圖像一旦被下載,瀏覽器只需要花費(fèi)相對(duì)瑣碎的解碼時(shí)間,與圖像不同的是,JS 必須被解析、編譯,最終執(zhí)行,這使得處理 JS 比處理其他類(lèi)型的資源更耗時(shí)。
上圖是瀏覽器解析/編譯 170 KB的 JS 的處理成本與同等大小的 JPEG 的解碼時(shí)間。JS 引擎的性能在不斷被改進(jìn),改進(jìn)網(wǎng)站 JS 性能也是開(kāi)發(fā)者要做的事情。Code Splitting 是優(yōu)化 JS 性能的技術(shù)之一,但是它不能減少應(yīng)用程序的 JS 代碼的總大小,在這里我們使用 Tree Shaking 來(lái)減小 js 代碼的大小。
什么是 Tree Shaking
您可以將應(yīng)用程序想象成一棵樹(shù)。您實(shí)際使用的源代碼和庫(kù)表示樹(shù)中綠色的活葉子,死代碼表示秋天時(shí)樹(shù)上棕色的枯葉,為了除掉枯葉,你必須搖動(dòng)樹(shù),讓它們掉下。Tree Shaking 是指消除死代碼,下面通過(guò)一個(gè)應(yīng)用程序演示了這個(gè)概念。使用 ES6 靜態(tài)模塊語(yǔ)法導(dǎo)入依賴(lài)項(xiàng):
// 在這里導(dǎo)入了所有的數(shù)組處理方法 import arrayUtils from "array-utils";
在應(yīng)用最初的時(shí)候,依賴(lài)項(xiàng)可能很少,隨著功能逐漸增加,依賴(lài)項(xiàng)也會(huì)增加,更糟糕的是,舊的依賴(lài)項(xiàng)不再使用,但可能不會(huì)從代碼庫(kù)中刪除,最終的結(jié)果是,應(yīng)用程序帶有大量未使用的 JS 代碼,Tree Shaking 解決了這個(gè)問(wèn)題,它通過(guò)分析我們?cè)谖募惺褂玫?ES6 靜態(tài)模塊語(yǔ)句來(lái)分析哪些模塊被導(dǎo)入了:
// 只導(dǎo)入部分方法
import { unique, implode, explode } from "array-utils";
這個(gè)導(dǎo)入示例與前一個(gè)示例的區(qū)別在于,本示例只導(dǎo)入模塊的特定部分,而不是從“array-utils”模塊導(dǎo)入所有內(nèi)容。
尋找 Tree Shaking 的機(jī)會(huì)
為了便于說(shuō)明,這里有一個(gè)使用 webpack 的單頁(yè)應(yīng)用程序示例來(lái)演示 Tree Shaking 是如何工作的。界面如下:
這個(gè)程序打包之后的代碼被分為兩文件,如下:
在任何應(yīng)用程序中,你需要從靜態(tài)導(dǎo)入語(yǔ)句尋找 Tree Shaking 的機(jī)會(huì),在示例程序中(FilterablePedalList.js)你將看到這樣一行導(dǎo)入語(yǔ)句:
import * as utils from "../../utils/utils";
在文件中這樣的導(dǎo)入語(yǔ)句應(yīng)該引起你的注意,它的意思是:從 utils 模塊導(dǎo)入所有內(nèi)容。問(wèn)題是:你真的用到所有的內(nèi)容了嗎?現(xiàn)在我們來(lái)檢查 FilterablePedalList.js 中究竟使用了 utils 模塊中的那些方法,通過(guò)檢索發(fā)現(xiàn)只使用了 utils.simpleSort:
if (this.state.sortBy === "model") {
// Simple sort gets used here...
json = utils.simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
// ..and here...
json = utils.simpleSort(json, "type", this.state.sortOrder);
} else {
// ..and here.
json = utils.simpleSort(json, "manufacturer", this.state.sortOrder);
}
我們現(xiàn)在開(kāi)始做 Tree Shaking 優(yōu)化
防止 Babel 將 ES6 模塊轉(zhuǎn)換為 CommonJS 模塊
在大型應(yīng)用中 Babel 是不可或缺的工具,但是它會(huì)讓 Tree Shaking 變得困難。如果你正在使用 babel-preset-env,它會(huì)自動(dòng)為你將 ES6 模塊轉(zhuǎn)換為更廣泛兼容的 CommonJS 模塊,即:用 require 代替 import。對(duì)于 CommonJS 模塊而言做 Tree Shaking 優(yōu)化非常困難,這是因?yàn)?CommonJS 模塊是動(dòng)態(tài)的,在構(gòu)建階段 bundlers 不容易分析出 CommonJS 模塊導(dǎo)出了什么和導(dǎo)入了什么。為了避免 babel-preset-env 將 ES6 模塊轉(zhuǎn)換成 CommonJS 模塊,我們可以這么做:
{
"presets": [
["env", {
"modules": false
}]
]
}
在你的 Babel -preset-env 配置中簡(jiǎn)單指定"modules": false 就可以讓 Babel 按照我們想要的方式運(yùn)行,這允許 webpack 分析你的依賴(lài)樹(shù)并擺脫那些未使用的依賴(lài)。
留意 side effects
當(dāng)函數(shù)修改了它作用域之外的內(nèi)容,我們就認(rèn)為這個(gè)函數(shù)有 side effects。side effects 也適用于ES6模塊,在你做 Tree Shaking 的時(shí)候你需要留意你的模塊是否有 side effects(副作用),如果模塊接受可預(yù)測(cè)的輸入,并輸出同樣可預(yù)測(cè)的輸出,而不修改其自身范圍之外的任何內(nèi)容,我們就認(rèn)為這個(gè)模塊沒(méi)有 side effects,對(duì)于沒(méi)有 side effects 的模塊你可以放心的做 Tree Shaking。在這里我舉兩個(gè) side effects 例子:
import './index.module.scss'; import './assets/shoot.svg';
如果在某個(gè)模塊中出現(xiàn)了上面這樣的 import 語(yǔ)句,則認(rèn)為這個(gè)模塊有 side effects,這時(shí)在做 Tree Shaking 的時(shí)候你需要小心一些。默認(rèn)情況下,webpack 在做 Tree Shaking 的時(shí)候,會(huì)認(rèn)為 index.module.scss 與 assets/shoot.svg 沒(méi)有被用到,所以它們會(huì)被移除,如果不想被移除,可以告訴 webpack 它們是 side effects:
{
"name": "webpack-tree-shaking-example",
"version": "1.0.0",
"sideEffects": [
"./index.module.scss",
"./assets/shoot.svg"
]
}
在項(xiàng)目的 package.json 中配置 sideEffects 字段。sideEffects 字段也能為 false,這表示項(xiàng)目中不存在有 side effects 的模塊。
只導(dǎo)入你需要的
現(xiàn)在我們已經(jīng)告訴 babel 不要將 ES6 模塊轉(zhuǎn)成 CommonJS 模塊,現(xiàn)在我們需要對(duì)導(dǎo)入語(yǔ)法做一點(diǎn)微調(diào),只從 utils 模塊中引入我們需要的函數(shù)嗎,在本指南的例子中,我們只需要 simpleSort:
import { simpleSort } from "../../utils/utils";
if (this.state.sortBy === "model") {
json = simpleSort(json, "model", this.state.sortOrder);
} else if (this.state.sortBy === "type") {
json = simpleSort(json, "type", this.state.sortOrder);
} else {
json = simpleSort(json, "manufacturer", this.state.sortOrder);
}
現(xiàn)在我們已經(jīng)完成了 Tree Shaking 的工作,下面是 Tree Shaking 之前 webpack 打包生成的 js 包大?。?/p>
Asset Size Chunks Chunk Names js/vendors.16262743.js 37.1 KiB 0 [emitted] vendors js/main.797ebb8b.js 20.8 KiB 1 [emitted] main
下面是 Tree Shaking 之后 webpack 打包生成的 js 包大小:
Asset Size Chunks Chunk Names js/vendors.45ce9b64.js 36.9 KiB 0 [emitted] vendors js/main.559652be.js 8.46 KiB 1 [emitted] main
main 的文件大小下降比較明顯,這是因?yàn)?webpack 移除了不需要的 utils 方法。
更復(fù)雜的情況
在有些情況你按照上面的步驟進(jìn)行 Tree Shaking,但是 webpack 還是將模塊的所有內(nèi)容都打包到最終的 Chunk 中了,例如:lodash。
// 仍然會(huì)導(dǎo)入所有的內(nèi)容
import { sortBy } from "lodash";
// 這只會(huì)導(dǎo)入 sortBy
import sortBy from "lodash/sortBy";
如果你想要使用第一種寫(xiě)法,那么你還需要安裝 babel-plugin-lodash。如果你使用了第三方庫(kù),你可以看一下這個(gè)庫(kù)的導(dǎo)出是否使用了 ES6 語(yǔ)法,如果它的導(dǎo)出用的是 CommonJS 語(yǔ)法,例如:module.exports,那么 webpack 不能對(duì)它進(jìn)行 Tree Shaking 優(yōu)化。有些插件,例如:webpack-common-shake,提供了對(duì) CommonJS 模塊進(jìn)行 Tree Shaking 的能力,但是它有一些限制。
總結(jié)
為了確保構(gòu)建工具可以成功地優(yōu)化你的應(yīng)用程序,應(yīng)該避免依賴(lài) CommonJS 模塊,并在整個(gè)應(yīng)用程序中使用 ES6 模塊語(yǔ)法。
以上就是JS 加載性能Tree Shaking優(yōu)化詳解的詳細(xì)內(nèi)容,更多關(guān)于JS 加載Tree Shaking的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript躲避行星游戲?qū)崿F(xiàn)全程
本文將使用 canvas 創(chuàng)建一個(gè)躲避小行星游戲。另外將重點(diǎn)介紹的兩個(gè)方面是:如何使用 JavaScript 來(lái)檢測(cè)鍵盤(pán)輸入,以及如何在游戲中使用和處理 HTML5 音頻。希望你能夠喜歡2022-08-08
js中Function引用類(lèi)型常見(jiàn)有用的方法和屬性詳解
在本篇文章里小編給大家整理的是關(guān)于js中Function引用類(lèi)型常見(jiàn)有用的方法和屬性知識(shí)點(diǎn),有興趣的朋友們可以學(xué)習(xí)下。2019-12-12
JavaScript中閉包的作用和應(yīng)用場(chǎng)景
這篇文章將給大家詳細(xì)介紹JavaScript?中閉包是什么,有哪些應(yīng)用場(chǎng)景,文章通過(guò)代碼示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的參考價(jià)值,需要的朋友可以參考下2023-09-09
JavaScript前端靜態(tài)資源預(yù)加載實(shí)現(xiàn)示例
這篇文章主要為大家介紹了JavaScript前端靜態(tài)資源預(yù)加載實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
javascript類(lèi)繼承的實(shí)現(xiàn)方法
JavaScript是一種面向?qū)ο蟮恼Z(yǔ)言,而繼承是面向?qū)ο缶幊痰囊粋€(gè)重要特性,在JavaScript中,繼承的實(shí)現(xiàn)方式有多種,本文將介紹javascript類(lèi)的繼承的實(shí)現(xiàn),感興趣的朋友一起看看吧2023-10-10
javascript 改變網(wǎng)頁(yè)加載的CSS
javascript 改變網(wǎng)頁(yè)加載的CSS主要通過(guò)動(dòng)態(tài)響應(yīng)select觸發(fā)的選項(xiàng)變化控制link標(biāo)簽加載的css,以實(shí)現(xiàn)不依賴(lài)cookie控制的頁(yè)面css樣式動(dòng)態(tài)加載功能,需要的朋友可以參考一下2007-12-12

