新聞中心
前言
身為一位前端工程師或多或少都有聽(tīng)過(guò) Webpack 這套前端打包工具吧,為了讓最終打包的檔案不會(huì)過(guò)于龐大,Webpack 可是下了非常多的苦功,例如:利用 Code Splitting 產(chǎn)出一個(gè)又一個(gè)的 chunk 讓網(wǎng)頁(yè)不會(huì)一次載入一份很大 JS包。

目前成都創(chuàng)新互聯(lián)公司已為超過(guò)千家的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)頁(yè)空間、網(wǎng)站托管、服務(wù)器托管、企業(yè)網(wǎng)站設(shè)計(jì)、郊區(qū)網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶(hù)導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶(hù)和合作伙伴齊心協(xié)力一起成長(zhǎng),共同發(fā)展。
然而今天的文章其實(shí)不是要講 Code Splitting,而是要講一個(gè)比較深入的原理:Tree Shaking。
什么是 Tree Shaking?
什么是 Tree Shaking?Tree Shaking 就字面上翻譯來(lái)看就是搖晃樹(shù)木,在 Webpack 的世界中我們通常都會(huì)設(shè)定一個(gè) Entry Points 來(lái)告訴 webpack 要從哪個(gè)文件開(kāi)始往其他文件進(jìn)行打包,如果用 Tree 的概念來(lái)看就是一個(gè)主干配上很多的樹(shù)枝。
Dynamic Language & Static Language
接下來(lái)講個(gè)跟 Tree Shaking 比較無(wú)關(guān)的小知識(shí),但這個(gè)小觀念可以幫助我們了解為何要在 JavaScript 上執(zhí)行 Tree Shaking 并不是我們想像中的那麼容易。
接下來(lái)講個(gè)跟 Tree Shaking 比較無(wú)關(guān)的小知識(shí),但這個(gè)小觀念可以幫助我們了解為何要在 JavaScript 上執(zhí)行 Tree Shaking 并在程式語(yǔ)言中有分為 動(dòng)態(tài)語(yǔ)言(Dynamic Language) 以及 靜態(tài)語(yǔ)言(Static Language),被歸類(lèi)在 Dynamic Language 中比較常見(jiàn)的有 JavaScript、PHP、Python 等語(yǔ)不是我們想像中的那么容易。
在編程語(yǔ)言中有分為 動(dòng)態(tài)語(yǔ)言(Dynamic Language) 以及 **靜態(tài)語(yǔ)言(Static Language)**,被歸類(lèi)在 Dynamic Language 中比較常見(jiàn)的有 JavaScript、PHP、Python 等語(yǔ)言,至于被歸類(lèi)在 Static Language 比較常見(jiàn)的有 C++、Java 等語(yǔ)言。
在 Dynamic Language 中由于我們可以動(dòng)態(tài)的載入非常多東西,例如 function、object 等,對(duì)于 Tree Shaking 來(lái)說(shuō)這種會(huì)動(dòng)態(tài)載入的東西實(shí)在是太難捉摸了,這也讓 Dynamic Language 的 Tree Shaking 很難達(dá)到最完美。
Dead Code Elimination
在開(kāi)始講 Tree Shaking 原理之前必須要了解一個(gè)技術(shù):死碼刪除(Dea誒 Code Elimination)。
在 ??compiler?? 的領(lǐng)域中,為了達(dá)到執(zhí)行時(shí)間的優(yōu)化,在代碼編譯的過(guò)程中 compiler 會(huì)將對(duì)于最終結(jié)果沒(méi)有影響到的代碼刪除,進(jìn)而達(dá)到執(zhí)行時(shí)間的優(yōu)化,這段過(guò)程稱(chēng)之為 Dead Code Elimination。
乍看之下 Dead Code Elimination 在做的事情好像就是 Tree Shaking 要做到的事情,就是要?jiǎng)h除無(wú)用的代碼,但兩者其實(shí)還是有著些微的差距,接下來(lái)就要講講 Tree Shaking 的原理。
Tree Shaking 原理
Tree Shaking 其實(shí)是 Dead Code Elimination 的一種新的實(shí)現(xiàn)原理,在上面的 Dynamic Language 的觀念中提到 Dynamic Language 的特性就是可以動(dòng)態(tài)載入任何東西,因?yàn)檫@個(gè)特性讓 Dead Code Elimination 相當(dāng)難實(shí)現(xiàn),因?yàn)?complier 永遠(yuǎn)不知道到底哪些程代碼是對(duì)最終結(jié)果不會(huì)有影響的。
所以 Tree Shaking 其實(shí)要做到的不會(huì)像 Dead Code Elimination 那樣死板板的要?jiǎng)h除對(duì)結(jié)果不會(huì)有影響的程式碼,而是要保留會(huì)需要用到的代碼,這樣也可以達(dá)到類(lèi)似 Dead Code Elimination 的效果,只是兩者的原理還是有一些差異,而這就是 Tree Shaking 的原理。
ES6 module v.s commonJS
上面提到 Tree Shaking 的原理最主要的目的就是要保留會(huì)需要用到的代碼,而這點(diǎn)在早期的 JavaScript 其實(shí)是無(wú)法實(shí)現(xiàn)的,但是在 ES6 誕生后有一個(gè)非常重要的概念叫:ES6 modules。
由于 ES6 modules 的誕生,我們可以在每個(gè)文件的最上方先引用即將會(huì)需要用到的東西,所以這些 bunbler 就可以藉由這些?? import file?? 很快速的知道可以保留哪些文件,進(jìn)而達(dá)到 Tree Shaking 的效果。
這時(shí)候讀者可能會(huì)有另一個(gè)問(wèn)題了,在 ES6 module 還沒(méi)誕生以前我們也可以利用 commonJS 來(lái)進(jìn)行 module 的導(dǎo)入,為什麼 ES6 module 可以做到 Tree Shaking 可是 commonJS 無(wú)法呢?
其實(shí)是因?yàn)?ES6 module 有著非常多的特性,讓 bundler 可以針對(duì)這些特性來(lái)進(jìn)行靜態(tài)的分析:
- module 必須要在頂層被 import。
- module 內(nèi)部會(huì)自動(dòng)被定義為 strict mode。
- module name 不能動(dòng)態(tài)改變。
- module 內(nèi)容為 immutable 無(wú)法在其他文件中被動(dòng)態(tài)新增或刪除內(nèi)容。
因?yàn)檫@些強(qiáng)限制在,所以 ES6 module 就可以讓 bundler 做到 Tree Shaking 的效果,而 commonJS 則無(wú)法達(dá)到此點(diǎn)。
改善 import 與 export 方式
我們都知道 ES6 modules 的 export 方式有分 ??named export??? 以及?? default export??,這兩種方法適用于不同的使用場(chǎng)景,也會(huì)對(duì) Tree Shaking 后的文件內(nèi)容有著非常大的差別。
default export
named export
乍看之下 ??default export??? 跟?? named export?? 在寫(xiě)法上好像沒(méi)什麼太大的差別(除了直接在項(xiàng)目前面加上 export 的寫(xiě)法比較不一樣外),最終都是需要用一個(gè)物件來(lái)包裝輸出,但兩者在 Tree Shaking 后的結(jié)果可是有著蠻大的差別,接下來(lái)就看來(lái)一下 Tree Shaking 過(guò)后的結(jié)果吧!
default export 經(jīng)由 Tree Shaking 后的結(jié)果:
named export 經(jīng)由 Tree Shaking 后的結(jié)果:
可以看到上面兩張圖,雖然 Tree Shaking 都有把 multiply 這個(gè) function 移除了,可是 ??default export??? 相較于 ??named export??? 還是新增了不少變量來(lái)處理 ??function parameter??,這樣就不是一個(gè)完美的性能優(yōu)化。
所以假如讀者在開(kāi)發(fā)時(shí)確定一個(gè)文件會(huì)需要同時(shí)輸出很多項(xiàng)目,不管是對(duì)象也好函數(shù)也罷,這時(shí)候都建議用 ??named export?? 的方式進(jìn)行輸出這樣才能達(dá)到最好的性能優(yōu)化。
改善第三方組件的 import 方式
最后再來(lái)看一下 import 第三方組件的最佳方式,在前端開(kāi)發(fā)的過(guò)程中為了不要重復(fù)照輪子很多時(shí)候都會(huì)使用大神所開(kāi)發(fā)好的第三方組件來(lái)加速開(kāi)發(fā),但第三方組件的 ??import?? 方式其實(shí)也會(huì)影響到最終的 bundle size。
接下將以 ant design 這套 UI library 來(lái)進(jìn)行說(shuō)明。
首先是利用官方文檔的說(shuō)明來(lái)進(jìn)行 import,其實(shí) antd 本身就有針對(duì)其 module 進(jìn)行 Tree Shaking 的性能優(yōu)化,所以我們?cè)瓌t上是可以放心的使用官方文檔的教學(xué)進(jìn)行 ??import??? 的,接下來(lái)我們利用 ??webpack-bundle-analyzer?? 來(lái)進(jìn)行檔案分析。
可以發(fā)現(xiàn) antd 的文件大小高達(dá) ??842.15KB???,而且裡面還跑出了許多跟 Button 無(wú)關(guān)的 component 文件,這顯然是一個(gè)不好的 ??import??? 方式,沒(méi)想到照著官文檔的方式進(jìn)行 ??import?? 也沒(méi)辦法達(dá)到最好的性能優(yōu)化。
但這其實(shí)也不是 antd 的錯(cuò), antd 本身就有做好 Tree Shaking 的動(dòng)作,詳細(xì)的說(shuō)明可以參考 antd 的官方文件,但是這邊的事例故意沒(méi)有在項(xiàng)目的 bundler 設(shè)定檔中開(kāi)啟 Tree Shaking 的功能,進(jìn)而導(dǎo)致 antd 的 Tree Shaking 失效。
雖然 bundler 沒(méi)有開(kāi)啟 Tree Shaking 功能讓整體的 bundle size 過(guò)大,但我們其實(shí)也可以自己手動(dòng)做這件事,這時(shí)候只要我們改成從 antd 的 es folder 進(jìn)行元件的單獨(dú) ??import?? 就可以讓最終的 bundle size 差非常多,寫(xiě)法如下。
接著我們一樣使用 webpack-bundle-analyzer 來(lái)進(jìn)行項(xiàng)目分析。
可以發(fā)現(xiàn)整個(gè) antd 的文件大小少了非常多,只剩下 ??74.8KB??? 而且與 Button 無(wú)關(guān)的其他 component 都沒(méi)出現(xiàn)了,所以同一種第三方組件不同的 import 方式真的會(huì)讓整體的性能差距非常大,這個(gè)就是比較好的第三方組件 ??import?? 方式。
package.json 中的 sideEffects
在 Webpack 的 Tree Shaking 配置中,有一個(gè)可以在 ??package.json??? 中配置的叫 ??sideEffects???,這個(gè) ??sideEffects?? 的配置主要是讓 Webpack 這種 bundler 知道此項(xiàng)目是否可以做 Tree Shaking 的動(dòng)作。
假如設(shè)定為 ??false??? 就代表可以將所有的文件進(jìn)行 Tree Shaking,若讀者知道有哪些檔案是不能做 Tree Shaking 的,這時(shí)候只要在 ??sideEffects?? 內(nèi)用一個(gè)數(shù)組將不能做 Tree Shaking 的文件路徑寫(xiě)上去,這時(shí)候 bundler 就只會(huì)針對(duì)這個(gè)數(shù)組以外的文件進(jìn)行 Tree Shaking。
Webpack 中的 usedExports
在 Webpack 的官方文件中要達(dá)到 Tree Shaking 的效果除了在 ??package.json??? 中加上 ??sideEffects??? 外,還可以使用 ??usedExports??。
在官方文件中有這麼一段說(shuō)明:
如果說(shuō) sideEffects 在做的事情是把不能做 Tree Shaking 的樹(shù)枝移除,那 ??usedExports?? 在做的事情就是把樹(shù)枝上沒(méi)有用到的樹(shù)葉移除,所以 usedExports 其實(shí)才是在做真正的 Tree Shaking。
useExports 利用 terser 這套工具進(jìn)行項(xiàng)目的 side effects 偵測(cè),假如打包過(guò)程中發(fā)現(xiàn)此弎既沒(méi)有 side effects 且某些代碼又沒(méi)有被引用到,則該代碼就會(huì)在之后的 ??uglify?? 被移除,藉此達(dá)到真正的 Tree Shaking 效果。
而 usedExports 的設(shè)定方式也非常簡(jiǎn)單,只要在 Webpack 的配置文件中,在 ??optimization??? 內(nèi)加上?? usedExports: true?? 這時(shí)候就可以將 usedExports 的功能打開(kāi),寫(xiě)法如下:
小結(jié)
今天介紹了 Tree Shaking 的相關(guān)基本觀念,雖然說(shuō)身為一位前端工程師不一定要懂這個(gè)概念,畢竟現(xiàn)在很多主流的框架都已經(jīng)先把 bundler 的相關(guān) ??config?? 都寫(xiě)好了,但了解這些工具背后在做的事情也能幫助到自己在開(kāi)發(fā)時(shí)可以稍微省思一下要如何改良自己的代碼,進(jìn)而提升整體的打包后的性能。
像是上面提到的 import 與 export 方式,引用第三方組件時(shí)可以如何引用達(dá)到最小的 bundle
size,有了這些概念在開(kāi)發(fā)時(shí)就可以提升整體的性能 ,所以筆者也建議目前正在學(xué)習(xí)網(wǎng)頁(yè)開(kāi)發(fā)的讀者都可以稍微了解一下 Tree Shaking 的概念喔。
名稱(chēng)欄目:原來(lái)項(xiàng)目打包也有這么技巧 - 淺談 Tree Shaking 機(jī)制
鏈接地址:http://m.fisionsoft.com.cn/article/dhgigpd.html


咨詢(xún)
建站咨詢(xún)
