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 比處理其他類型的資源更耗時(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)入依賴項(xiàng):
// 在這里導(dǎo)入了所有的數(shù)組處理方法 import arrayUtils from "array-utils";
在應(yīng)用最初的時(shí)候,依賴項(xiàng)可能很少,隨著功能逐漸增加,依賴項(xiàng)也會(huì)增加,更糟糕的是,舊的依賴項(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 分析你的依賴樹(shù)并擺脫那些未使用的依賴。
留意 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)該避免依賴 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)文章
inquirer.js一個(gè)用戶與命令行交互的工具詳解
這篇文章主要介紹了inquirer.js一個(gè)用戶與命令行交互的工具詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05動(dòng)態(tài)加載外部javascript文件的函數(shù)代碼分享
動(dòng)態(tài)加載外部javascript文件的函數(shù)代碼分享,做個(gè)記錄備忘,方便查找。2011-07-07js 在定義的時(shí)候立即執(zhí)行的函數(shù)表達(dá)式(function)寫(xiě)法
如果不需要顯示調(diào)用函數(shù), 讓這個(gè)函數(shù)在定義的時(shí)候就執(zhí)行的話, 該如何寫(xiě)才可以呢,接下來(lái)將詳細(xì)介紹實(shí)現(xiàn)步驟,感興趣的朋友可以了解下2013-01-01JavaScript常見(jiàn)的函數(shù)中的屬性與方法總結(jié)
當(dāng)定義和調(diào)用函數(shù)時(shí),JavaScript?函數(shù)對(duì)象會(huì)自動(dòng)具有一些特定的屬性,本文為大家總結(jié)了一些常見(jiàn)的屬性和方法,感興趣的小伙伴可以了解一下2023-05-05Bootstrap基本組件學(xué)習(xí)筆記之列表組(11)
這篇文章主要為大家詳細(xì)介紹了Bootstrap基本組件學(xué)習(xí)筆記之列表組,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12JS常見(jiàn)疑難點(diǎn)分析之match,charAt,charCodeAt,map,search用法分析
這篇文章主要介紹了JS常見(jiàn)疑難點(diǎn)分析之match,charAt,charCodeAt,map,search用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了match,charAt,charCodeAt,map,search的功能,使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-12-12javascript跑馬燈抽獎(jiǎng)實(shí)例講解
這篇文章主要為大家介紹了javascript跑馬燈抽獎(jiǎng)特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-01-01使用uniapp實(shí)現(xiàn)發(fā)布朋友圈功能
這篇文章主要介紹了使用uniapp實(shí)現(xiàn)發(fā)布朋友圈功能,在文章底部給大家介紹了uniapp?微信小程序分享、分享朋友圈功能,通過(guò)頁(yè)內(nèi)自定義分享按鈕,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09