webpack plugin源码解析(六) CompressionWebpackPlugin

news2024/9/20 14:35:21

文章目录

  • 作用
  • 涉及 webpack API
    • 处理 asset 钩子compilation.hooks.processAssets
    • 返回或新建缓存:compilation.getCache
    • 返回 asset 文件信息:compilation.getAsset
    • 文件名匹配函数:compiler.webpack.ModuleFilenameHelpers.matchObject
    • 模版字符串替换:compilation.getPath
  • 实现
    • constructor
    • apply
    • 生成输出压缩文件

作用

  • 压缩打包后的文件,可以配置是否删除源文件
const CompressionPlugin = require("compression-webpack-plugin");

new CompressionPlugin()

涉及 webpack API

  • 处理 asset 钩子compilation.hooks.processAssets

    • PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER:优化已有 asset 的转换操作阶段,例如对 asset 进行压缩,并作为独立的 asset
    • additionalAssets: true 会多次调用回调,一次是在指定 stage 添加资产时触发回调,另一次是后来由插件添加资产时,这里为 CompressionWebpackPlugin 添加的压缩文件后触发
compiler.hooks.thisCompilation.tap(pluginName, compilation => {
  compilation.hooks.processAssets.tapPromise({
    name: pluginName,
    // 优化已有 asset 的转换操作,例如对 asset 进行压缩,并作为独立的 asset
    stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER, 
    additionalAssets: true // true会多次调用回调,一次是在指定 stage 添加资产时触发回调,另一次是后来由插件添加资产时
  }, assets => 
    this.compress(compiler, compilation, assets));
});
  • 返回或新建缓存:compilation.getCache

    • 具体查看 copy-webpack-plugin 解析文章
  • 返回 asset 文件信息:compilation.getAsset

   const {
     info,
     source
   } =compilation.getAsset(name); // name:"main.js" 打包后输出文件的 name
  • 文件名匹配函数:compiler.webpack.ModuleFilenameHelpers.matchObject

    • 具体查看 copy-webpack-plugin 解析文章
  • 模版字符串替换:compilation.getPath

    • 具体查看 copy-webpack-plugin 解析文章

实现

constructor

  • 初始化选项和压缩配置,以及默认使用 zlib 库进行压缩
class CompressionPlugin {
  constructor(options) {
    validate(
    /** @type {Schema} */
    schema, options || {}, {
      name: "Compression Plugin",
      baseDataPath: "options"
    });
    const {
      test,
      include,
      exclude,
      algorithm = "gzip",
      compressionOptions ={},
      filename = (options || {}).algorithm === "brotliCompress" ? "[path][base].br" : "[path][base].gz",
      threshold = 0,
      minRatio = 0.8,
      deleteOriginalAssets = false
    } = options || {};

    this.options = {
      test,
      include,
      exclude,
      algorithm,
      compressionOptions,
      filename,
      threshold,
      minRatio,
      deleteOriginalAssets
    };
    /**
    {
	  test: undefined,
	  include: undefined,
	  exclude: undefined,
	  algorithm: "gzip",
	  compressionOptions: {
	    level: 9,
	  },
	  filename: "[path][base].gz",
	  threshold: 0,
	  minRatio: 0.8,
	  deleteOriginalAssets: false,
	}
	*/

    this.algorithm = this.options.algorithm;

    if (typeof this.algorithm === "string") {

      const zlib = require("zlib");  // 默认使用 zlib 压缩


      this.algorithm = zlib[this.algorithm];

      if (!this.algorithm) {
        throw new Error(`Algorithm "${this.options.algorithm}" is not found in "zlib"`);
      }

      const defaultCompressionOptions = {
        gzip: {
          level: zlib.constants.Z_BEST_COMPRESSION // 9
        },
        deflate: {
          level: zlib.constants.Z_BEST_COMPRESSION
        },
        deflateRaw: {
          level: zlib.constants.Z_BEST_COMPRESSION
        },
        brotliCompress: {
          params: {
            [zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY
          }
        }
      }[algorithm] || {};
      
      this.options.compressionOptions ={ // 传递给 zlib 的压缩参数
        ...defaultCompressionOptions,
        ...this.options.compressionOptions
      };
    }
  }
}

apply

