1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        聊一聊面試中經常被問到的Tree Shaking

        共 9376字,需瀏覽 19分鐘

         ·

        2020-08-23 02:33


        天下武功,唯快不破!最新版的 antd 以及 vue 都對 Tree Shaking 提供了支持。我們內部的組件在支持這部分功能時,也專門梳理了相關的特性。這是四月份寫的文章了,長時間不用就會忘,復習一下!




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

        原理

        ESM

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

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

        保證了依賴關系是確定的,和運行時的狀態(tài)無關,可以進行可靠的靜態(tài)分析。靜態(tài)分析會在繪制依賴圖時做DCE,減少打包體積。

        ESM 也支持動態(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 通常是指:

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


        //?導入但沒有賦值給?JavaScript?對象,也沒有在代碼里用到
        //?這會被當做“死”代碼,會被?tree-shaking
        import?'./stuff';
        doSomething();

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

        //?導入整個庫,但是沒有賦值給?JavaScript?對象,也沒有在代碼里用到
        //?非常奇怪,這竟然被當做“活”代碼,因為 Webpack 對庫的導入和本地代碼導入的處理方式不同。
        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';

        這樣的文件結構是無法進行 tree-shaking 的, 因為沒有 import?!

        自執(zhí)行的模塊 import
        自執(zhí)行模塊我們通常會使用?import 'xxx'?來進行模塊引用,而不進行顯式的調用。因此模塊本身就有副作用。

        import?'utils/refresh'

        對于這種模塊可以這樣處理:

        • 在 sideEffects 中通過數組聲明,使其在 Tree Shaking 的范圍之外
        • 模塊改造,暴露成員支持顯式調用


        unused harmony export
        如果該模塊被標識為 unused harmony export,則說明沒有外部引用使用到該成員,webpack 認為是可以安全去除的。

        harmony export
        部分被標識為 harmony export 的模塊也會被去除。這個是跟 UglifyJS 的機制有關系。

        沒有提供導出成員的模塊

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

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

        對于只有暴露的成員,但是沒有被引用的成員,這種模塊會被直接刪除。

        • [x] exports provided
        • [ ] exports used

        配置

        babel的配置文件

        {
        ??"presets":?[
        ????["env",?{
        ??????"modules":?false??//?配置了這個,babel就不會像默認那樣轉變成 require 形式。
        ????}],
        ????"stage-2",
        ????"react"
        ??]
        }

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

        webpack 的配置文件

        webpack 4 通過 optimization 取代了4個常用的插件:

        廢棄插件optimization 屬性功能
        UglifyjsWebpackPluginsideEffectsminimizerTree Shaking & Minimize
        ModuleConcatenationPluginconcatenateModulesScope hoisting生產環(huán)境默認開啟
        CommonsChunkPluginsplitChunksruntimeChunkOccurrenceOrder
        NoEmitOnErrorsPluginNoEmitOnErrors編譯出現錯誤時,跳過輸出階段生產環(huán)境默認開啟

        usedExports

        Webpack 將識別出它認為沒有被使用的代碼,并在最初的打包步驟中給它做標記。

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

        package.json 的配置

        用過 redux 的童鞋應該對純函數不陌生,自然也就應該了解函數式編程,函數式編程中就有副作用一說。


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

        一個函數會、或者可能會對函數外部變量產生影響的行為。


        具有副作用的文件不應該做 tree-shaking,因為這將破壞整個應用程序。比如全局樣式表及全局的 JS 配置文件。
        webpack 總會害怕把你要用的代碼刪除了,所以默認所有的文件都有副作用,不能被 Tree Shaking。

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

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

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

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

        組件不支持DCE?


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

        關鍵是在應用組件的業(yè)務項目里面配置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:?[
        ????//?這里配置成空數組是為了使最終產生的?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了。


        做個小小的改動,將?.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

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

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


        升級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?}]]
        }




        總結

        webpack 官方號稱提速 98%,其最重要的前提就是你的模塊引入方式要是ESM,而不能是因為兼容性考慮的UMD實現。

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

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

        滿足了文件要求后,簡單來說你需要做如下配置操作

        • [x] 在 package.json 文件中將 sideEffects 設為 false
        • [x] 將css相關 loader中 sideEffects 設為 true
        • [x] 讓@babel/preset-env 不編譯 ES6 模塊語句
        • [ ] 使用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
        組件沒辦法DCE?:https://zhuanlan.zhihu.com/p/32831172

        瀏覽 108
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            韩国一级黄色视频 | 和少妇做爰猛烈叫床很黄口述 | 黄色影院在线免费观看 | 男人舔女人下部视频 | 亚洲色婷婷一区二区三区 | 日逼网页 | 亚洲黄色av | 伊人99在线视频 | 91污视频在线观看 | 性爱小说网站 |