什么是Tree Shaking

什么是TreeShaking呢?

  • TreeShaking是一个术语,在计算机中表示消除死代码(dead_code)
  • 最早的想法起源于LISP,用于消除未调用的代码

Js的TreeShaking:

  • 对JS进行TreeShaking是源自打包工具rollup
  • 这是因为TreeShaking依赖于ES Module的静态语法分析
  • webpack2正式内置支持了ES2015模块,和检测未使用模块的能力
  • webpack4扩展了这个能力,并且通过package.json的sideEffects属性作为标记,告知webpack在编译时,哪些文件可以安全的删除
  • webpack5中,也提供了对部分CommonJs的treeshaking支持

    webpack实现Tree Shaking

事实上webpack实现Tree Shaking采用了两种不同的方案:

  • usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化的
  • sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用

    usedExports

将mode设置为development模式:

  • 为了可以看到usedExports带来的效果,我们需要设置为development模式
  • 因为在production模式下,webpack默认的一些优化会带来很大的影响

设置usedExports为true和false对比打包后的代码:

  • 在usedExports设置为true时,会有一段注释:unused harmony export mul;
  • 这段注释的意义是什么呢?告知Terser在优化时,可以删除这段代码

这个时候,我们将minimize设置为true:

  • usedExports设置为false时,mul函数没有被移除掉
  • usedExports设置为true时,mul函数被移除了

所以,usedExports实现TreeShaking是结合Terser来完成的

注意:使用usedExports对于引入了文件却没有使用文件内任何变量的情况,是无法做到不打包引入的文件的,想要实现这个效果,需要使用sideEffects

sideEffects

sideEffects用于告知webpack compiler哪些模块是有副作用的:

  • 副作用的意思是这里面的代码有执行一些特殊的任务,不能仅仅通过export来判断这段代码的意义

在package.json中设置sideEffects的值:

  • 如果我们将sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports
  • 如果有一些我们希望保留,可以设置为数组

比如我们有一个format.js、style.css文件:

  • 该文件在导入时没有使用任何的变量来接收
  • 那么打包后的文件,不会保留format.js、style.css相关的任何代码

如果是符合模块化的JS代码,那么这没有问题,比如format.js中我们访问window,给window添加了一个abc变量,这一步就会被删除掉,这是符合js的模块化开发的。

但是如果是css,就会导致样式失效,这时有两个解决方案

  1. package.json中添加变量sideEffects,值是一个数组,把style.css做为数组项

    1. "sideEffects": [
    2. "./src/style.css"
    3. ],

    如果是全部CSS文件都不被删除,就这样配置

    "sideEffects": [
    "**.css"
    ],
    
  2. webpack中配置css-loader

    {
    test: /\.css$/,
    use: [
    isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
    'css-loader',
    ],
    sideEffects: true,
    },
    

    也是添加sideEffects:true,就可以全局配置

    webpack中tree shaking的设置

  • 在optimization中配置usedExports为true,来帮助Terser进行优化
  • 在package.json中配置sideEffects,直接对模块进行优化

    CSS实现TreeShaking

上面我们通过webpack实现了对js代码的tree shaking,css同样可以进行tree shaking

  • CSS的TreeShaking需要借助于一些其他的插件
  • 在早期的时候,我们会使用PurifyCss插件来完成CSS的tree shaking,但是目前该库已经不在维护了
  • 目前我们可以使用另外一个库来完成CSS的TreeShaking:PurgeCSS,也是一个帮助我们删除未使用的CSS的工具

安装PurgeCss的webpack插件:

npm install purgecss-webpack-plugin -D

配置PurgeCss:

  • paths表示要检测那些目录下的内容,这里我们可以使用glob(glob是一个库,帮我们指定全局)
  • 默认情况下,PurgeCss会将我们的body、html标签的样式移除掉,如果我们需要保留,可以添加一个safelist属性
    • safelist属性是一个函数,返回一个对象,对象有一给standard属性,值是一个数组
      new PurgeCssPlugin({
      paths: glob.sync(`${resolveApp('./src')}/**/*`, { nodir: true }),
      safelist: function () {
      return {
       standard: ['body', 'html'],
      };
      },
      }),
      
      在现代的前端开发中,CSS tree shaking 没有太大意义,因为 CSS tree shaking 的原理就是分析 html 或其他代码中对 css 选择器的使用情况,将未被使用的 css 删除。但是因为现代开发时 css 选择器多为动态的,所以会有误删的情况,因此不被使用。

      HTTP压缩

Http压缩是一种内置在浏览器和客户端之间的,以改进传输速度和宽带利用率的方式

Http压缩的流程是什么呢?

  • 第一步:HTTP数据在服务器发送前就已经被压缩了;(可以在webpack中完成)
  • 第二步:兼容的浏览器在向服务器发送请求时,会告知服务器自己支持哪些压缩格式
  • 第三步:服务器在浏览器支持的压缩格式下,直接返回对应的压缩后的文件,并且在响应头中告知浏览器

    目前的压缩格式

目前的压缩格式非常多

  • compress:UNIX的 compress 程序的方法,不推荐使用,一般使用gzip或deflate
  • defalte:基于deflate算法的压缩,使用zlib数据格式封装
  • gzip:GNU zip 格式,是目前使用比较广泛的压缩算法
  • br:一种新的开源压缩算法,专为HTTP内容的编码而设计

    Webpack对文件压缩

webpack中相当于是实现了HTTP压缩的第一步操作,我们可以使用CompressionPlugin。

第一步,安装CompressionPlugin:

npm i compression-webpack-plugin -D

第二步,配置并使用

// 引入
const CompressionPlugin = require('compression-webpack-plugin');

plugins:{
    // ... 
    // 配置 compression
    new CompressionPlugin({
    threshold: 0,
    test: /\.(css|js)$/i,
    minRatio: 0.8,
    algorithm: 'gzip',
  }),
}

InlineCheckHtmlPlugin

这个插件可以将一些chunk出来的模块,内联到html中

  • 比如runtime的代码,代码量不大,但是必须要加载的
  • 那么我们就可以直接内联到html中

这个插件是在react-dev-utils中实现的,我们首先需要安装一下

npm install react-dev-utils -D

然后引入并配置

// 引入相关插件
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime.*\.js$/]),

webpack打包library

看着很唬人,其实就是我们自己写个包,然后给别人用,因为不知道用的那个人是在什么环境使用,所以我们需要给这个我们自己的包做一些配置

const path = require('path');

module.exports = {
  mode: 'production',
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, './build'),
    filename: 'jujuul_utils.js',
    libraryTarget: 'umd',
    library: 'jujuulUtils',
    globalObject: 'this',
  },
};

关键点就是 libraryTarget ,配置成 umd 就可以在任意环境使用