Tree-Shaking性能優化實踐 - 原理篇

Tree-Shaking性能優化實踐 - 原理篇

 

一. 什麼是Tree-shaking

先來看一下Tree-shaking原始的本意javascript


上圖形象的解釋了Tree-shaking 的本意,本文所說的前端中的tree-shaking能夠理解爲經過工具"搖"咱們的JS文件,將其中用不到的代碼"搖"掉,是一個性能優化的範疇。具體來講,在 webpack 項目中,有一個入口文件,至關於一棵樹的主幹,入口文件有不少依賴的模塊,至關於樹枝。實際狀況中,雖然依賴了某個模塊,但其實只使用其中的某些功能。經過 tree-shaking,將沒有使用的模塊搖掉,這樣來達到刪除無用代碼的目的。前端

 

Tree-shaking 較早由 Rich_Harris 的 rollup 實現,後來,webpack2 也增長了tree-shaking 的功能。其實在更早,google closure compiler 也作過相似的事情。三個工具的效果和使用各不相同,使用方法能夠經過官網文檔去了解,三者的效果對比,後文會詳細介紹。java

 

二. tree-shaking的原理

Tree-shaking的本質是消除無用的js代碼。無用代碼消除在普遍存在於傳統的編程語言編譯器中,編譯器能夠判斷出某些代碼根本不影響輸出,而後消除這些代碼,這個稱之爲DCE(dead code elimination)。node

Tree-shaking 是 DCE 的一種新的實現,Javascript同傳統的編程語言不一樣的是,javascript絕大多數狀況須要經過網絡進行加載,而後執行,加載的文件大小越小,總體執行時間更短,因此去除無用代碼以減小文件體積,對javascript來講更有意義。webpack

Tree-shaking 和傳統的 DCE的方法又不太同樣,傳統的DCE 消滅不可能執行的代碼,而Tree-shaking 更關注宇消除沒有用到的代碼。下面詳細介紹一下DCE和Tree-shaking。git

 

(1)先來看一下DCE消除大法github

 

Dead Code 通常具備如下幾個特徵web

•代碼不會被執行,不可到達編程

•代碼執行的結果不會被用到瀏覽器

•代碼只會影響死變量(只寫不讀)

 

下面紅框標示的代碼就屬於死碼,知足以上特徵

圖4

傳統編譯型的語言中,都是由編譯器將Dead Code從AST(抽象語法樹)中刪除,那javascript中是由誰作DCE呢?

首先確定不是瀏覽器作DCE,由於當咱們的代碼送到瀏覽器,那還談什麼消除沒法執行的代碼來優化呢,因此確定是送到瀏覽器以前的步驟進行優化。

其實也不是上面提到的三個工具,rollup,webpack,cc作的,而是著名的代碼壓縮優化工具uglify,uglify完成了javascript的DCE,下面經過一個實驗來驗證一下。

 

如下全部的示例代碼都能在咱們的github中找到,歡迎戳❤

github.com/lin-xi/tree…

 

分別用rollup和webpack將圖4中的代碼進行打包

圖5

中間是rollup打包的結果,右邊是webpack打包的結果

能夠發現,rollup將無用的代碼foo函數和unused函數消除了,可是仍然保留了不會執行到的代碼,而webpack完整的保留了全部的無用代碼和不會執行到的代碼。

 

分別用rollup + uglify和 webpack + uglify 將圖4中的代碼進行打包

圖6

中間是配置文件,右側是結果

能夠看到右側最終打包結果中都去除了沒法執行到的代碼,結果符合咱們的預期。

 

(2) 再來看一下Tree-shaking消除大法

前面提到了tree-shaking更關注於無用模塊的消除,消除那些引用了但並無被使用的模塊。

先思考一個問題,爲何tree-shaking是最近幾年流行起來了?而前端模塊化概念已經有不少年曆史了,其實tree-shaking的消除原理是依賴於ES6的模塊特性。

ES6 module 特色:

  • 只能做爲模塊頂層的語句出現
  • import 的模塊名只能是字符串常量
  • import binding 是 immutable的

ES6模塊依賴關係是肯定的,和運行時的狀態無關,能夠進行可靠的靜態分析,這就是tree-shaking的基礎。

所謂靜態分析就是不執行代碼,從字面量上對代碼進行分析,ES6以前的模塊化,好比咱們能夠動態require一個模塊,只有執行後才知道引用的什麼模塊,這個就不能經過靜態分析去作優化。

這是 ES6 modules 在設計時的一個重要考量,也是爲何沒有直接採用 CommonJS,正是基於這個基礎上,才使得 tree-shaking 成爲可能,這也是爲何 rollup 和 webpack 2 都要用 ES6 module syntax 才能 tree-shaking。

 

咱們仍是經過例子來詳細瞭解一下

面向過程編程函數和麪向對象編程是javascript最經常使用的編程模式和代碼組織方式,從這兩個方面來實驗:

  • 函數消除實驗
  • 類消除實驗

