Webpack的性能优化

315 阅读19分钟

Webpack的性能优化

webpack作为前端目前使用最广泛的打包工具,在面试中也是经常会被问到的。

比较常见的面试题包括:

  • 可以配置哪些属性来进行webpack性能优化?
  • 前端有哪些常见的性能优化? (问到前端性能优化时, 除了其他常见的,也完全可以从webpack来回答)

webpack的性能优化较多,我们可以对其进行分类:

  • 优化一: 打包后的结果,上线时的性能优化。(比如分包处理、 减小包体积、CDN服务器等)
  • 优化二: 优化打包速度,开发或者构建时优化打包速度。(比如exclude、 cache-loader等)

大多数情况下,我们会更加侧重于优化一, 这对于线上的产品影响更大

在大多数情况下webpack都帮我们做好了该有的性能优化:

  • 比如配置mode为production或者development时,默认webpack的配置信息;
  • 但是我们也可以针对性的进行自己的项目优化;

image-20230112015900374

代码分离

代码分离(Code Splitting)是webpack一个非常重要的特性:

  • 它主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件;
  • 比如默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度;
  • 代码分离可以分出更小的bundle,以及控制资源加载优先级,提供代码的加载性能;

Webpack中常用的代码分离有三种:

  • 入口起点: 使用entry配置手动分离代码;
  • 防止重复: 使用Entry Dependencies或者SplitChunksPlugin去重和分离代码;
  • 动态导入: 通过模块的内联函数调用来分离代码;

分包处理的优势和必要性

image-20230112142234672

webpack多入口依赖

入口起点的含义非常简单,就是配置多入口:

  • 比如配置-个index.js和main.js的入口;
  • 他们分别有自己的代码逻辑;
  entry: {
    index: "./src/index.js",
    main: './src/main.js'
    },
    // 可以进行共享的
    shared: ["axios"]
        },
    output: {
    path: path.resolve(__dirname, "./build"),
    // [name]为占位符
    filename: "[name]-bundle.js",
    clean: true
  },

项目依赖

假如我们的index.js和main.js都依赖两个库: lodash、 dayjs

  • 如果我们单纯的进行入口分离,那么打包后的两个bunlde都有会有一-份lodash和dayjs; .
  • 事实上我们可以对他们进行共享;
  entry: {
    index: {
      import: "./src/index.js",
      dependOn: "shared"
    },
    main: {
      import: './src/main.js',
      dependOn: "shared"
    },
    // 可以进行共享的
    shared: ["axios"]
  },

webpack的动态导入(dynamic import)

另外一个代码拆分的方式是动态导入时,webpack提供了两种实现动态导入的方式:

  • 第一-种,使用ECMAScript中的import()语法来完成,也是目前推荐的方式;
  • 第二种,使用webpack遗留的require.ensure,目前已经不推荐使用;

比如我们有一个模块bar.js:

  • 该模块我们希望在代码运行过程中来加载它(比如判断一-个条件成立时加载) ;
  • 因为我们并不确定这个模块中的代码一定会用到,所以最好拆分成一个独立的js文件;
  • 这样可以保证不用到该内容时,浏览器不需要加载和处理该文件的js代码;
  • 这个时候我们就可以使用动态导入;
  • 这里实现了一个懒加载

image-20230112171817595

注意:使用动态导入barjs: .

  • 在webpack中,通过动态导入获取到一个对象;
  • 真正导出的内容,在该对象的default属性中,所以我们需要做一个简单的解构;
function about() {
  console.log("about function exec~");
}
const name = "默认导出值"export {
  about
}
​
export default name
btn1.onclick = function () {
  import("./router/about").then(res => {
    res.about()    
    res.default()
  })
} 

分包重命名

动态导入的文件命名:

  • 因为动态导入通常是一定会打包成独立的文件的,所以并不会在cacheGroups中进行配置;
  • 那么它的命名我们通常会在output中,通过chunkFilename属性来命名;
  output: {
    clean: true,
    path: path.resolve(__dirname, "./build"),
    // [name]为占位符
    filename: "[name]-bundle.js",
    // 单独针对分包的文件进行命名
    chunkFilename: "[name]_chunk.js"
  },

但是,你会发现默认情况下我们获取到的[name]是和id的名称保持-致的

  • 如果我们希望修改name的值,可以通过magic comments (魔法注释)的方式;
btn1.onclick = function () {
  import(/* webpackChunkName:"about" */"./router/about").then(res => {
    res.about()
    res.default()
  })
}
btn2.onclick = function () {
  import(/* webpackChunkName:"category" */"./router/category")
}

SplitChunkPlugin

另外一种分包的模式是splitChunk, 它底层是使用SplitChunksPlugin来实现的:

  • 因为该插件webpack已经默认安装和集成,所以我们并不需要单独安装和直接使用该插件;
  • 只需要提供SplitChunksPlugin相关的配置信息即可;
  // 优化配置
  optimization: {
    splitChunks: {
      // 所有导入的第三包都会分开
      chunks: "all"
    }
  },

自定义配置解析

Chunks:

  • 默认值是async
  • all表示对同步和异步代码都进行处理

minSize:

  • 拆分包的大小,少为minSize;
  • 如果一个包拆分出来达不到minSize,那么这个包就不会拆分;

maxSize:

  • 将大于maxSize的包,拆分为不小于minSize的包;

cacheGroups:

  • 用于对拆分的包就行分组,比如一个lodash在拆分之后,并不会立即打包,而是会等到有没有其他符合规则的包一起来打包;
  • test属性:匹配符合规则的包;
  • name属性:拆分包的name属性;
  • filename属性:拆分包的名称,可以自己使用placeholder属性;
  // 优化配置
  optimization: {
    splitChunks: {
      chunks: "all",
      // 当一个包大于指定值时,继续进行拆包
      maxSize: 20000,
      // 将包拆分成不小于minSize的包
      minSize: 10000,
​
      // 自己对需要拆包的内容进行分包
      cacheGroups: {
        vendors: {
          // 在window上 /和\都有可能是斜杠 mac上只有/
          test: /[\/]node_modules[\/]/, //完整写法
          filename: "[id]_vendors.js"
        },
        utils: {
          test: /utils/,
          filename: "[id]_utils.js"
        }
      }
    }
  },

optimization.chunklds配置

optimization.chunklds配置用于告知webpack模块的id采用什么算法生成。 有三个比较常见的值:

  • natural:按照数字的顺序使用id;

  • named: development 下的默认值,一个可读的名称的id;

  • deterministic:确定性的,在不同的编译中不变的短数字id (有利于浏览器缓存)

    • 在webpack4中是没有这个值的;
    • 那个时候如果使用natural,那么在一些编译发生变化时, 就会有问题;

最佳实践:

  • 开发过程中,我们推荐使用named;
  • 打包过程中,我们推荐使用deterministic;
// 优化配置
  optimization: {
    // 设置生成的chunkId的算法
    // development默认:named
    //  production:deterministic(确定性的数字) 
    // webpack4中使用:natural 自然数递增
    chunkIds: "deterministic",
​
    // 分包插件:splitChunksPlugin
    splitChunks: {
      chunks: "all",
      // 当一个包大于指定值时,继续进行拆包
      // maxSize: 20000,
      // 将包拆分成不小于minSize的包
      // minSize: 10000,
      minSize: 10,
      // 自己对需要拆包的内容进行分包
      cacheGroups: {
        vendors: {
          // 在window上 /和\都有可能是斜杠 mac上只有/
          test: /[\/]node_modules[\/]/, //完整写法
          filename: "[id]_vendors.js"
        },
        utils: {
          test: /utils/,
          filename: "[id]_utils.js"
        }
      }
    },
    // 代码优化:TerserPlugin => 让代码更简单  => Terser
    minimize:true,
    minimizer: [
      // JS代码简化 去掉注释
      new TerserPlugin({
        extractComments: false
      })
    ]
  },

prefetch和preload

webpack v4.6.0+增加了对预获取和预加载的支持。

在声明import时,使用下面这些内置指令,来告知浏览器:

  • prefetch(预获取):将来某些导航下可能需要的资源
  • preload(预加载):当前导航下可能需要资源

与prefetch指令相比,preload 指令有许多不同之处:

  • preload chunk会在父chunk加载时,以并行方式开始加载。prefetch chunk会在父chunk加载结束后开始加载。
  • preload chunk具有中等优先级,并立即下载。prefetch chunk在浏览器闲置时下载。
  • preload chunk会在父chunk中立即请求,用于当下时刻。prefetch chunk会用于未来的某个时刻。

使用魔法注释 /* webpackPrefetch:true */ 就可以实现分包预加载

btn1.onclick = function () {
  import(
    /* webpackChunkName:"about" */
    /* webpackPrefetch:true */
    "./router/about").then(res => {
      res.about()
      res.default()
    })
}
btn2.onclick = function () {
  import(
    /* webpackChunkName:"category" */
    /* webpackPrefetch:true */
    "./router/category")
}

image-20230113150112037

CDN加速服务器配置

CDN称之为内容分发网络(Content Delivery Network或Content Distribution Network,缩写: CDN)

  • 它是指通过相互连接的网络系统,利用最靠近每个用户的服务器;
  • 更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户;
  • 来提供高性能、可扩展性及低成本的网络内容传递给用户;

image-20230113150153669

在开发中,我们使用CDN主要是两种方式:

  • 方式一:打包的所有静态资源,放到CDN服务器

    用户所有资源都是通过CDN服务器加载的;

  • 方式二:一些第三方资源放到CDN服务器上;

所有的静态资源都想要放到CDN服务器上

如果所有的静态资源都想要放到CDN服务器上,我们需要购买自己的CDN服务器;

  • 目前阿里、腾讯、亚马逊、Google等都可以购买CDN服务器;
  • 我们可以直接修改publicPath,在打包时添加上自己的CDN地址;
  output: {
    clean: true,
    path: path.resolve(__dirname, "./build"),
    // [name]为占位符
    filename: "[name]-bundle.js",
    // 单独针对分包的文件进行命名
    chunkFilename: "[name]_chunk.js",
    // CND服务器地址
    publicPath: "http://coderwhycnd.com/"
  },

此时Html加载的包都是从CDN处加载

    <script defer src="http://coderwhycnd.com/246_vendors.js"></script>
    <script defer src="http://coderwhycnd.com/497_utils.js"></script>
    <script defer src="http://coderwhycnd.com/main-bundle.js"></script>

第三方库的CND

通常一些比较出名的开源框架都会将打包后的源码放到一-些比较出名的、免费的CDN服务器上:

  • 国际上使用比较多的是unpkg、JSDelivr、 cdnjs; .
  • 国内也有一个比较好用的CDN是bootcdn;

在项目中,我们如何去引入这些CDN呢?

  • 第一, 在打包的时候我们不再需要对类似于lodash或者dayjs这些库进行打包;
  • 第二,在html模块中,我们需要自己加入对应的CDN服务器地址;

第一步,我们可以通过webpack配置,来排除一些库的打包:

// 排除某些包不需要进行打包
  externals: {
    react: "React",
    // key属性名:排除的框架名称 
    // value值:从CDN地址请求下来的js中提供对应的名称
    axios: "axios"
  },

第二步,在html模块中,加入CDN服务器地址:

  <body>
    <div id="root"></div>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.2/axios.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
  </body> 

认识shimming

shimming是一个概念,是某一类功能的统称:

  • shimming翻译过来我们称之为垫片,相当于给我们的代码填充一-些垫片来处理一-些问题;
  • 比如我们现在依赖一个第三方的库,这个第三方的库本身依赖lodash,但是默认没有对lodash进行导入(认为全局存在lodash),那么我们就可以通过ProvidePlugin来实现shimming的效果;

注意: webpack并不推荐随意的使用shimming

  • Webpack背后的整个理念是使前端开发更加模块化;
  • 也就是说,需要编写具有封闭性的、不存在隐含依赖(比如全局变量)的彼此隔离的模块;

假如我们的lodash、dayjs都使用了CDN进行引入,所以相当于在全局是可以使用和dayjs的

  • 假如一个文件中我们使用了axios,但是没有对它进行引入,那么下面的代码是会报错的;
// import axios from "axios";// import dayjs from "dayjs"
​
axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
  console.log(res);
})
​
console.log(dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss"));
​
/*
如果abc.js是一个第三方库,但是他没有引用axios,但是却使用了axios的方法,
此时如果本地没有引用axios这个第三方库就会报错,所以需要shimming
*/

我们可以通过使用ProvidePlugin来实现shimming的效果:

  • ProvidePlugin能够帮助我们在每个模块中,通过一个变量来获取一-个package;
  • 如果webpack看到这个模块,它将在最终的bundle中引入这个模块;
  • 另外ProvidePlugin是webpack默认的一个插件,所以不需要专门导入;

这段代码的本质是告诉webpack:

如果你遇到了至少一处用到axios变量的模块实例,那请你将axios package引入进来,并将其提供给需要用到它的模块。

const { ProvidePlugin } = require("webpack") 
    plugins: [
    new ProvidePlugin({
      axios: ["axios", "default"],
      dayjs: "dayjs"
    })
  ]

CSS样式的单独提取

MiniCssExtractPlugin可以帮助我们将css提取到一个独立的css文件中,该插件需要在webpack4+才可以使用。

首先,我们需要安装mini-css-extract-plugin:

npm install mini-css-extract-plugin -D

配置rules和plugins:

    module: {
    rules: [
      {
        test: /.css$/,
        use: [
          // "style-loader",  开发阶段 直接插入到html文件
          MiniCssExtractPlugin.loader,  // 生产阶段 分离单独的css文件
          'css-loader'
        ]
      }
    ]
  },  
    plugins: [
    // 完成css的提取
    new MiniCssExtractPlugin({
      // 直接导入重命名
      filename: 'css/[name].css',
      // 动态导入重命名
      chunkFilename: 'css/[name]_chunk.css'
​
    })
  ]

打包重命名的Hash生成

在我们给打包的文件进行命名的时候,会使用placeholder, placeholder中有几个属性比较相似:

  • hash、 chunkhash、 contenthash
  • hash本身是通过MD4的散列函数处理后,生成一个128位的hash值(32个十六进制) ;

hash值的生成和整个项目有关系:

  • 比如我们现在有两个入口index.js和mainjs;
  • 它们分别会输出到不同的bundle文件中,粗在文件名称中我们有使用hash;
  • 这个时候,如果修改了index.js文件中的内容,那么hash会发生变化;
  • 那就意味着两个文件的名称都会发生变化;

chunkhash可以有效的解决上面的问题,它会根据不同的入口进行解析来生成hash值:

  • 比如我们修改了index:js,那么main.js的chunkhash是不会发生改变的;

contenthash表示生成的文件hash名称,只和内容有关系:

  • 比如我们的index.js,引入了一个style.css, style.css有 被抽取到一个独立的css文件中; .
  • 这个css文件在命名时,如果我们使用的是chunkhash;
  • 那么当index.js文件的内容发生变化时, css文件的命名也会发生变化;
  • 这个时候我们可以使用contenthash;
const path = require("path")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
​
module.exports = {
  mode: "development",
  entry: {
    main: "./src/main.js",
    index: "./src/index.js"
  },
  output: {
    clean: true,
    path: path.resolve(__dirname, './build'),
    filename: "[name]_[contenthash]_bundle.js",
    chunkFilename: "[contenthash]_chunk.js"
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[contenthash]_[name].css"
    })
  ]
}

TerserPlugin代码压缩

什么是Terser呢?

  • Terser是一个JavaScript的解释(Parser) 、Mangler (绞肉机) /Compressor (压缩机)的工具集;
  • 早期我们会使用uglify-js来压缩、丑化我们的JavaScript代码,但是目前已经不再维护,并且不支持ES6+的语法;
  • Terser是从uglify-es fork过来的,并且保留它原来的大部分API以及适配uglify-es和uglify-js@3等;

也就是说,Terser可以帮助我们压缩、丑化我们的代码,让我们的bundle变得更小。

因为Terser是一个独立的工具,所以它可以单独安装:

#全局安装

npm install terser -g

#局部安装

npm install terser -D

命令行使用

我们可以在命令行中使用Terser:

terser [input files] [options]

#举例说明

terser js/file1.js -o foo.min.js -C -m

我们这里来讲解几个Compress option和Mangle(乱砍) option:

Compress option:

  • arrows: class或者object中的函数, 转换成箭头函数;
  • arguments:将函数中使用arguments[index]转成对应的形参名称;
  • dead_ code:移除不可达的代码(tree shaking) ;
  • 他属性可以查看文档;

Mangle option

  • toplevel:默认值是false,顶层作用域中的变量名称,进行丑化(转换) ;
  • keep_classnames: 默认值是false,是否保持依赖的类名称;
  • keep_fnames:默认值是false,是否保持原来的函数名称;
  • 其他属性可以查看文档;
npx terser abx.js -o abx.min.js -c arrows=true arguments=true,dead_code=true -m toplevel=true,keep_fnames=true

image-20230113230309484

Terser在webpack中配置

真实开发中,我们不需要手动的通过terser来处理我们的代码,我们可以直接通过webpack来处理:

  • 在webpack中有一 个minimizer属性 ,在production模式下, 默认就是使用TerserPlugin来处理我们的代码的;
  • 如果我们对默认的配置不满意,也可以自己来创建TerserPlugin的实例,組覆盖相关的配置;

首先,我们需要打开minimize,让其对我们的代码进行压缩(默认production模式 下已经打开了)

其次,我们可以在minimizer创建-个TerserPlugin:

  • extractComments: 默认值为true, 表示会将注释抽取到一个单独的文件中; .

    • 在开发中,我们不希望保留这个注释时,可以设置为false
  • parallel: 使多进程并发运行提高构建的速度,默认值是true

    • 并发运行的默认数量: os.cpus().length - 1;
    • 我们也可以设置自己的个数,但是使用默认值即可;
  • terserOptions: 设置我们的terser相关的配置

    • compress: 设置压缩相关的选项;
    • mangle: 设置丑化相关的选项,可以直接设置为true;
    • toplevel: 顶层变量是否进行转换;
    • keep_ classnames: 保留类的名称;
    • keep_ fnames:保留函数的名称;
  // 优化配置
  optimization: {
        minimize: true,
    // 代码优化:TerserPlugin => 让代码更简单  => Terser
    minimizer: [
      // JS压缩插件 TerserPlugin
      new TerserPlugin({
        extractComments: false,
        terserOptions: {
          compress: {
            // 改变arguments参数名称
            arguments: true,
            // 启用 没有使用过的代码
            unused: false
          },
          // 丑化代码
          mangle: true,
          // 顶层作用域中的变量名称
          // toplevel: false
          // 保持原来的函数名称
          keep_fnames: true
        }
      })
      // CSS压缩插件 CSSMinimizerPlugin
    ]
  }, 

CSS压缩

另一个代码的压缩是CSS:

  • CSS压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等;
  • CSS的压缩我们可以使用另外一个插件: css-minimizer-webpack plugin;
  • css-minimizer-webpack- plugin是使用cssnano工具来优化、压缩CSS (也可以单独使用) ;

第一步,安装css-minimizer-webpack-plugin:

npm install css-minimizer-webpack-plugin 

第二步,在optimization.minimizer中配置

minimizer: [
      // JS压缩插件 TerserPlugin
      new TerserPlugin({
        extractComments: false,
        terserOptions: {
          compress: {
            // 改变arguments参数名称
            arguments: true,
            // 启用 没有使用过的代码
            unused: false
          },
          // 丑化代码
          mangle: true,
          // 顶层作用域中的变量名称
          // toplevel: false
          // 保持原来的函数名称
          keep_fnames: true
        }
      }),
      // CSS压缩插件 CSSMinimizerPlugin
      new CssMinimizerPlugin({
        // parallel: true,  
      })
    ]

Tree Shaking的实现

将mode设置为development模式:

  • 为了可以看到usedExports带来的效果,我们需要设置为development模式
  • 因为在production模式下,webpack默认的一些优化会带来很大的影响。
  optimization: {
    // 导入模块时,分析模块中那些函数有被使用,那些函数没有被使用
    usedExports: true,
  }

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

  • 在usedExports设置为true时,会有一段注释: unused harmony export mul;
  • 这段注释的意义是什么呢?告知Terser在优化时,可以删除掉这段代码;
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "sum": function() { return /* binding */ sum; }
/* harmony export */ });
/* unused harmony export mul */
function sum(num1, num2) {
  return num1 + num2;
}
function mul(num1, num2) {
  return num1 + num2;
}

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

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

Tree Shaking要注意的sideEffects(副作用)

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

  • 副作用的意思是这里面的代码有执行一些特殊的任务 ,不能仅仅通过export来判断这段代码的意义;
  • 副作用的问题,在讲React的纯函数时是有讲过的;
// 副作用代码
// 推荐:在平时写模块时,尽量编写纯模块
window.lyric = "哈哈哈哈哈哈哈"

在package.json中设置sideEffects的值:

  • 如果我们将sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports;
  • 如果有一些我们希望保留,可以设置为数组;
  "sideEffects": [
    "./src/demo/parse-lyric.js"
  ],

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

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

所以,如何在项目中对JavaScript的代码进行TreeShaking呢(生成环境) ?

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

CSS的TreeShaking

上面我们学习的都是关于JavaScript的Tree Shaking,那么CSS是否也可以进行Tree Shaking操作呢?

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

配置这个插件(生成环境) :

  • paths: 表示要检测哪些目录下的内容需要被分析,这里我们可以使用glob;
  • 默认情况下,Purgecss会将我们的html标签的样式移除掉,如果我们希望保留,可以添加一个safelist的属性;

image-20230114204218918

purgecss也可以对less文件进行处理(所以它是对打包后的css进行tree shaking操作) ;

Scope Hoisting作用

什么是Scope Hoisting呢?

  • Scope Hoisting从webpack3开始增加的一个新功能;
  • 功能是对作用域进行提升,并且让webpack打包后的代码更小、运行更快;

默认情况下webpack打包会有很多的函数作用域,包括一些(比如最外层的) IIFE:

  • 无论是从最开始的代码运行,还是加载一个模块, 都需要执行一系列的函数;
  • Scope Hoisting可以将函数合并到一一个模块中来运行;

使用Scope Hoisting非常的简单, webpack已经内置了对应的模块:

  • 在production模式下,默认这个模块就会启用;
  • 在development模式下,我们需要自己来打开该模块;
  // webpack插件
  plugins: [
    // 作用域提升
    new webpack.optimize.ModuleConcatenationPlugin()
  ]

HTTP文件压缩传输

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

