<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          聊一聊面試中經(jīng)常被問(wèn)到的Tree Shaking

          共 9376字,需瀏覽 19分鐘

           ·

          2020-08-23 02:33


          天下武功,唯快不破!最新版的 antd 以及 vue 都對(duì) Tree Shaking 提供了支持。我們內(nèi)部的組件在支持這部分功能時(shí),也專門梳理了相關(guān)的特性。這是四月份寫的文章了,長(zhǎng)時(shí)間不用就會(huì)忘,復(fù)習(xí)一下!




          JS 文件絕大多數(shù)需要通過(guò)網(wǎng)絡(luò)進(jìn)行加載,然后執(zhí)行。DCE(dead code elimination)可以使得加載文件的大小更小,整體執(zhí)行時(shí)間更短。tree shaking?就是通常用于描述移除 JavaScript 上下文中的未引用代碼(dead-code)。它依賴于 ES2015 模塊語(yǔ)法的?靜態(tài)結(jié)構(gòu)?特性,例如?import?和?export

          原理

          ESM

          • import 只能作為模塊頂層的語(yǔ)句出現(xiàn)
          • import 的模塊名只能是字符串常量
          • import binding 是 immutable 的

          這就是區(qū)別于CMJ,ESM 獨(dú)有的靜態(tài)分析特性。等等,那什么是靜態(tài)分析呢,就是不執(zhí)行代碼。CMJ 中的 require,只有執(zhí)行以后才知道引用的是什么模塊。

          保證了依賴關(guān)系是確定的,和運(yùn)行時(shí)的狀態(tài)無(wú)關(guān),可以進(jìn)行可靠的靜態(tài)分析。靜態(tài)分析會(huì)在繪制依賴圖時(shí)做DCE,減少打包體積。

          ESM 也支持動(dòng)態(tài)引入,類似于下面這種引入方式是不支持Tree Shacking的。

          if?(false)?{
          ??import('./a.js').then(()?=>?{//?...})
          }?else?{
          ??//?...
          }
          //?antd.js
          var?emptyObject?=?{};

          if?(true)?{
          ??Object.freeze(emptyObject);
          }
          module.exports?=?emptyObject;


          Dead Code

          Dead Code 通常是指:

          • 代碼不會(huì)被執(zhí)行
          • 代碼執(zhí)行的結(jié)果不會(huì)被用到
          • 代碼只會(huì)影響死變量(只寫不讀)
          //?導(dǎo)入并賦值給?JavaScript?對(duì)象,但在接下來(lái)的代碼里沒(méi)有用到
          //?這就會(huì)被當(dāng)做“死”代碼,會(huì)被?tree-shaking
          import?Stuff?from?'./stuff';
          doSomething();


          //?導(dǎo)入但沒(méi)有賦值給?JavaScript?對(duì)象,也沒(méi)有在代碼里用到
          //?這會(huì)被當(dāng)做“死”代碼,會(huì)被?tree-shaking
          import?'./stuff';
          doSomething();

          //?全部導(dǎo)入?(不支持?tree-shaking)
          import?_?from?'lodash';
          //?具名導(dǎo)入(支持?tree-shaking)
          import?{?debounce?}?from?'lodash';
          //?直接導(dǎo)入具體的模塊?(支持?tree-shaking)
          import?debounce?from?'lodash/lib/debounce';
          //?導(dǎo)入并賦值給?JavaScript?對(duì)象,然后在下面的代碼中被用到
          //?這會(huì)被看作“活”代碼,不會(huì)做?tree-shaking
          import?Stuff?from?'./stuff';
          doSomething(Stuff);

          //?導(dǎo)入整個(gè)庫(kù),但是沒(méi)有賦值給?JavaScript?對(duì)象,也沒(méi)有在代碼里用到
          //?非常奇怪,這竟然被當(dāng)做“活”代碼,因?yàn)?Webpack 對(duì)庫(kù)的導(dǎo)入和本地代碼導(dǎo)入的處理方式不同。
          import?'my-lib';
          export?{?default?as?Title?}?from?'./Title';
          export?{?default?as?Options?}?from?'./Options';
          export?{?default?as?AddonArea?}?from?'./AddonArea';
          export?{?default?as?Answer?}?from?'./AddonArea/Answer';
          export?{?default?as?Analysis?}?from?'./AddonArea/Analysis';
          export?{?default?as?OriginalText?}?from?'./AddonArea/OriginalText';
          export?{?default?as?Labels?}?from?'./AddonArea/Labels';

          這樣的文件結(jié)構(gòu)是無(wú)法進(jìn)行 tree-shaking 的, 因?yàn)闆](méi)有 import?!

          自執(zhí)行的模塊 import
          自執(zhí)行模塊我們通常會(huì)使用?import 'xxx'?來(lái)進(jìn)行模塊引用,而不進(jìn)行顯式的調(diào)用。因此模塊本身就有副作用。

          import?'utils/refresh'

          對(duì)于這種模塊可以這樣處理:

          • 在 sideEffects 中通過(guò)數(shù)組聲明,使其在 Tree Shaking 的范圍之外
          • 模塊改造,暴露成員支持顯式調(diào)用


          unused harmony export
          如果該模塊被標(biāo)識(shí)為 unused harmony export,則說(shuō)明沒(méi)有外部引用使用到該成員,webpack 認(rèn)為是可以安全去除的。

          harmony export
          部分被標(biāo)識(shí)為 harmony export 的模塊也會(huì)被去除。這個(gè)是跟 UglifyJS 的機(jī)制有關(guān)系。

          沒(méi)有提供導(dǎo)出成員的模塊

          //?./src/modules/edu-discount/seckill/index.ts

          import?*?as?SeckillTypes?from?'./types';
          export?{?SeckillTypes?};

          對(duì)于只有暴露的成員,但是沒(méi)有被引用的成員,這種模塊會(huì)被直接刪除。

          • [x] exports provided
          • [ ] exports used

          配置

          babel的配置文件

          {
          ??"presets":?[
          ????["env",?{
          ??????"modules":?false??//?配置了這個(gè),babel就不會(huì)像默認(rèn)那樣轉(zhuǎn)變成 require 形式。
          ????}],
          ????"stage-2",
          ????"react"
          ??]
          }

          為 webpack 進(jìn)行 tree-shaking 創(chuàng)造了條件。
          ??不能引用類似?@babel/plugin-transform-modules-commonjs會(huì)把模塊編譯成 commonjs 的插件;

          webpack 的配置文件

          webpack 4 通過(guò) optimization 取代了4個(gè)常用的插件:

          廢棄插件optimization 屬性功能
          UglifyjsWebpackPluginsideEffectsminimizerTree Shaking & Minimize
          ModuleConcatenationPluginconcatenateModulesScope hoisting生產(chǎn)環(huán)境默認(rèn)開(kāi)啟
          CommonsChunkPluginsplitChunksruntimeChunkOccurrenceOrder
          NoEmitOnErrorsPluginNoEmitOnErrors編譯出現(xiàn)錯(cuò)誤時(shí),跳過(guò)輸出階段生產(chǎn)環(huán)境默認(rèn)開(kāi)啟

          usedExports

          Webpack 將識(shí)別出它認(rèn)為沒(méi)有被使用的代碼,并在最初的打包步驟中給它做標(biāo)記。

          //?Base?Webpack?Config?for?Tree?Shaking
          const?config?=?{
          ?mode:?'production',
          ?optimization:?{
          ??usedExports:?true,
          ??minimizer:?[
          ???new?TerserPlugin({...})?//?支持刪除死代碼的壓縮器
          ??]
          ?}
          };

          package.json 的配置

          用過(guò) redux 的童鞋應(yīng)該對(duì)純函數(shù)不陌生,自然也就應(yīng)該了解函數(shù)式編程,函數(shù)式編程中就有副作用一說(shuō)。


          照顧一下不知道的同學(xué),那什么是副作用呢?

          一個(gè)函數(shù)會(huì)、或者可能會(huì)對(duì)函數(shù)外部變量產(chǎn)生影響的行為。


          具有副作用的文件不應(yīng)該做 tree-shaking,因?yàn)檫@將破壞整個(gè)應(yīng)用程序。比如全局樣式表及全局的 JS 配置文件。
          webpack 總會(huì)害怕把你要用的代碼刪除了,所以默認(rèn)所有的文件都有副作用,不能被 Tree Shaking。

          //?所有文件都有副作用,全都不可?tree-shaking
          {
          ?"sideEffects":?true
          }

          //?沒(méi)有文件有副作用,全都可以 tree-shaking,即告知 webpack,它可以安全地刪除未用到的 export。
          {
          ?"sideEffects":?false
          }

          //?除了數(shù)組中包含的文件外有副作用,所有其他文件都可以?tree-shaking,但會(huì)保留符合數(shù)組中條件的文件
          {
          ?"sideEffects":?[
          ???"*.css",
          ???"*.less"
          ?]
          }

          所以,首先關(guān)閉你的 sideEffects,
          直接通過(guò)?module.rules?中的 sideEffects 配置可縮小你的影響范圍。
          加了 sideEffect 配置后,構(gòu)建出來(lái)的一些 IIFE 函數(shù)也會(huì)加上/PURE/注釋,便于后續(xù) treeshaking。

          組件不支持DCE?


          我們的組件用的是 father,可以看到其依賴的father-build 是基于 rollup 的,那就好辦了。webpack 的 Tree Shaking 還是 copy 的 rollup家的。

          關(guān)鍵是在應(yīng)用組件的業(yè)務(wù)項(xiàng)目里面配置optimization.sideEffects: true

          //?webpack.config.js
          const?path?=?require('path')
          const?webpackConfig?=?{
          ??module?:?{
          ????rules:?[
          ??????{
          ????????test:?/\.(jsx|js)$/,
          ????????use:?'babel-loader',
          ????????exclude:?path.resolve(__dirname,?'node_modules')
          ??????}???
          ????]
          ??},
          optimization?:?{
          ??sideEffects:?true,
          ??minimizer:?[
          ????//?這里配置成空數(shù)組是為了使最終產(chǎn)生的?main.js?不被壓縮
          ??]
          },
          ??plugins:[]
          };
          module.exports?=?webpackConfig;
          //?package.json
          {
          ??"name":?"treeshaking-test",
          ??"version":?"0.1.0",
          ??"description":?"",
          ??"main":?"src/index.js",
          ??"scripts":?{
          ????"build":?"webpack?--config?webpack.config.js"
          ??},
          ??"author":?"lu.lu??(https://github.com/lulu27753)",
          ??"license":?"MIT",
          ??"dependencies":?{
          ????"big-module":?"^0.1.0",
          ????"big-module-with-flag":?"^0.1.0",
          ????"webpack-bundle-analyzer":?"^3.7.0"
          ??},
          ??"devDependencies":?{
          ????"babel-preset-env":?"^1.7.0",
          ????"webpack":?"^4.43.0",
          ????"webpack-cli":?"^3.3.11"
          ??}
          }
          //?.babelrc
          {
          ??"presets":?[
          ????["env",?{?"modules":?false?}]
          ??]
          }

          可以看到最終打包后的文件如下:

          //?dist/main.js
          "use?strict";
          //?ESM?COMPAT?FLAG
          __webpack_require__.r(__webpack_exports__);

          //?CONCATENATED?MODULE:?./node_modules/big-module/es/a.js
          var?a?=?'a';
          //?CONCATENATED?MODULE:?./node_modules/big-module/es/b.js
          var?b?=?'b';
          //?CONCATENATED?MODULE:?./node_modules/big-module/es/c.js
          var?c?=?'c';
          //?CONCATENATED?MODULE:?./node_modules/big-module/es/index.js



          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/a.js
          var?a_a?=?'a';
          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/b.js
          var?b_b?=?'b';
          //?CONCATENATED?MODULE:?./src/index.js


          console.log(a,?b,?a_a,?b_b);

          /***/?})
          /******/?]);

          可以很清楚的看到?big-module-with-flag?中的 c 模塊被DCE了。


          做個(gè)小小的改動(dòng),將?.babelrc?中的?modules?改為"commonjs"

          {
          ??"presets":?[
          ????["env",?{?"modules":?"commonjs"?}]
          ??]
          }
          "use?strict";
          //?ESM?COMPAT?FLAG
          __webpack_require__.r(__webpack_exports__);

          //?EXPORTS
          __webpack_require__.d(__webpack_exports__,?"a",?function()?{?return?/*?reexport?*/?a;?});
          __webpack_require__.d(__webpack_exports__,?"b",?function()?{?return?/*?reexport?*/?b;?});
          __webpack_require__.d(__webpack_exports__,?"c",?function()?{?return?/*?reexport?*/?c;?});

          //?CONCATENATED?MODULE:?./node_modules/big-module/es/a.js
          var?a?=?'a';
          //?CONCATENATED?MODULE:?./node_modules/big-module/es/b.js
          var?b?=?'b';
          //?CONCATENATED?MODULE:?./node_modules/big-module/es/c.js
          var?c?=?'c';
          //?CONCATENATED?MODULE:?./node_modules/big-module/es/index.js

          /***/?}),
          /*?2?*/
          /***/?(function(module,?__webpack_exports__,?__webpack_require__)?{

          "use?strict";
          //?ESM?COMPAT?FLAG
          __webpack_require__.r(__webpack_exports__);

          //?EXPORTS
          __webpack_require__.d(__webpack_exports__,?"a",?function()?{?return?/*?reexport?*/?a;?});
          __webpack_require__.d(__webpack_exports__,?"b",?function()?{?return?/*?reexport?*/?b;?});
          __webpack_require__.d(__webpack_exports__,?"c",?function()?{?return?/*?reexport?*/?c;?});

          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/a.js
          var?a?=?'a';
          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/b.js
          var?b?=?'b';
          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/c.js
          var?c?=?'c';
          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/index.js

          /***/?})
          /******/?]);

          結(jié)果是?CDE?失敗!
          將?modules?的值改回去,并升級(jí)big-module-with-flag為0.2.0。CDE?成功,可以打假一波了(?,網(wǎng)上很多文章都是基于webpack3的,過(guò)時(shí)了)


          升級(jí)big-module-with-flag為0.5.0, 并更改?src/index.js

          import?{?a?as?a1,?b?as?b1?}?from?"big-module";
          import?{?a?as?a2,?b?as?b2,?Apple??}?from?"big-module-with-flag";

          console.log(a1,?b1,?a2,?b2);

          const?appleModel?=?new?Apple({model:?'IphoneX'}).getModel()

          console.log(appleModel)
          var?Apple?=?/*#__PURE__*/function?()?{
          ??function?Apple(_ref)?{
          ????var?model?=?_ref.model;

          ????_classCallCheck(this,?Apple);

          ????this.className?=?'Apple';
          ????this.model?=?model;
          ??}

          ??_createClass(Apple,?[{
          ????key:?"getModel",
          ????value:?function?getModel()?{
          ??????return?this.model;
          ????}
          ??}]);

          ??return?Apple;
          }();


          //?CONCATENATED?MODULE:?./src/index.js


          console.log(a,?b,?es_a,?es_b);
          var?appleModel?=?new?Apple({
          ??model:?'IphoneX'
          }).getModel();
          console.log(appleModel);

          DCE 成功!

          var?_bigModule?=?__webpack_require__(2);

          var?_bigModuleWithFlag?=?__webpack_require__(1);

          console.log(_bigModule.a,?_bigModule.b,?_bigModuleWithFlag.a,?_bigModuleWithFlag.b);
          var?appleModel?=?new?_bigModuleWithFlag.Apple({
          ??model:?'IphoneX'
          }).getModel();
          console.log(appleModel);

          /***/?}),
          /*?1?*/
          /***/?(function(module,?__webpack_exports__,?__webpack_require__)?{

          "use?strict";
          //?ESM?COMPAT?FLAG
          __webpack_require__.r(__webpack_exports__);

          //?EXPORTS
          __webpack_require__.d(__webpack_exports__,?"a",?function()?{?return?/*?reexport?*/?es_a;?});
          __webpack_require__.d(__webpack_exports__,?"b",?function()?{?return?/*?reexport?*/?es_b;?});
          __webpack_require__.d(__webpack_exports__,?"c",?function()?{?return?/*?reexport?*/?es_c;?});
          __webpack_require__.d(__webpack_exports__,?"Person",?function()?{?return?/*?reexport?*/?Person;?});
          __webpack_require__.d(__webpack_exports__,?"Apple",?function()?{?return?/*?reexport?*/?Apple;?});

          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/a.js
          var?a?=?'a';
          /*?harmony?default?export?*/?var?es_a?=?(a);
          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/b.js
          var?b?=?'b';
          /*?harmony?default?export?*/?var?es_b?=?(b);
          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/c.js
          var?c?=?'c';
          /*?harmony?default?export?*/?var?es_c?=?(c);
          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/Person.js
          function?_classCallCheck(instance,?Constructor)?{?if?(!(instance?instanceof?Constructor))?{?throw?new?TypeError("Cannot?call?a?class?as?a?function");?}?}

          function?_defineProperties(target,?props)?{?for?(var?i?=?0;?i?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?Person?=?/*#__PURE__*/function?()?{
          ??function?Person(_ref)?{
          ????var?name?=?_ref.name,
          ????????age?=?_ref.age,
          ????????sex?=?_ref.sex;

          ????_classCallCheck(this,?Person);

          ????this.className?=?'Person';
          ????this.name?=?name;
          ????this.age?=?age;
          ????this.sex?=?sex;
          ??}

          ??_createClass(Person,?[{
          ????key:?"getName",
          ????value:?function?getName()?{
          ??????return?this.name;
          ????}
          ??}]);

          ??return?Person;
          }();


          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/Apple.js
          function?Apple_classCallCheck(instance,?Constructor)?{?if?(!(instance?instanceof?Constructor))?{?throw?new?TypeError("Cannot?call?a?class?as?a?function");?}?}

          function?Apple_defineProperties(target,?props)?{?for?(var?i?=?0;?i?false;?descriptor.configurable?=?true;?if?("value"?in?descriptor)?descriptor.writable?=?true;?Object.defineProperty(target,?descriptor.key,?descriptor);?}?}

          function?Apple_createClass(Constructor,?protoProps,?staticProps)?{?if?(protoProps)?Apple_defineProperties(Constructor.prototype,?protoProps);?if?(staticProps)?Apple_defineProperties(Constructor,?staticProps);?return?Constructor;?}

          var?Apple?=?/*#__PURE__*/function?()?{
          ??function?Apple(_ref)?{
          ????var?model?=?_ref.model;

          ????Apple_classCallCheck(this,?Apple);

          ????this.className?=?'Apple';
          ????this.model?=?model;
          ??}

          ??Apple_createClass(Apple,?[{
          ????key:?"getModel",
          ????value:?function?getModel()?{
          ??????return?this.model;
          ????}
          ??}]);

          ??return?Apple;
          }();


          //?CONCATENATED?MODULE:?./node_modules/big-module-with-flag/es/index.js






          /***/?}),
          /*?2?*/
          /***/?(function(module,?__webpack_exports__,?__webpack_require__)?{

          "use?strict";
          //?ESM?COMPAT?FLAG
          __webpack_require__.r(__webpack_exports__);

          //?EXPORTS
          __webpack_require__.d(__webpack_exports__,?"a",?function()?{?return?/*?reexport?*/?a;?});
          __webpack_require__.d(__webpack_exports__,?"b",?function()?{?return?/*?reexport?*/?b;?});
          __webpack_require__.d(__webpack_exports__,?"c",?function()?{?return?/*?reexport?*/?c;?});

          //?CONCATENATED?MODULE:?./node_modules/big-module/es/a.js
          var?a?=?'a';
          //?CONCATENATED?MODULE:?./node_modules/big-module/es/b.js
          var?b?=?'b';
          //?CONCATENATED?MODULE:?./node_modules/big-module/es/c.js
          var?c?=?'c';
          //?CONCATENATED?MODULE:?./node_modules/big-module/es/index.js


          //?.babelrc
          {
          ??"presets":?[["env",?{?"loose":?false?}]]
          }




          總結(jié)

          webpack 官方號(hào)稱提速 98%,其最重要的前提就是你的模塊引入方式要是ESM,而不能是因?yàn)榧嫒菪钥紤]的UMD實(shí)現(xiàn)。

          如果你是一個(gè)第三方庫(kù)的維護(hù)者,請(qǐng)人性化的按業(yè)界規(guī)范提供ES版本,同時(shí)配置 sideEffects: false.

          • Webpack 只有在壓縮代碼的時(shí)候會(huì) tree-shaking, 通常就指是生產(chǎn)環(huán)境
          • 代碼的 module 引入必須是 import 的引入方式,也就意味著被轉(zhuǎn)換成 ES5 的代碼是無(wú)法支持 tree-shaking 的。

          滿足了文件要求后,簡(jiǎn)單來(lái)說(shuō)你需要做如下配置操作

          • [x] 在 package.json 文件中將 sideEffects 設(shè)為 false
          • [x] 將css相關(guān) loader中 sideEffects 設(shè)為 true
          • [x] 讓@babel/preset-env 不編譯 ES6 模塊語(yǔ)句
          • [ ] 使用TerserPlugin,js代碼壓縮插件(webpack 自帶)

          參考

          webpack 官方文檔:https://webpack.docschina.org/guides/tree-shaking/
          官方DEMO:https://github.com/webpack/webpack/tree/master/examples/side-effects
          webpack 新插件系統(tǒng)如何工作:https://medium.com/webpack/the-new-plugin-system-week-22-23-c24e3b22e95
          Tree-Shaking原理:https://juejin.im/post/5a4dc842518825698e7279a9
          組件沒(méi)辦法DCE?:https://zhuanlan.zhihu.com/p/32831172

          瀏覽 108
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  69成人电影 | 高清一级无码 | 色婷婷yy | 九热视频在线观看 | 六月婷婷综合中文字幕 |