  • 通过 processAssets 钩子的 PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER 阶段进行 assets 压缩
 apply(compiler) {
 
   const pluginName = this.constructor.name;
   
   compiler.hooks.thisCompilation.tap(pluginName, compilation => {
     compilation.hooks.processAssets.tapPromise({
       name: pluginName,
       // 优化已有 asset 的转换操作,例如对 asset 进行压缩,并作为独立的 asset
       stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER, 
       additionalAssets: true // true会多次调用回调,一次是在指定 stage 添加资产时触发回调,另一次是后来由插件添加资产时,这里为 CompressionWebpackPlugin 添加的压缩文件后触发
     }, assets => 
       this.compress(compiler, compilation, assets));

     compilation.hooks.statsPrinter.tap(pluginName, stats => {
       stats.hooks.print.for("asset.info.compressed").tap("compression-webpack-plugin", (compressed, {
         green,
         formatFlag
       }) => compressed ?
         green(formatFlag("compressed")) : "");
     });
   });
 }

compress

  • 遍历源 asset 进行压缩,会通过缓存已压缩文件来优化性能

asset 数据结构
在这里插入图片描述

async compress(compiler, compilation, assets) {

	const cache = compilation.getCache("CompressionWebpackPlugin");
	// 遍历文件
	const assetsForMinify = (await Promise.all(Object.keys(assets).map(async name => {
	  // 获取文件信息
	  const {
        info,
        source
      } =compilation.getAsset(name);
	})
	
	if (info.compressed) { // 当插件第一次添加压缩文件后,因为 additionalAssets:true 会第二次触发插件回调,如果第一次被压缩了 info.compressed 为 true
	  return false;
	}
	
    // 通过开发者传递的 test、exclude、include 匹配文件
    if (!compiler.webpack.ModuleFilenameHelpers.matchObject.bind(undefined, this.options)(name)) {
      return false;
    }
    
    // 获取压缩相关 name
    let relatedName; // "gzipped"

    if (typeof this.options.algorithm === "function") {
      if (typeof this.options.filename === "function") {
        relatedName = `compression-function-${crypto.createHash("md5").update(serialize(this.options.filename)).digest("hex")}`;
      } else {
        /**
         * @type {string}
         */
        let filenameForRelatedName = this.options.filename;
        const index = filenameForRelatedName.indexOf("?");

        if (index >= 0) {
          filenameForRelatedName = filenameForRelatedName.slice(0, index);
        }

        relatedName = `${path.extname(filenameForRelatedName).slice(1)}ed`;
      }
    } else if (this.options.algorithm === "gzip") {
      relatedName = "gzipped";
    } else {
      relatedName = `${this.options.algorithm}ed`;
    }

    if (info.related && info.related[relatedName]) {
      return false;
    }
	
	// 缓存文件相关
	const cacheItem = cache.getItemCache(serialize({ // 第一个参数key:序列化成字符串,通过 serialize-javascript 库序列化成字符串
	  name,
	  algorithm: this.options.algorithm,
	  compressionOptions: this.options.compressionOptions
	}), cache.getLazyHashedEtag(source)); // 第二个参数 etag: 根据资源文件内容生成 hash
	// 返回缓存内容
	const output = (await cacheItem.getPromise()) || {};
	
	// 返回文件 buffer
	let buffer; // No need original buffer for cached files
	
	if (!output.source) {
	 if (typeof source.buffer === "function") {
	   buffer = source.buffer();
	 } // Compatibility with webpack plugins which don't use `webpack-sources`
	 // See https://github.com/webpack-contrib/compression-webpack-plugin/issues/236
	 else {
	   buffer = source.source();
	
	   if (!Buffer.isBuffer(buffer)) {
	     // eslint-disable-next-line no-param-reassign
	     buffer = Buffer.from(buffer);
	   }
	 }
	
	 if (buffer.length < this.options.threshold) { // 小于开发者传入的要压缩的阈值退出
	   return false;
	 }
	}
	
	return {
	 name,
	 source,
	 info,
	 buffer,
	 output,
	 cacheItem,
	 relatedName
	};
  }))).filter(assetForMinify => Boolean(assetForMinify));
  
  // webpack 格式文件,用于生成输出文件 
  const {
    RawSource
  } = compiler.webpack.sources;
  const scheduledTasks = [];
  
  // 压缩操作
  for (const asset of assetsForMinify) {
	  scheduledTasks.push((async () => {
	  	// ...
	  })
  }
  
  await Promise.all(scheduledTasks);
}

生成输出压缩文件

  // 压缩操作
  for (const asset of assetsForMinify) {
	  scheduledTasks.push((async () => {
        const {
          name,
          source,
          buffer,
          output,
          cacheItem,
          info,
          relatedName
        } = asset;
        
        // 优先将压缩相关内容存入缓存
        if (!output.source) {
          if (!output.compressed) {
            try {
              // 文件内容压缩
              output.compressed = await this.runCompressionAlgorithm(buffer);
            } catch (error) {
              compilation.errors.push(error);
              return;
            }
          }
		  // 压缩效果相关阈值,> 开发者传入的值跳过
          if (output.compressed.length / buffer.length > this.options.minRatio) {
            await cacheItem.storePromise({
              compressed: output.compressed
            });
            return;
          }
		  // 根据压缩后的内容生成文件
          output.source = new RawSource(output.compressed);
          await cacheItem.storePromise(output); // 存入 source、compressed
        }
		
		// this.options.filename:"[path][base].gz" , filename:"main.css"
		// newFilename:'main.css.gz'
		const newFilename = compilation.getPath(this.options.filename, {
          filename: name // name:"main.css"
        });
        const newInfo = {
          compressed: true
        };
		
		// 是否删除源文件,通过 compilation.updateAsset 更新源文件信息
		if (this.options.deleteOriginalAssets) {
          if (this.options.deleteOriginalAssets === "keep-source-map") {
            compilation.updateAsset(name, source, {
              // @ts-ignore
              related: {
                sourceMap: null
              }
            });
          }

          compilation.deleteAsset(name);
        } else {
          compilation.updateAsset(name, source, {
            related: {
              [relatedName]: newFilename
            }
          });
        }
		// 生成压缩文件
		compilation.emitAsset(newFilename, output.source, newInfo);
	  })
  }

runCompressionAlgorithm

  • 通过 zlib 进行压缩
const zlib = require("zlib");
this.algorithm = zlib['gzip'];

 runCompressionAlgorithm(input) {
   return new Promise((resolve, reject) => {
     this.algorithm(input, this.options.compressionOptions, (error, result) => {
       if (error) {
         reject(error);
         return;
       }

       if (!Buffer.isBuffer(result)) {
         resolve(Buffer.from(result));
       } else {
         resolve(result);
       }
     });
   });
 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/448930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

科研热点|8本期刊被剔除SCIE,4月最新SCIE/SSCI目录已更新 (附下载)~

2023年4月18日&#xff0c;科睿唯安更新了Web of Science核心期刊目录&#xff0c;此次更新后SCIE期刊目录共包含9505本期刊&#xff0c;SSCI期刊目录共包含3557本期刊。此次4月SCIE & SSCI期刊目录更新&#xff0c;与3月更新相比 (警惕&#xff01;多达50本SCI/SSCI被剔除…

Kafka中时间轮分析与Java实现

仿kafka实现java版时间轮_java实现时间轮算法_Hekliu的博客-CSDN博客 https://www.cnblogs.com/softlin/p/7426083.html https://blog.csdn.net/happyjacob/article/details/128518700 一、背景 在Kafka中应用了大量的延迟操作但在Kafka中 并没用使用JDK自带的Timer或是Dela…

m3u8转mp4下载,有URL,IV

1、背景 在线m3u8现在是主流加密方式的视频。 2、下载m3u8视频难点 首先需要连接m3u8文件格式,这个自行百度,其次加密方式确定和key以及iv。如果没有加密直接找一个在线的m3u8转mp4就可以,但是问题就是很多带加密,而且key不是m3m8中key URL返回的数据,市面上软件无法直…

基于matlab评估机场监控雷达上 5G 新无线电 (NR) 信号的干扰

一、前言 随着5G NR系统的频率范围超出LTE中使用的频段&#xff0c;频谱管理变得更加复杂。对扩大5G覆盖范围的需求是由更高的数据速率和更低的延迟的好处推动的。新5G基站的实施反过来又推动了了解这些信号如何影响在相同频段上运行的已安装系统的需求。其中一个系统是空中交通…

类对象

一、类初识 类&#xff1a;表示一种事物所具有的共同特征和行为 对象&#xff1a;一个类的实例 如下图&#xff0c;通过狗这个类进行详解 这是一个Dog类 对象&#xff1a;斗牛犬、小猎犬、牧羊犬 类中的属性&#xff1a;breed(品种)、size(大小)、color(颜色)、age(年龄)、 …

OpenCv基础之绘图及几何变换实例

文章目录 OpenCv基础之绘图及几何变换实例创建背景图线段绘制矩形绘制圆绘制椭圆绘制绘制多边形添加文字 几何变换图像平移图像缩放图像旋转仿射变换透视变化 OpenCv基础之绘图及几何变换实例 绘图在图像处理中&#xff0c;主要是在处理完图像后&#xff0c;将图像中的目标进行…

Python算法设计 - 哈夫曼编码

目录 一、哈夫曼树二、哈夫曼编码三、Python算法实现四、作者Info 一、哈夫曼树 上图是根据“this is an example of a huffman tree”中得到的字母频率来建构的哈夫曼树 二、哈夫曼编码 多年来&#xff0c;哈夫曼编码在统计数据压缩方面是非常先进的&#xff0c;应当指出&am…

C# 类库打包推送到nuget

步骤1&#xff1a;注册nuget 账号&#xff0c;可以使用outlook邮箱进行注册 步骤2&#xff1a;建立 apikey 名字自己起&#xff0c;Glob Pattern 填入“*” 步骤3&#xff1a;把程序打包&#xff0c;打包很简单右键vs2022 打包就好 但是注意*.csproj 文件修改,修改目的是为了…

IGS 产品长文件命名方式简介

文章目录 Part.I IntroductionPart.II 文件命名方式Chap.I 官方说明Chap.II 实例 Reference Part.I Introduction 2022 年 11 月 30 日&#xff08;DOY 331, GPSWD 22380&#xff09;及以后&#xff0c;IGS 的参考框架从 IGS-14 切换为 用 IGS-20&#xff0c;最新的卫星和地…

vue3中<script setup> 和 setup函数的区别

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的 <script> 语法&#xff0c;它具有更多优势&#xff1a; 更少的样板内容&#xff0c;更简洁的代码。能够使用纯 TypeScript…

拿下模板进阶

模板进阶 1. 非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 非类型形参&#xff0c;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中可将…

代码随想录训练营day53|1143、最长公共子序列;1035、不相交的线;53、最大子序和动态规划

1143、最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长公共子序列的长度。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字符&#xff08;也可以不删除任何字符&#xff…

如何在一个中文大模型上,加入招投标字段标注的数据,搭建一个招投标字段解析的Transformer模型?

ChatGPT方案1 在一个中文大模型上加入招投标字段标注的数据&#xff0c;并搭建招投标字段解析的Transformer模型可以通过以下步骤实现&#xff1a; 收集并标注招投标相关的数据。可以使用现有的数据集&#xff0c;也可以通过爬虫技术获取相关数据&#xff0c;然后进行人工标注。…

Linux应用编程(进程)

一、进程与程序 注册进程终止处理函数 atexit() #include <stdlib.h> int atexit(void (*function)(void));使用该函数需要包含头文件<stdlib.h>。 函数参数和返回值含义如下&#xff1a; function&#xff1a;函数指针&#xff0c;指向注册的函数&#xff0c;此…

使用S3协议通过dfs实现Spring的SPI机制和spring.factories

目录 参考一、SPI机制1、什么是SPI2、使用场景&#xff1f;3、使用介绍4、代码演示新建工程edevp-dfs-api的spi接口新建阿里云oss实现类新建minio实现类新建测试工程edevp-demo测试 5、总结优点&#xff1a;解耦缺点&#xff1a; 二、Spring Boot的扩展机制之Spring Factories1…

MathType7最新版本下载安装与使用,注册表文件分享,添加为Word公式插件

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;mathtype 免费获取MathType7安装包注册表文件 MathType是强大的数学公式编辑器&#xff0c;与常见的文字处理软件和演示程序配合使用&#xff0c;能够在各种文档中加入复杂的数学公式和符号&#xff0c;可用在编辑数学试卷…

【回忆 总结】我的大学四年

大学四年关键词速览 如果穿越回大一&#xff0c;你想对大一的你提什么最重要的建议&#xff1f;同样是上网课&#xff0c;我为何能比大多数同学学的更好&#xff1f;回到学校&#xff0c;我的大二似乎一帆风顺&#xff1f;在不断的迷茫和徘徊中&#xff0c;大三的我做出的决定&…

Vue开发过程中那些易混淆的知识点

vue & vue cli Vue CLI Vue 一堆的js插件Vue CLI是基于 Node.js 开发出来的工具&#xff0c;它是一个官方发布 vue.js 项目脚手架&#xff0c;可以快速搭建 Vue 开发环境以及对应的 webpack 配置&#xff0c;单独编译&#xff0c;单独部署。可以再集成各种第三方插件&am…

偏好强化学习概述

文章目录 为什么需要了解偏好强化学习什么是偏好强化学习基于偏好的马尔科夫决策过程&#xff08;Markov decision processes with preferences&#xff0c;MDPP&#xff09; 反馈类型分类学习算法分类近似策略分布(Approximating the Policy Distribution)比较和排序策略(Comp…

STATS 782 - R Basic Concepts

文章目录 前言一、R basic1. R Operator2. 变量赋值3. c() - combine 函数4. 对向量的操作5. Special Numerical Values 二、 Built-in Functions1. min, max and range2. sum and prod3. Cumulative Summaries4. paste5. list&#xff08;&#xff09;6. seq&#xff08;&…