webpack实现Tree Shaking

668 阅读4分钟

webpack 5-

TreeShaking顾名思义是将代码中未使用的代码删除掉,从而减小整个文件的打包体积。
让我们先来看看没有进行Tree Shaking之前的打包文件:
mamth.js:

export function square(x) {
    return x * x;
}

export function cube(x) {
    return x * x * x;
}

入口文件:

import { cube } from './math.js';

function component() {
    const element = document.createElement('pre');
    element.innerHTML = [
        'Hello webpack!',
        '5 cubed is equal to ' + cube(5)
    ].join('\n\n');

    return element;
}

document.body.appendChild(component());

webpack.config.js配置文件:

entry: {
        index: './src/index.js'
    },
mode: 'development',

optimization: {
        usedExports: true
    },

webpack进行打包:

eval("/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"cube\": () => (/* binding */ cube)\n/* harmony export */ });\n/* unused harmony export square */\nfunction square(x) {\n    return x * x;\n}\n\nfunction cube(x) {\n    return x * x * x;\n}\n\n\n//# sourceURL=webpack://webpack/./src/math.js?");

可以看到打包文件中包含了math模块中并未引用的square函数,并没有shaking掉
接下里配置是指打包能够shaking掉未使用的代码:

首先配置package.json,加入sideEffects选项:

"sideEffects": false,

sideEffects是配置有副作用的选项,如果所有模块都没有副作用,就将sideEffects的值设置为false即可。

修改webpack.config.js文件,去掉usedExports,将mode设置为上产模式:

entry: {
        index: './src/index.js'
    },
 mode: 'production',

重新打包,可以看到打包文件被压缩,而且压缩代码并没有Square函数,表示未被使用的代码已经被Three shaking掉了。

webpack 5

sideEffects:

在webpack5中已经自带tree-shaking功能,在打包模式为production时,默认开启 tree-shaking功能。

创建2个文件夹:

index.js:

import tool from './util.js'

// 使用a变量,调用文件里面的a函数,不使用b函数
console.log(tool.add(1, 2))
console.log('hello world')

// 不可能执行的代码
if (false) {
 console.log('这是一段不可能执行的代码')
}

// 定义了但是没用的变量
const m = 1

util.js:

function add(a, b) {
 console.log(a, b)
 return a + b
}
function count(num) {
 console.log(num)
 return num++
}
export default {
 add,
 count
}

打包之后可以看到以下几个现象:

  • 1,import但是没有使用的代码被删除了
  • 2,代码中定义但是没有使用的代码也被删除了

这是webpack自动帮我们做的优化,那么如果我们再util.js中添加副作用的代码呢,也就是可能对当前代码没有作用,但可能对全局有作用的一些副作用代码,比如在util.js中添加如下代码:

Array.prototype.side = function () { console.log('这是一段有副作用的代码') }

如果webpack足够智能,那么它打包过程不应该删除这些副作用,实际上webpack也是这样做的,可以查看打包结果,打包文件中确实保留了副作用代码,那么webpack是怎么做到的呢?它是怎么判断一段代码是否有副作用的呢?原来它是借助了terser这个库。terser是一个ES6+的js解析器、处理程序和代码压缩工具,webpack正是依赖这个库进行js的解析和代码压缩。

如果不进行额外的配置,当对一个模块进行导入的时候,不仅会判断使用了哪些导出的代码,还会分析这个模块是否有副作用存在,如果有副作用存在,依然是要将副作用代码一起打包的。

我们如果在package.json中配置:

"sideEffects": false

此配置告诉webpack不用去分析导入的模块是否有副作用,直接保留使用的导出,其他全部删除即可。配置之后在构建,会发现此时打包文件已经不存在副作用代码了。

sideEffects配置项其实更多是用来声明需要进行副作用分析的模块的。因为在实际开发中不可能所有模块都没有副作用。当我们配置了sideEffects,只有包含在sideEffects中的文件,webpack才会进行副作用分析,如果没有包含在sideEffects配置的属组中,webpack就不会去分析,就算这些文件存在副作用,webpack也不会分析,这些不会分析的副作用最终都会被删除。

"sideEffects": [ "**/*.css" ]

最后附上webpack官网的总结:

image.png

usedExports: index.js:

import tool from './util.js'
// 使用a变量,调用文件里面的a函数,不使用b函数
// console.log(tool.add(1, 2))
console.log('hello world')
console.log(tool.add(1, 1))
// 不可能执行的代码
if (false) {
  console.log('这是一段不可能执行的代码')
}

// 定义了但是没用的变量
const no_usem = 1

util.js:

function add(a, b) {
    console.log(a, b)
    return a + b
}
function count(num) {
    console.log(num)
    return num++
}

Array.prototype.side = function () {
    console.log('这是一段有副作用的代码')
}
export default {
    add,
    count
}

将webpack配置mode='development',查看构建结果:

  • 不可执行的代码段会被删除
  • 没有引用的代码会被保留
  • 引入模块的所有代码都会保留

配置webpack.config.js:

mode: 'development',
  optimization: {
    minimize: true,
    usedExports: true,
  },

重新进行打包,结果如下:

  • 没有引用的代码没有被删除
  • 引入模块的所有代码都会保留

可以看到使用usedExports之后,没有被引用的代码被删除了,这时候仍存在一个问题,如果通过模块化引入另一个js文件,即使没有被使用,useExports 也不会进行 tree shaking。我们希望在引入的文件中也进行 tree shaking,删除无用的代码,这个时候在 package.json 中配置 sideEffects 属性来处理。