文章目录
作用
- 压缩打包后的文件,可以配置是否删除源文件
const CompressionPlugin =require("compression-webpack-plugin");newCompressionPlugin()
涉及 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 进行压缩,并作为独立的 assetstage: 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 库进行压缩
classCompressionPlugin{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(typeofthis.algorithm ==="string"){const zlib =require("zlib");// 默认使用 zlib 压缩this.algorithm = zlib[this.algorithm];if(!this.algorithm){thrownewError(`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 进行压缩,并作为独立的 assetstage: 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 数据结构
asynccompress(compiler, compilation, assets){const cache = compilation.getCache("CompressionWebpackPlugin");// 遍历文件const assetsForMinify =(await Promise.all(Object.keys(assets).map(asyncname=>{// 获取文件信息const{
info,
source
}=compilation.getAsset(name);})if(info.compressed){// 当插件第一次添加压缩文件后,因为 additionalAssets:true 会第二次触发插件回调,如果第一次被压缩了 info.compressed 为 truereturnfalse;}// 通过开发者传递的 test、exclude、include 匹配文件if(!compiler.webpack.ModuleFilenameHelpers.matchObject.bind(undefined,this.options)(name)){returnfalse;}// 获取压缩相关 namelet relatedName;// "gzipped"if(typeofthis.options.algorithm ==="function"){if(typeofthis.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`;}}elseif(this.options.algorithm ==="gzip"){
relatedName ="gzipped";}else{
relatedName =`${this.options.algorithm}ed`;}if(info.related && info.related[relatedName]){returnfalse;}// 缓存文件相关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())||{};// 返回文件 bufferlet buffer;// No need original buffer for cached filesif(!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/236else{
buffer = source.source();if(!Buffer.isBuffer(buffer)){// eslint-disable-next-line no-param-reassign
buffer = Buffer.from(buffer);}}if(buffer.length <this.options.threshold){// 小于开发者传入的要压缩的阈值退出returnfalse;}}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 =awaitthis.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 =newRawSource(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-ignorerelated:{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){returnnewPromise((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);}});});}
本文转载自: https://blog.csdn.net/weixin_43294560/article/details/130246421
版权归原作者 神奇大叔 所有, 如有侵权,请联系我们删除。
版权归原作者 神奇大叔 所有, 如有侵权,请联系我们删除。