聊一聊面试中经常被问到的Tree Shaking

这样的文件结构是无法进行 tree-shaking 的, 因为没有 import?!

自执行的模块 import

自执行模块我们通常会使用 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 创造了条件。

⚠️不能引用类似 @babel/plugin-transform-modules-commonjs会把模块编译成 commonjs 的插件;

webpack 的配置文件

webpack 4 通过 optimization 取代了4个常用的插件:

| 废弃插件 | optimization 属性 | 功能 |

|

| — | — | — | — |

| UglifyjsWebpackPlugin | sideEffects | minimizer | Tree Shaking & Minimize |

| ModuleConcatenationPlugin | concatenateModules | Scope hoisting | 生产环境默认开启 |

| CommonsChunkPlugin | splitChunks | runtimeChunk | OccurrenceOrder |

| NoEmitOnErrorsPlugin | NoEmitOnErrors | 编译出现错误时,跳过输出阶段 | 生产环境默认开启 |

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/注释,便于后续 treeshaking。

组件不支持DCE?


我们的组件用的是 father,可以看到其依赖的father-build 是基于 rollup 的,那就好办了。webpack 的 Tree Shaking 还是 copy 的 rollup家的。

关键是在应用组件的业务项目里面配置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 lulu27753@163.com (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_exportswebpack_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_exportswebpack_require) {

“use strict”;

// ESM COMPAT FLAG

webpack_require.r(webpack_exports);

文末

js前端的重头戏,值得花大部分时间学习。

JavaScript知识

推荐通过书籍学习,《 JavaScript 高级程序设计(第 4 版)》你值得拥有。整本书内容质量都很高,尤其是前十章语言基础部分,建议多读几遍。

前端电子书

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

另外,大推一个网上教程 现代 JavaScript 教程 ,文章深入浅出,很容易理解,上面的内容几乎都是重点,而且充分发挥了网上教程的时效性和资料链接。

学习资料在精不在多,二者结合,定能构建你的 JavaScript 知识体系。

面试本质也是考试,面试题就起到很好的考纲作用。想要取得优秀的面试成绩,刷面试题是必须的,除非你样样精通。

这是288页的前端面试题

288页面试题

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值