天天看點

【譯】如何在 Webpack 2 中使用 tree-shaking如何在 Webpack 2 中使用 tree-shaking

<b>本文講的是【譯】如何在 Webpack 2 中使用 tree-shaking,</b>

<b></b>

了解在 Webpack 中使用 tree-shaking 的最佳的方式是通過一個微型應用例子。我将它比作一個汽車有特定的引擎,該應用由 2 個檔案組成。第 1 個檔案有:一些 class,代表不同種類的引擎;一個函數傳回其版本号 -- 都通過 export 關鍵字導出。

第 2 個檔案表示一個汽車擁有它自己的引擎,将這個檔案作為應用打包的入口(entry)。

通過定義類 SportsCar,我們隻使用了 V8Engine,而沒有用到 V6Engine。運作這個應用會輸出:‘V8 Sports Car’。

應用了 tree-shaking 後,我們期望打包結果隻包含用到的類和函數。在這個例子中,意味着它隻有 V8Engine 和 SportsCar 類。讓我們來看看它是如何工作的。

Webpack 用注釋 /\unused harmony export V6Engine*/ 将未使用的類和函數标記下來,用/*harmony export (immutable)*/ webpack_exports[“a”] = V8Engine;* 來标記用到的。你應該會問未使用的代碼怎麼還在?tree-shaking 沒有生效嗎?

背後的原因是:Webpack 僅僅标記未使用的代碼(而不移除),并且不将其導出到子產品外。它拉取所有用到的代碼,将剩餘的(未使用的)代碼留給像 UglifyJS 這類壓縮代碼的工具來移除。UglifyJS 讀取打包結果,在壓縮之前移除未使用的代碼。通過這一機制,就可以移除未使用的函數 getVersion 和類 V6Engine。

而 Rollup 不同,它(的打包結果)隻包含運作應用程式所必需的代碼。打包完成後的輸出并沒有未使用的類和函數,壓縮僅涉及實際使用的代碼。

最重要的是讓 ES6 子產品不受 Babel 預設(preset)的影響。Webpack 認識 ES6 子產品,隻有當保留 ES6 子產品文法時才能夠應用 tree-shaking。如果将其轉換為 CommonJS 文法,Webpack 不知道哪些代碼是使用過的,哪些不是(就不能應用 tree-shaking了)。最後,Webpack将把它們轉換為 CommonJS 文法。

對應 Webpack 配置:

可以看到函數 getVersion 被移除了,這是我們所預期的,然而類 V6Engine 并沒有被移除。這是什麼原因呢?

首先 Babel 檢測到 ES6 子產品将其轉換為 ES5,然後 Webpack 将所有的子產品聚集起來,最後 UglifyJS 會移除未使用的代碼。我們來看一下 UglifyJS 的輸出,就可以找到問題出在哪裡。

WARNING in car.prod.bundle.js from UglifyJs

Dropping unused function getVersion [car.prod.bundle.js:103,9]

Side effects in initialization of unused variable V6Engine [car.prod.bundle.js:79,4]

它告訴我們類 V6Engine 轉換為 ES5 的代碼在初始化時有副作用。

在使用 ES5 文法定義類時,類的成員函數會被添加到屬性 prototype,沒有什麼方法能完全避免這次指派。UglifyJS 不能夠分辨它僅僅是類聲明,還是其它有副作用的操作 -- UglifyJS 不能做控制流分析。

編譯過程阻止了對類進行 tree-shaking。它僅對函數起作用。

Babili 會在編譯前删除未使用的代碼。在編譯為 ES5 之前,很容易找到未使用的類,是以 tree-shaking 也可以用于類聲明,而不再僅僅是函數。

我們隻需用 Babili 替換 UglifyJS,然後删除 babel-loader 即可。另一種方式是将 Babili 作為 Babel 的預設,僅使用 babel-loader(移除 UglifyJS 插件)。推薦使用第一種(插件的方式),因為當編譯器不是 Babel(比如 Typescript)時,它也能生效。

我們需要将 ES6+ 代碼傳給 BabiliPlugin,否則它不用移除(未使用的)類。

使用 Typescript 等編譯器時,也應當使用 ES6+。Typescript 應當輸出 ES6+ 代碼,以便 tree-shaking 能夠生效。

對第三方包來說也是,應當使用 ES6 子產品。幸運的是,越來越多的包作者同時釋出 CommonJS 格式 和 ES6 格式的子產品。ES6 子產品的入口由 package.json 的字段 module 指定。

對 ES6 子產品,未使用的函數會被移除,但 class 并不一定會。隻有當包内的 class 定義也為 ES6 格式時,Babili 才能将其移除。很少有包能夠以這種格式釋出,但有的做到了(比如說 lodash 的 lodash-es)。

通過 tree-shaking 你可以相當程度上減少應用的體積。Webpack 2 内置支援它,但其機制并不同于 Rollup。它會包含所有的代碼,标記未使用的函數和函數,以便壓縮工具能夠移除。這就是對所有代碼都進行 tree-shake 的困難之處。使用預設的壓縮工具 UglifyJS,它僅移除未使用的函數和變量;Babili 支援 ES6,可以用它來移除(未使用的)類。我們還必須特别注意第三方子產品釋出的方式是否支援 tree-shaking。

希望這篇文章為您清楚闡述了 Webpack tree-shaking 背後的原理,并為您提供了克服困難的思路。

<b>原文釋出時間為:2017年8月22日</b>

<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>

繼續閱讀