日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Webpack4: Tree-shaking 深度解析

發(fā)布時間:2023/12/19 编程问答 59 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Webpack4: Tree-shaking 深度解析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

什么是Tree-shaking

所謂Tree-shaking就是‘搖’的意思,作用是把項(xiàng)目中沒必要的模塊全部抖掉,用于在不同的模塊之間消除無用的代碼,可列為性能優(yōu)化的范疇。

Tree-shaking早期由rollup實(shí)現(xiàn),后來webpack2也實(shí)現(xiàn)了Tree-shaking的功能,但是至今還不是很完備。至于為什么不完備,可以看一下百度外賣的Tree-shaking原理

Tree-shading原理

Tree-shaking的本質(zhì)用于消除項(xiàng)目一些不必要的代碼。早在編譯原理中就有提到DCE(dead code eliminnation),作用是消除不可能執(zhí)行的代碼,它的工作是使用編輯器判斷出某些代碼是不可能執(zhí)行的,然后清除。

Tree-shaking同樣的也是消除項(xiàng)目中不必要的代碼,但是和DCE又有略不相同??梢哉f是DCE的一種實(shí)現(xiàn),它的主要工作是應(yīng)用于模塊間,在打包過程中抽出有用的部分,用于完成DCE。

Tree-shaking是依賴ES6模塊靜態(tài)分析的,ES6 module的特點(diǎn)如下:

  • 只能作為模塊頂層的語句出現(xiàn)
  • import 的模塊名只能是字符串常量
  • import binding 是 immutable的
  • 依賴關(guān)系確定,與運(yùn)行時無關(guān),靜態(tài)分析。正式因?yàn)镋S6 module的這些特點(diǎn),才讓Tree-shaking更加流行。

    主要特點(diǎn)還是依賴于ES6的靜態(tài)分析,在編譯時確定模塊。如果是require,在運(yùn)行時確定模塊,那么將無法去分析模塊是否可用,只有在編譯時分析,才不會影響運(yùn)行時的狀態(tài)。

    Webpack4的Tree-shaking

    webpack從第2版本就開始支持Tree-shaking的功能,但是至今也并不能實(shí)現(xiàn)的那么完美。凡是具有副作用的模塊,webpack的Tree-shaking就歇菜了。

    副作用

    副作用在我們項(xiàng)目中,也同樣是頻繁的出現(xiàn)。知道函數(shù)式編程的朋友都會知道這個名詞。所謂模塊(這里模塊可稱為一個函數(shù))具有副作用,就是說這個模塊是不純的。這里可以引入純函數(shù)的概念。

    對于相同的輸入就有相同的輸出,不依賴外部環(huán)境,也不改變外部環(huán)境。

    符合上述就可以稱為純函數(shù),不符合就是不純的,是具有副作用的,是可能對外界造成影響的。

    webpack自身的Tree-shaking不能分析副作用的模塊。以lodash-es這個模塊來舉個例子

    //test.js import _ from "lodash-es";const func1 = function(value){return _.isArray(value); } const func2 = function(value){return value=null; }export {func1,func2, } //index.js import {func2} from './test.js' func2() 復(fù)制代碼

    上述代碼在test.js中引入lodash-es,在func1中使用了loadsh,并且這里不符合純函數(shù)的概念,它是具有副作用的。func2是一個純函數(shù)。

    在index.js中只引入了func2,并且使用了func2,可見整個代碼的執(zhí)行是和func1是沒有任何關(guān)系的。我們通過生產(chǎn)環(huán)境打包一下試試看(Tree-shaking只在生產(chǎn)環(huán)境生效)

    main.js 91.7KB,可見這個結(jié)果是符合我們的預(yù)期的,因?yàn)閒unc1函數(shù)的副作用,webpack自身的Tree-shaking并沒有檢測到這里有沒必要的模塊。解決辦法還是用的,webpack的插件系統(tǒng)是很強(qiáng)大的。

    webpack-deep-scope-plugin

    webpack-deep-scope-plugin是一位中國同胞(學(xué)生)在Google夏令營,在導(dǎo)師Tobias帶領(lǐng)下寫的一個webpack插件。(此時慢慢的羨慕)

    這個插件主要用于填充webpack自身Tree-shaking的不足,通過作用域分析來消除無用的代碼。

    插件的原理

    這個插件是基于作用域分析的,那么都有什么樣的作用域?

    // module scope start// Block{ // <- scope start } // <- scope end// Classclass Foo { // <- scope start} // <- scope end// If elseif (true) { // <- scope start} /* <- scope end */ else { // <- scope start} // <- scope end// Forfor (;;) { // <- scope start } // <- scope end// Catchtry {} catch (e) { // <- scope start} // <- scope end// Functionfunction() { // <- scope start } // <- scope end// Scopeswitch() { // <- scope start } // <- scope end// module scope end 復(fù)制代碼

    對于ES6模塊來說,上面作用域只有function和class是可以被導(dǎo)出的,其他的作用域可以稱之為function和class的子作用域并不能被導(dǎo)出實(shí)際上歸屬于父作用域的。

    插件通過分析代碼的作用域,進(jìn)而得到作用域與作用域之間的關(guān)系。

    分析作用域

    分析代碼的作用域的基礎(chǔ)是建立做AST(Abstract Syntax Tree)抽象語法樹上面的。這個可以通過escope來完成。

    拿到解析完的AST抽象語法樹,利用圖的深度優(yōu)先遍歷找到哪些作用域是可以被使用到的,哪些作用域是不可以被使用到的。從而分析作用域之間的關(guān)系和導(dǎo)出變量之間的關(guān)系。進(jìn)而執(zhí)行模塊消除。

    插件的不足

    JavaScript中還是有一些代碼是不會消去的。

    根作用域的引用
    import { isNull } from 'lodash-es';export function scope(...args) {return isNull(...args); }復(fù)制代碼

    在根作用域引用到的作用域不會被消除。

    給變量重新賦值
    import _ from "lodash-es";var func1 func1 = function(value){return _.isArray(value); } const func2 = function(value){return value=null; }export {func1,func2, } 復(fù)制代碼

    上述代碼中先定義了func1,然后又給func1賦值,這樣缺少了數(shù)據(jù)流分析,同樣插件也是不可以的。

    純函數(shù)調(diào)用

    引用作者的例子

    import _curry1 from './internal/_curry1'; import curryN from './curryN'; import max from './max'; import pluck from './pluck';var allPass = /*#__PURE__*/_curry1(function allPass(preds) {return curryN(reduce(max, 0, pluck('length', preds)), function () {var idx = 0;var len = preds.length;while (idx < len) {if (!preds[idx].apply(this, arguments)) {return false;}idx += 1;}return true;}); }); export default allPass; 復(fù)制代碼

    當(dāng)一個匿名函數(shù)被包在一個函數(shù)調(diào)用中(IIFE也是如此),那么插件是無法分析的。但是如果加上/*#__PURE__*/注釋的話,這個插件會把這個函數(shù)調(diào)用當(dāng)作一個獨(dú)立的域,tree-shaking是可以生效的。

    探討的一些問題

    我們都知道在這個ES6泛濫的時代,ES6的代碼在項(xiàng)目中出現(xiàn)已經(jīng)很廣泛。(先不考慮線上環(huán)境打包成ES5)。上面提到插件的利用作用域來分析。能導(dǎo)出的作用域只有class和funciton。function的情況在上面已經(jīng)說過,現(xiàn)在來探討一下class的情況。

    no plugin

    當(dāng)不使用插件的時候,我們來看一下會不會Tree-shaking,預(yù)期是會被Tree-shaking。書寫下面這樣一段簡單的代碼。

    class Test{init(value){console.log('test init');} } export {Test, } 復(fù)制代碼

    我們發(fā)現(xiàn)在沒有使用插件的情況下,被Tree-shaking了。預(yù)期相同。

    no plugin + 副作用

    當(dāng)我們在不適用插件的情況下,并且引入副作用,觀察一下會不會打包,預(yù)期是不會打包。書寫下面代碼。

    class Test{init(value){console.log('test init');return _.isArray(value);} } export {Test, } 復(fù)制代碼

    觀察打包結(jié)果,main.js 91.7KB,并沒有被Tree-shaking,符合預(yù)期的結(jié)果。

    plugin + 副作用

    當(dāng)我們使用插件并且代碼中存在副作用的情況下,觀察打包情況。由于上面的插件原理的鋪墊,我們預(yù)期這次是可以Tree-shaking的。利用上例代碼來測試。

    我們觀察可以看出main.js 6.78KB,Tree-shaking生效。

    plugin + 副作用 + babel

    由于用戶瀏覽器對ES6支持度不夠的原因,線上的代碼不能全是ES6的,有時候我們要把ES6的代碼打包成ES5的,放到線上環(huán)境來執(zhí)行。利用上例代碼來測試。

    ??? 什么鬼,我沒有用到它,為什么這么大??? 一串懵逼

    成也babel,敗也babel

    懵逼懵逼,babel成就了線上生產(chǎn)環(huán)境,但失去了Tree-shaking優(yōu)化。我們來看看怎么回事。

    沒有副作用的情況

    當(dāng)去除調(diào)副作用的時候我們來打包一下。

    沒有找到test init Tree-shaking生效。為什么呢?我們使用babel線上工具編譯一下源代碼。

    "use strict";function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {return right[Symbol.hasInstance](left); } else {return left instanceof right; } }function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }var Test =/*#__PURE__*/function () {function Test() {_classCallCheck(this, Test);}_createClass(Test, [{key: "init",value: function init(value) {console.log("test init")}}]);return Test;}(); 復(fù)制代碼

    上面可以看到最新的babel和webpack有了契合,在Test立即執(zhí)行函數(shù)的地方使用了 /*#__PURE__*/(忘記可以往上看),讓下面的IIFE變成可分析的,成功了使用了Tree-shaking。

    有副作用的情況下

    上面探討情況的時候就得知有副作用的情況下,不可以被打包的。ES6編譯代碼如下。

    "use strict";function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {return right[Symbol.hasInstance](left); } else {return left instanceof right; } }function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }var Test =/*#__PURE__*/function () {function Test() {_classCallCheck(this, Test);}_createClass(Test, [{key: "init",value: function init(value) {console.log("test init")return _.isArray(value);}}]);return Test;}(); 復(fù)制代碼

    這里雖然bable新版契合了webpack,但是還是有一些問題。自己也沒有找出是哪里除了問題,作者說JavaScript代碼還是有一些是不可以清除的,也許就出現(xiàn)到這里。提供一個作者的插件Demo。

    babel的解決方案

    無論是ES6,還是ES5,Tree-shaking不能生效的原因總的歸根結(jié)底還是因?yàn)榇a副作用的問題??上攵a的書寫規(guī)范是多么重要。這里我所想出的解決方案有兩種。

    1.代碼的書寫規(guī)范,盡量避免副作用。

    書寫代碼過程中盡量使用純函數(shù)的方式來寫代碼,保持書寫規(guī)范,不讓代碼有副作用。例如把class類引用的副作用改成純的。

    class Test{init(value,_){ //參數(shù)引入lodash模塊console.log('test init');return _.isArray(value);} } export{Test, } 復(fù)制代碼

    可以看出,沒有副作用的ES6代碼編譯成ES5,Tree-shaking也是生效的。

    2.靈活使用ES6代碼

    兩套代碼。當(dāng)瀏覽器支持的時候,就使用ES6的代碼,ES5的代碼。此方案可參考瀏覽器支持ES6的最優(yōu)解決方案

    總結(jié)

    項(xiàng)目中難免會一些用不到的模塊占位置影響我們的項(xiàng)目,Tree-shaking的出現(xiàn)也為開發(fā)者在性能優(yōu)化方面提供了非常大的幫助,靈活使用Tree-shaking才能讓Tree-shaking發(fā)揮作用,處理好項(xiàng)目中代碼的副作用可以使項(xiàng)目更加的完美。

    引用文章

    webpack 如何通過作用域分析消除無用代碼

    Tree-Shaking性能優(yōu)化實(shí)踐 - 原理篇

    原文發(fā)布于Webpack4:Tree-shaking深度解析

    總結(jié)

    以上是生活随笔為你收集整理的Webpack4: Tree-shaking 深度解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。