先看下函數消除實驗

utils中get方法沒有被使用到,咱們指望的是get方法最終被消除。

注意,uglify目前不會跨文件去作DCE,因此上面這種狀況,uglify是不能優化的。

先看看rollup的打包結果

徹底符合預期,最終結果中沒有get方法

再看看webpack的結果

也符合預期,最終結果中沒有get方法

能夠看到rollup打包的結果比webpack更優化

函數消除實驗中,rollup和webpack都經過,符合預期

 

再來看下類消除實驗

增長了對menu.js的引用,但其實代碼中並無用到menu的任何方法和變量,因此咱們的指望是,最終代碼中menu.js裏的內容被消除

main.js
menu.js

rollup打包結果

包中居然包含了menu.js的所有代碼

webpack打包結果

包中居然也包含了menu.js的所有代碼

類消除實驗中,rollup,webpack 全軍覆沒,都沒有達到預期
what happend?

這跟咱們想象的徹底不同啊?爲何呢?無用的類不能消除,這還能叫作tree-shaking嗎?我當時一度懷疑本身的demo有問題,後來各類網上搜索,才明白demo沒有錯。

下面摘取了rollup核心貢獻者的的一些回答:

圖7
  • rollup只處理函數和頂層的import/export變量,不能把沒用到的類的方法消除掉
  • javascript動態語言的特性使得靜態分析比較困難
  • 圖7下部分的代碼就是反作用的一個例子,若是靜態分析的時候刪除裏run或者jump,程序運行時就可能報錯,那就本末倒置了,咱們的目的是優化,確定不能影響執行

 

再舉個例子說明下爲何不能消除menu.js,好比下面這個場景

function Menu() { } Menu.prototype.show = function() { } Array.prototype.unique = function() { // 將 array 中的重複元素去除 } export default Menu; 複製代碼

若是刪除裏menu.js,那對Array的擴展也會被刪除,就會影響功能。那也許你會問,難道rollup,webpack不能區分是定義Menu的proptotype 仍是定義Array的proptotype嗎?固然若是代碼寫成上面這種形式是能夠區分的,若是我寫成這樣呢?

function Menu() { } Menu.prototype.show = function() { } var a = 'Arr' + 'ay' var b if(a == 'Array') { b = Array } else { b = Menu } b.prototype.unique = function() { // 將 array 中的重複元素去除 } export default Menu; 複製代碼

這種代碼,靜態分析是分析不了的,就算能靜態分析代碼,想要正確徹底的分析也比較困難。

更多關於反作用的討論,能夠看這個

圖標

Tree shaking class methods · Issue #349 · rollup/rollupgithub.com

 

tree-shaking對函數效果較好

函數的反作用相對較少,頂層函數相對來講更容易分析,加上babel默認都是"use strict"嚴格模式,減小頂層函數的動態訪問的方式,也更容易分析

 

咱們開始說的三個工具,rollup和webpack表現不理想,那closure compiler又如何呢?

將示例中的代碼用cc打包後獲得的結果以下:

天啊,這不就是咱們要的結果嗎?完美消除全部無用代碼的結果,輸出的結果很是性感

closure compiler, tree-shaking的結果完美!

但是不能高興得太早,能獲得這麼完美結果是須要條件的,那就是cc的侵入式約束規範。必須在代碼裏添加這樣的代碼,看紅線框標示的

google定義一整套註解規範Annotating JavaScript for the Closure Compiler,想更多瞭解的,能夠去看下官網。

侵入式這個就讓人很不爽,google Closure Compiler是java寫的,和咱們基於node的各類構建庫不可能兼容(不過目前好像已經有nodejs版 Closure Compiler),Closure Compiler使用起來也比較麻煩,因此雖然效果很贊,但比較難以應用到項目中,遷移成本較大。

 

說了這麼多,總結一下:

三大工具的tree-shaking對於無用代碼,無用模塊的消除,都是有限的,有條件的。closure compiler是最好的,但與咱們平常的基於node的開發流很難兼容。

tree-shaking對web意義重大,是一個極致優化的理想世界,是前端進化的又一個終極理想。

理想是美好的,但目前還處在發展階段,還比較困難,有各個方面的,甚至有目前看來沒法解

決的問題,但仍是應該相信新技術能帶來更好的前端世界。

優化是一種態度,不因小而不爲,不因艱而不攻。

 

知識有限,若是錯誤,請不惜指正,謝謝

 

下一篇將繼續介紹 Tree-Shaking性能優化實踐 - 實踐篇

圖標

 


本文中示例代碼都能在咱們的github中找到,歡迎戳❤

圖標

lin-xi/treeshakinggithub.com

 

 

 

 

 

 

 

 

關注下面的標籤,發現更多類似文章
 
Webpack
 
rollup.js
相關文章
相關標籤/搜索