Tree Shaking

Tree Shaking (树摇)表示去掉代码中没有使用到的代码,减少代码体积。

webpack 中可以对 JavaScript 代码进行 Tree Shaking,但是需要依赖于 ES Module 的静态语法分析,在 webpack5 中也提供了部分 CommonJS 的 tree shaking 支持。

webpack 实现 Tree Shaking

在 webpack 中可以通过两种方案来实现 Tree Shaking:

  1. - usedExports:通过标记某些函数是否被使用,之后通过 terser 来进行优化。
  2. - sideEffects 跳过整个模块/文件,直接查看该文件是否有副作用,没有则直接移除。

接下来基于下面的文件结构来查看 Tree Shaking 的效果:

  1. // index.js 入口文件
  2. import { addition } from './js/index';
  3. console.log(addition(1,2));
// .js/index.js
export const addition = (a, b) => {
  return a + b;
}

export const multiply = (num1, num2) => num1 * num2;

usedExports

修改 webpack 配置:

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  // ...
  mode: 'development',
    optimization: {
      usedExports: true,
    minimize: true,
    minimizer: {
        new TerserPlugin({
        parallel: true,
        extractComments: false,
        terserOptions: {
          compress: {
            arguments: false
          },
        },
      }),
    },
  },
}
使用上面的配置后已经去掉了未使用的 `multiply` 函数:<br />![](https://cdn.nlark.com/yuque/0/2021/png/1561260/1618642014262-a344f755-ca62-4172-b1f9-76a7a45a6051.png#clientId=ufbc8344d-9945-4&from=paste&height=106&id=ucf39f4a1&margin=%5Bobject%20Object%5D&originHeight=212&originWidth=1230&originalType=binary&size=63973&status=done&style=stroke&taskId=u3b69a00a-7449-4620-87c0-8cf25170fbd&width=615)<br />如果我们将 Terser 的配置关闭掉:
minimize: false,

去掉 Terser 配置后再进行打包后的代码如下图,在图中可以看到 multiply 没有被移除掉,不过绿箭头指的这一段注释就是 usedExports 所添加一个标记(_/* unused harmony export multiply */_),后面 Terser 会处理这个标记,所以 usedExports 是和 Terser 配合使用的。
webpack 的性能优化(二) - 图1

sideEffects

sideEffects 用于告知 webpack compiler 哪些模块是有副作用的,如果一个模块没有副作用且引入了没有被使用,那么 webpack 就可以安全的将其删除。

sideEffect 是配置到 package.json 文件中的,而不是 webpack.config 中,配置项如下:

  - true:表示所有模块都有副作用
  - false:表示所有模块都没有副作用
  - []:配置哪些模块有副作用,数组中指定路径或匹配方式

注:如果设置为 false 则表示所有模块都没有副作用,可能会影响到 css 、html 等文件。

在使用 sideEffect 之前我们修改一下 index 的代码:

import './js/index';

console.log('test');

然后先使用 usedExports 方式进行打包:
webpack 的性能优化(二) - 图2
打包后的代码如上图,我们可以看到在 index 并没有用到 ./js/index 文件中的任何代码,但是打包后的代码依然有它的影子(.js/index.js 中的代码确实没有被打包进来),这就说明 usedExports 删除的不是很干净。

配置 sideEffect:

{
    "sideEffects": false
}
打包后的效果如下:<br />![](https://cdn.nlark.com/yuque/0/2021/png/1561260/1618643278295-75f8e97b-6923-44db-9443-726dae297872.png#clientId=ufbc8344d-9945-4&from=paste&height=90&id=ubea7ac3d&margin=%5Bobject%20Object%5D&originHeight=179&originWidth=552&originalType=binary&size=19192&status=done&style=stroke&taskId=uee378aca-e753-4c23-babd-809525aa2a6&width=276)<br />我们可以看到 `./js/index` 彻底没有了,但是因为这里设置的 sideEffects 为 true,这样则表示所有的文件都是没有副作用的,假如我们引入了一个 css 文件其实也是会被 Tree Shaking 掉的:
// index.js 入口文件
import './js/index';
import './styles.css'

console.log('test');

打包后没有 CSS 文件(这里使用了 mini-css-extract-plugin 将 CSS 打包为文件)。
webpack 的性能优化(二) - 图3
修改 sideEffect 配置:

"sideEffect": [
    "**/*.css",
]
修改为上面的配置后所有 CSS 就可以被打包了,假如 JS 文件中存在副作用也可以配置到这里,但是不推荐在模块中写副作用的代码,所以这里只配置 css 就好了,不过我们也可以将这里的 sideEffect 设置为 false,然后在 webpack 中单独配置 sideEffect 为 true,实现相同的效果:
"sideEffect": false
// webpack.config
module.exports = {
    module: {
      rules: [
       {
        test: /\.css/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
        ],
        sideEffects: true, // 注意:这里配置的是 true, 表示匹配到的文件有副作用
      },
    ],
  },
}

Tree Shaking 的设置

sideEffect 只会在一个模块引入但是没有被使用的情况下才会将其 Tree Shaking 掉,只要使用了模块中的任意一个变量则不会被 Tree Shaking,也就是说这个模块中未使用的代码不会被 Tree Shaking。

修改入口文件如下:

import { addition } from './js/index';
import './styles.css';

console.log(addition(1,2));

上面代码中只是用了 addition 函数,但是没有使用 multiply 函数,打包后的代码如下图,可以看到 multiply 并没有被 Tree Shaking 掉(使用了或有副作用则不删除、没使用且没有副作用则删除):
webpack 的性能优化(二) - 图4

CSS 的 Tree Shaking

CSS 也可以实现 Tree Shaking(比如有些类并没有使用到),实现 CSS Tree Shaking 需要下载 PurgeCSS 插件:

npm i purgecss-webpack-plugin -D
修改 webpack 配置:
const PurgeCssPlugin = require('purgecss-webpack-plugin');
const glob = require('glob'); // 需要使用 glob

module.exports = {
    plugins: [
      new PurgeCssPlugin({
      // 匹配 src 下面的所有文件
      paths: glob.sync(`${path.resolve(__dirname, './src')}/**/*`, {
        nodir: true, // 不匹配文件夹
      }),
      safelist: function() {
        return {
          // html 和 body 不能被 Tree Shaking 掉
          standard: ['html', 'body'],
        };
      }
    }),
  ],
}

HTTP 压缩

HTTP 压缩是一种内置在服务器和客户端之间以改进传输速度和带宽利用率的方式。

HTTP 压缩流程:
1.HTTP数据一般在服务器发送前就已经被压缩过了(webpack 中完成)
2.兼容的浏览器在向服务器发送请求时,会在请求头中告知自己支持哪些压缩格式 Accept-Encoding: gzip, deflate
3.服务器在浏览器支持的压缩格式下返回对应的压缩文件,并在响应头中告知浏览器 Content-Encoding: gzip

目前的压缩格式非常多,常用的有:

  - deflate
  - gzip
  - br

webpack 实现文件压缩

实现 HTTP 压缩需要下载一个对应的插件:

npm i compression-webpack-plugin -D

修改 webpack 配置:

const CommpressionWebpackPlugin = require('commpress-webpack-plugin');

module.epxorts = {
    plugins: [
      new CompressionWebpackPlugin({
      test: /\.(js|css)$/i, // 匹配文件
      minRatio: 0.7, // 最少压缩比例
      algorithm: 'gzip', // 压缩算法
    }),
  ],
}