We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
在webpack对模块进行打包时,将模块中未被使用的冗余代码剔除,仅打包有效代码,精简生成包的体积。
将每个工具函数单独 export,不要集成为一个class,tree shaking的最小单元是一个对象,它不能识别一个对象中的哪些函数是需要的
export
class
tree shaking
在 Webpack 中,启动Tree Shaking功能必须同时满足三个条件:
Tree Shaking
optimization.usedExports
true
mode = production
optimization.minimize = true
optimization.minimizer
我们通过对比ES Module与CommonJS的区别来理解ES Module的模块机制,它们的区别主要体现在模块的输出和执行上,
ES Module
CommonJS
所以ES Module最大的特点就是静态化,在编译时就能确定模块的依赖关系,以及输入和输出的值,这意味着什么?意味着模块的依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,正是基于这个基础,才使得 Tree-Shaking 成为可能,
借助静态模块分析,Tree-Shaking实现的大体思路:借助 ES6 模块语法的静态结构,通过编译阶段的静态分析,找到没有引入的模块并打上标记,然后在压缩阶段利用像uglify-js这样的压缩工具删除这些没有用到的代码。
Tree-Shaking
uglify-js
treeShaking
optimization.providedExports
导出
被使用的导出
import
/* harmony import */
被使用过的export
/harmony export([type])/
没有被使用的export
/* unused harmony export [FuncName] */
后面的说明围绕下例展开:
//my-module.js //my-module.js export const name = 123; export const age = 9999; //index.js import {name, age} from './test.js'; console.log(name);
1)webpack打包(uglifyWebpackPlugin处理前)
全部export被标记为/* harmony export (binding) */;
/* harmony export (binding) */
// my-module.js /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "name", function() { return name; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "age", function() { return age; }); var name = 123; var age = 9999;
2)经过UglifyJSPlugin压缩后不会删除未被使用的导出;
// my-module.js /* harmony export (binding) */ i.d(e, "name", function() {return t;}), /* harmony export (binding) */ i.d(e, "age", function() {return o;}); var t = 123, o = 9999;
结论: 当usedExports:false时,无法对未使用的接口做处理。
usedExports:false
未被使用的export会被标记为/* unused harmony export name */,不会使用__webpack_require__.d进行exports绑定;
/* unused harmony export name */
__webpack_require__.d
exports
// my-module.js /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return name; }); /* unused harmony export age */ var name = 123; var age = 9999;
可以看到age被标记为unused,同时没有使用__webpack_require__.d链接exports。 __webpack_require__.d的作用如下:
age
unused
// define getter function for harmony exports __webpack_require__.d = function(exports, name, getter) { if(!__webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { enumerable: true, get: getter }); } };
2)经过UglifyJSPlugin压缩后,未使用的接口代码会被删除(如果被别的模块import导入但未被使用,同样会被剔除)。原理显而易见,age未被__webpack_require__.d引用。
UglifyJSPlugin
// my-module.js /* harmony export (binding) */ i.d(e, "a", function() {return t;}); /* unused harmony export age */ var t = 123;
不建议在开发环境使用压缩插件。
由于生产环境内置了`ModuleConcatenationPlugin`插件,实现"预编译",让webpack根据模块间的关系依赖图中,将所有的模块连接成一个模块,称为"作用域提升"。对于代码缩小体积有很大的提升,也能侧面解决副作用的问题;每个模块会被标记`//CONCATENATED MODULE`。
//被打包到一个作用域内 (function(module, __webpack_exports__, __webpack_require__) { //... //CONCATENATED MODULE: ./src/my-module.js var test_name = 123; var age = 9999; //CONCATENATED MODULE: ./src/index.js console.log(test_name); }
2)开启uglifyWebpackPlugin:
uglifyWebpackPlugin:
compress: true;函数的调用会被用函数体替换,使用变量处用其对应值代替,将未使用的变量删除。压缩替换后如下:
compress: true;
function(e, n, o) { //... // CONCATENATED MODULE: ./src/index.js console.log(123); }
可以看到导入的age接口未被使用因此被删除,同时优化了多余的中间变量,代码得到精简。
在导入时会执行特殊行为的代码,而不是仅仅暴露一个export 或多个 export。比如console.log()、polyfills、import a CSS file等。由于编译器并不知道其是否会影响运行效果,故而不做处理。
console.log()
polyfills
import a CSS file
// package.json //false:表示该模块无副作用代码,若该模块的所有export没有被使用时,可直接删除该模块 //true:表示该模块有副作用代码,该模块要保留副作用代码 "sideEffects": [Boolean],
或者
// package.json //[file1,file2]:指定有副作用的文件,在webpack作用域提升时就不会引入 "sideEffects": ['*.css', 'src/tool.js'],
import { name } from './module.js'; //name没有使用 import './module.js'
name没有被使用,module.js中没有export被使用,且若module.js中包含副作用代码:
name
module.js
没有export
包含副作用代码
sideEffects
false
module
import { name } from './module.js'; console.log(name)
module.js中name接口被使用,未被使用的其余export都会被删除;无论sidesEffects设置什么值,module.js中的副作用代码始终会被保留。
未被使用的其余export
sidesEffects
The text was updated successfully, but these errors were encountered:
No branches or pull requests
一、作用
在webpack对模块进行打包时,将模块中未被使用的冗余代码剔除,仅打包有效代码,精简生成包的体积。
如何编写易于 Tree shaking 的代码
将每个工具函数单独
export
,不要集成为一个class
,tree shaking
的最小单元是一个对象,它不能识别一个对象中的哪些函数是需要的二、如何使用
在 Webpack 中,启动
Tree Shaking
功能必须同时满足三个条件:optimization.usedExports
为true
,启动标记功能mode = production
optimization.minimize = true
optimization.minimizer
数组三、实现原理
1. ESModule
我们通过对比
ES Module
与CommonJS
的区别来理解ES Module
的模块机制,它们的区别主要体现在模块的输出和执行上,ES Module
输出的是值的引用,而CommonJS
输出的是值的拷贝ES Module
是编译时执行,而CommonJS
模块是在运行时加载所以
ES Module
最大的特点就是静态化,在编译时就能确定模块的依赖关系,以及输入和输出的值,这意味着什么?意味着模块的依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,正是基于这个基础,才使得 Tree-Shaking 成为可能,2. webpack对模块打标记 、删除死代码
借助静态模块分析,
Tree-Shaking
实现的大体思路:借助 ES6 模块语法的静态结构,通过编译阶段的静态分析,找到没有引入的模块并打上标记,然后在压缩阶段利用像uglify-js
这样的压缩工具删除这些没有用到的代码。涉及知识:
treeShaking
依赖于对模块导出和被导入的分析:optimization.providedExports
:确定每个模块的导出
,默认所有模式都开启optimization.usedExports
:确定每个模块下被使用的导出
。生产模式下默认开启,其他模式下不开启。import
和export
标记为3类:import
标记为/* harmony import */
被使用过的export
标记为/harmony export([type])/
没有被使用的export
标记为/* unused harmony export [FuncName] */
,其中[FuncName]为export的方法名四、开发模式和生产模式的默认配置存在差异,其打包方式也存在差异,这里分开讨论:
后面的说明围绕下例展开:
development模式
[optimization.usedExports:false]
1)webpack打包(uglifyWebpackPlugin处理前)
全部
export
被标记为/* harmony export (binding) */
;2)经过UglifyJSPlugin压缩后不会删除未被使用的导出;
结论: 当
usedExports:false
时,无法对未使用的接口做处理。[optimization.usedExports: true]
1)webpack打包(uglifyWebpackPlugin处理前)
未被使用的
export
会被标记为/* unused harmony export name */
,不会使用__webpack_require__.d
进行exports
绑定;可以看到
age
被标记为unused
,同时没有使用__webpack_require__.d
链接exports
。__webpack_require__.d
的作用如下:2)经过
UglifyJSPlugin
压缩后,未使用的接口代码会被删除(如果被别的模块import
导入但未被使用,同样会被剔除)。原理显而易见,age
未被__webpack_require__.d
引用。不建议在开发环境使用压缩插件。
production模式
1)webpack打包(uglifyWebpackPlugin处理前)
2)开启
uglifyWebpackPlugin:
compress: true;
函数的调用会被用函数体替换,使用变量处用其对应值代替,将未使用的变量删除。压缩替换后如下:可以看到导入的age接口未被使用因此被删除,同时优化了多余的中间变量,代码得到精简。
五、sideEffects
在导入时会执行特殊行为的代码,而不是仅仅暴露一个
export
或多个export
。比如console.log()
、polyfills
、import a CSS file
等。由于编译器并不知道其是否会影响运行效果,故而不做处理。在package.json中设置如何处理副作用:
或者
情景1:
name
没有被使用,module.js
中没有export
被使用,且若module.js
中包含副作用代码
:sideEffects
为false
,则副作用也被删除。即module
整个模块都不会被打包;sideEffects
为true
或副作用列表中包含module.js
,则会仅保留其副作用代码。情景2:
module.js
中name
接口被使用,未被使用的其余export
都会被删除;无论sidesEffects
设置什么值,module.js
中的副作用代码始终会被保留。参考文献
The text was updated successfully, but these errors were encountered: