webpack plugin源码解析(六) CompressionWebpackPlugin

这篇具有很好参考价值的文章主要介绍了webpack plugin源码解析(六) CompressionWebpackPlugin。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

作用

  • 压缩打包后的文件,可以配置是否删除源文件
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 数据结构
compression-webpack-plugin,vite&webpack源码解析,webpack,javascript,前端

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文章来源地址https://www.toymoban.com/news/detail-764857.html

  • 通过 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);
       }
     });
   });
 }

到了这里,关于webpack plugin源码解析(六) CompressionWebpackPlugin的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • webpack打包之 copy-webpack-plugin

    copy-webpack-plugin 打包复制文件插件。 1、什么时候要使用? 在离线应用中,前端所有文件都需在在本地,有些文件(比如iconFont以及一些静态img)需要转为离线文件,这些文件可以直接引用更方便些,这就需要在打包时直接复制到打包文件下。 2、安装依赖 3、配置webpack 4、打包

    2024年02月17日
    浏览(41)
  • 手写Webpack-Plugin

    通过插件我们可以扩展webpack,使webpack可以执行更广泛的任务,拥有更强的构建能力。 webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能

    2024年01月21日
    浏览(33)
  • 手写一个webpack插件(plugin)

    熟悉 vue 和 react 的小伙伴们都知道,在执行过程中会有各种生命周期钩子,其实webpack也不例外,在使用webpack的时候,我们有时候需要在 webpack 构建流程中引入自定义的行为,这个时候就可以在 hooks 钩子中添加自己的方法。 创建插件 webpack 加载 webpack.config.js 中所有配置,此

    2024年02月08日
    浏览(45)
  • 04-webpack中使用plugin

    loader是用于特定的模块的类型转换,plugin用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等。 CleanWebpackPlugin 用于清除之前打包的文件。npm install clean-webpack-plugin DefinePlugin DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安

    2024年02月09日
    浏览(42)
  • Webpack的Loader和Plugin

    1.1 Loader作用 把js和json外的其它文件转为Webpack可以识别的模块 1.2 Loader简介 1.2.1 Loader类型 1.总类型 pre: 前置loader normal: 普通loader inline: 内联loader post: 后置loader 2.默认类型 默认为normal类型 3.修改类型 配置时可以通过enforce修改 pre,normal,post 类型。 1.2.2 Loader顺序 1.总顺序

    2024年04月18日
    浏览(42)
  • webpack中plugin的工作原理

    一、plugin的作用 Webpack中的插件是用来扩展Webpack功能的函数,加入自定义的构建行为,使 webpack 可以执行更广泛的任务,拥有更强的构建能力,它们在Webpack构建过程中被执行 。Webpack所有的插件都需要在webpack.config.js的plugins节点中配置。 插件的作用很多,以下是插件常见的

    2024年02月10日
    浏览(32)
  • eslint-webpack-plugin

    说明:现在eslint已经弃用了eslint-loader,如果要安装来使用的话,会报错,烦死人 大概的报错信息如下: ERROR in ./src/index.js Module build failed (from ./node_modules/eslint-loader/dist/cjs.js): TypeError: Cannot read property ‘getFormatter’ of undefined 那么我们现在一般使用eslint提供的eslint-webpack-plugin插

    2024年02月15日
    浏览(46)
  • vue中webpack配置compression-webpack-plugin打包压缩和优化,terser-webpack-plugin在构建过程中对 JavaScript 代码进行压缩和优化

    参考地址:https://v4.webpack.js.org/plugins/compression-webpack-plugin/ 一、compression-webpack-plugin的使用,安装插件 二、在 webpack 配置中,require 或 import 引入 三、配置 参考地址:https://v4.webpack.js.org/plugins/terser-webpack-plugin/ 一、安装terser-webpack-plugin 二、在 Webpack 配置中引入 三、配置

    2024年04月14日
    浏览(52)
  • webpack loader和plugins的区别

    在Webpack中,Loader和Plugin是两个不同的概念,用于不同的目的。 Loader是用于处理非JavaScript模块的文件的转换工具。它们将文件作为输入,并将其转换为Webpack可以处理的模块。例如,当您在Webpack配置中使用Babel Loader时,它会将ES6+的JavaScript代码转换为ES5代码,以便在旧版浏览器

    2024年02月09日
    浏览(40)
  • webpack 中的loader 和plugin的区别

    Loader: 作用: Loader 用于在模块加载时对文件进行转换。它是一个转换器,将文件从一种形式转换为另一种形式,例如,将 ES6 语法的 JavaScript 文件转换为能够在浏览器中运行的普通 JavaScript。 使用场景: Loader通常被配置在 module.rules 中,指定了哪些文件应该被哪些Loader处理。

    2024年01月22日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包