一. 什麼是Tree-shaking
![](http://static.javashuo.com/static/loading.gif)
先來看一下Tree-shaking原始的本意javascript
上圖形象的解釋了Tree-shaking 的本意,本文所說的前端中的tree-shaking能夠理解爲經過工具"搖"咱們的JS文件,將其中用不到的代碼"搖"掉,是一個性能優化的範疇。具體來講,在 webpack 項目中,有一個入口文件,至關於一棵樹的主幹,入口文件有不少依賴的模塊,至關於樹枝。實際狀況中,雖然依賴了某個模塊,但其實只使用其中的某些功能。經過 tree-shaking,將沒有使用的模塊搖掉,這樣來達到刪除無用代碼的目的。前端
![](http://static.javashuo.com/static/loading.gif)
Tree-shaking 較早由 Rich_Harris 的 rollup 實現,後來,webpack2 也增長了tree-shaking 的功能。其實在更早,google closure compiler 也作過相似的事情。三個工具的效果和使用各不相同,使用方法能夠經過官網文檔去了解,三者的效果對比,後文會詳細介紹。java
二. tree-shaking的原理
![](http://static.javashuo.com/static/loading.gif)
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
![](http://static.javashuo.com/static/loading.gif)
Dead Code 通常具備如下幾個特徵web
•代碼不會被執行,不可到達編程
•代碼執行的結果不會被用到瀏覽器
•代碼只會影響死變量(只寫不讀)
下面紅框標示的代碼就屬於死碼,知足以上特徵
![](http://static.javashuo.com/static/loading.gif)
傳統編譯型的語言中,都是由編譯器將Dead Code從AST(抽象語法樹)中刪除,那javascript中是由誰作DCE呢?
首先確定不是瀏覽器作DCE,由於當咱們的代碼送到瀏覽器,那還談什麼消除沒法執行的代碼來優化呢,因此確定是送到瀏覽器以前的步驟進行優化。
其實也不是上面提到的三個工具,rollup,webpack,cc作的,而是著名的代碼壓縮優化工具uglify,uglify完成了javascript的DCE,下面經過一個實驗來驗證一下。
如下全部的示例代碼都能在咱們的github中找到,歡迎戳❤
分別用rollup和webpack將圖4中的代碼進行打包
![](http://static.javashuo.com/static/loading.gif)
中間是rollup打包的結果,右邊是webpack打包的結果
能夠發現,rollup將無用的代碼foo函數和unused函數消除了,可是仍然保留了不會執行到的代碼,而webpack完整的保留了全部的無用代碼和不會執行到的代碼。
分別用rollup + uglify和 webpack + uglify 將圖4中的代碼進行打包
![](http://static.javashuo.com/static/loading.gif)
中間是配置文件,右側是結果
能夠看到右側最終打包結果中都去除了沒法執行到的代碼,結果符合咱們的預期。
(2) 再來看一下Tree-shaking消除大法
前面提到了tree-shaking更關注於無用模塊的消除,消除那些引用了但並無被使用的模塊。
先思考一個問題,爲何tree-shaking是最近幾年流行起來了?而前端模塊化概念已經有不少年曆史了,其實tree-shaking的消除原理是依賴於ES6的模塊特性。
![](http://static.javashuo.com/static/loading.gif)
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方法最終被消除。
![](http://static.javashuo.com/static/loading.gif)
注意,uglify目前不會跨文件去作DCE,因此上面這種狀況,uglify是不能優化的。
先看看rollup的打包結果
![](http://static.javashuo.com/static/loading.gif)
徹底符合預期,最終結果中沒有get方法
再看看webpack的結果
![](http://static.javashuo.com/static/loading.gif)
也符合預期,最終結果中沒有get方法
能夠看到rollup打包的結果比webpack更優化
函數消除實驗中,rollup和webpack都經過,符合預期
再來看下類消除實驗
增長了對menu.js的引用,但其實代碼中並無用到menu的任何方法和變量,因此咱們的指望是,最終代碼中menu.js裏的內容被消除
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
rollup打包結果
![](http://static.javashuo.com/static/loading.gif)
包中居然包含了menu.js的所有代碼
webpack打包結果
![](http://static.javashuo.com/static/loading.gif)
包中居然也包含了menu.js的所有代碼
類消除實驗中,rollup,webpack 全軍覆沒,都沒有達到預期
![](http://static.javashuo.com/static/loading.gif)
這跟咱們想象的徹底不同啊?爲何呢?無用的類不能消除,這還能叫作tree-shaking嗎?我當時一度懷疑本身的demo有問題,後來各類網上搜索,才明白demo沒有錯。
下面摘取了rollup核心貢獻者的的一些回答:
![](http://static.javashuo.com/static/loading.gif)
- 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
![](http://static.javashuo.com/static/loading.gif)
tree-shaking對函數效果較好
函數的反作用相對較少,頂層函數相對來講更容易分析,加上babel默認都是"use strict"嚴格模式,減小頂層函數的動態訪問的方式,也更容易分析
咱們開始說的三個工具,rollup和webpack表現不理想,那closure compiler又如何呢?
將示例中的代碼用cc打包後獲得的結果以下:
![](http://static.javashuo.com/static/loading.gif)
天啊,這不就是咱們要的結果嗎?完美消除全部無用代碼的結果,輸出的結果很是性感
closure compiler, tree-shaking的結果完美!
但是不能高興得太早,能獲得這麼完美結果是須要條件的,那就是cc的侵入式約束規範。必須在代碼裏添加這樣的代碼,看紅線框標示的
![](http://static.javashuo.com/static/loading.gif)
google定義一整套註解規範Annotating JavaScript for the Closure Compiler,想更多瞭解的,能夠去看下官網。
侵入式這個就讓人很不爽,google Closure Compiler是java寫的,和咱們基於node的各類構建庫不可能兼容(不過目前好像已經有nodejs版 Closure Compiler),Closure Compiler使用起來也比較麻煩,因此雖然效果很贊,但比較難以應用到項目中,遷移成本較大。
說了這麼多,總結一下:
三大工具的tree-shaking對於無用代碼,無用模塊的消除,都是有限的,有條件的。closure compiler是最好的,但與咱們平常的基於node的開發流很難兼容。
![](http://static.javashuo.com/static/loading.gif)
tree-shaking對web意義重大,是一個極致優化的理想世界,是前端進化的又一個終極理想。
理想是美好的,但目前還處在發展階段,還比較困難,有各個方面的,甚至有目前看來沒法解
決的問題,但仍是應該相信新技術能帶來更好的前端世界。
優化是一種態度,不因小而不爲,不因艱而不攻。
知識有限,若是錯誤,請不惜指正,謝謝
下一篇將繼續介紹 Tree-Shaking性能優化實踐 - 實踐篇
本文中示例代碼都能在咱們的github中找到,歡迎戳❤