HTTP压缩的流程什么呢?

  • 第一步: HTTP数据在服务器发送前就已经被压缩了; (可以在webpack中完成)

  • 第二步:兼容的浏览器在向服务器发送请求时,会告知服务器自己支持哪些压缩格式;

    image-20230114210421623

  • 第三步:服务器在浏览器支持的压缩格式下,直接返回对应的压缩后的文件,并且在响应头中告知浏览器;

    image-20230114210440932

目前的压缩格式非常的多:

  • compress - UNIX的"compress" 程序的方法(历史性原因,不推荐大多数应用使用,应该使用gzip或deflate) ;
  • deflate - 基于deflate算法(定义于RFC 1951)的压缩,使用zlib数据格式封装;
  • gzip - GNU zip格式(定义于RFC 1952),是目前使用比较广泛的压缩算法;
  • br - 一种新的开源压缩算法,专为HTTP内容的编码而设计;

webpack对文件的压缩

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

  • 第一步,安装CompressionPlugin:

    npm install compression-webpack-plugin -D
    
  • 第二步,使用CompressionPlugin即可:

image-20230114211424049

  plugins: [
    // HTTP压缩
    new CompressionPlugin({
      test: /.(css|js)/,
      minRatio: 0.7,
      algorithm: "gzip"
    })
  ]

HTML文件的压缩

我们之前使用了HtmlWebpackPlugin插件来生成HTML的模板,事实上它还有一些其他的配置:

inject:设置打包的资源插入的位置

  • true、false 、body、head

cache:设置为true,只有当文件改变时,才会生成新的文件(默认值也是true)

minify: 默认会使用一个插件html-minifier-terser

plugins: [
      new HtmlWebpackPlugin({
        template: "./index.html",
        // 缓存,如果有更新才生成新html
        cache: true,
        minify: isProduction ? {
          // 移除注释
          removeComments: true,
          // 移除属性
          removeEmptyAttributes: true,
          // 移除默认属性
          removeRedundantAttributes: true,
          // 折叠空白字符
          collapseWhitespace: true,
          // 压缩内联css
          minifyCSS: true,
          // 压缩JavaScript
          minifyJS: {
            mangle: {
              toplevel: true
            }
          }
        } : false
      })
    ]

webpack打包分析

对打包时间分析

如果我们希望看到每一个loader. 每一个Plugin消耗的打包时间, 可以借助于一个插件: speed-measure-webpack-plugin

第一步,安装speed-measure-webpack plugin插件

npm install speed-measure-webpack-plugin -D

第二步,使用speed-measure-webpack-plugin插件

  • 创建插件导出的对象SpeedMeasurePlugin;
  • 使用smp.wrap包裹我们导出的webpack配置;
    const smp = new SpeedMeasurePlugin()
​
​
  const finalConfig = merge(mergeConfig, getCommonConfig(isProduction))
  return smp.wrap(finalConfig)

对打包文件分析

方案一:生成一个stats.json的文件

    "build": "webpack --config ./config/comm.config.js --env production --profile --json=stats.json",

通过执行npm run build:status可以获取到一 个stats.json的文件:

方案二:使用webpack bundle-analyzer工具

  • 另一个非常直观查看包大小的工具是webpack- bundle-analyzer

第一步:安装

pnpm add webpack-bundle-analyzer -D

第二步:我们可以在webpack配置中使用该插件:

  plugins: [
    // 对打包结果分析
    new BundleAnalyzerPlugin()
  ]

在打包webpack的时候,这个工具是帮助我们打开- -个8888端口上的服务,我们可以直接的看到每个包的大小。

  • 比如有一个包时通过一个Vue组件打包的,但是非常的大,那么我们可以考虑是否可以拆分出多个组件,并且对其进行懒加载;
  • 比如一-个图片或者字体文件特别大,是否可以对其进行压缩或者其他的优化处理;

image-20230114232404039