2024年前端最全聊一聊面试中经常被问到的Tree Shaking,前端组件化和插件化

本文介绍了Web开发中如何利用权威文档学习,重点关注了TreeShaking、Webpack配置、ESM模块和DCE(DeadCodeElimination),强调了在前端工程中处理副作用的重要性以及如何通过配置避免不必要的代码被删除。
摘要由CSDN通过智能技术生成

最后

推荐一些系统学习的途径和方法。

路线图

每个Web开发人员必备,很权威很齐全的Web开发文档。作为学习辞典使用,可以查询到每个概念、方法、属性的详细解释,注意使用英文关键字搜索。里面的一些 HTML,CSS,HTTP 技术教程也相当不错。

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

HTML 和 CSS:

html5知识

css基础知识

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?!

自执行的模块 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({

最后

好了,这就是整理的前端从入门到放弃的学习笔记,还有很多没有整理到,我也算是边学边去整理,后续还会慢慢完善,这些相信够你学一阵子了。

做程序员,做前端工程师,真的是一个学习就会有回报的职业,不看出身高低,不看学历强弱,只要你的技术达到应有的水准,就能够得到对应的回报。

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

学习从来没有一蹴而就,都是持之以恒的,正所谓活到老学到老,真正懂得学习的人,才不会被这个时代的洪流所淘汰。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值