webpack plugin源码解析(二) copy-webpack-plugin_copywebpackplugin-程序员宅基地

技术标签: webpack  前端  vite&webpack源码解析  javascript  

作用

  • 拷贝文件到另一个文件
new CopyPlugin({
    
    patterns: [
      {
     
        from: "src/css", 
        to: "css" ,
        transform(content, absoluteFrom) {
    
            console.log('trans',content,absoluteFrom)
            return content;
        },
        // transformAll(assets) {
    
        //     console.log('transAll',assets)
        //     return assets;
        // }
      },
    ],
  }),

涉及 webpack API

compiler.context

  • 获取项目上下文路径(项目根目录)

webpack 接收编译错误 compilation.errors

 compilation.errors.push(new Error(`Invalid "pattern.to" for the "pattern.from": "${
      normalizedPattern.from}" and "pattern.transformAll" function. The "to" option must be specified.`));

compilation.getLogger(“copy-webpack-plugin”);

  • Webpack Logger 可以用在 loader 和 plugin。生成的 Logger 将作为 stats 的一部分进行输出
const logger = compilation.getLogger("copy-webpack-plugin");
logger.log("starting to add additional assets...");

compilation.getCache(“CopyWebpackPlugin”);

  • 用于获取指定插件的缓存对象,后续可以使用该对象来读取和写入缓存数据,下次构建时直接从缓存中读取,避免重复计算
const cache = compilation.getCache("CopyWebpackPlugin");
  • 获取|设置缓存

// 获取
const source = await cache.getPromise(key, etag);

// 设置
await cache.storePromise(key, etag, data);
  • 根据文件内容计算hash

const etag = cache.getLazyHashedEtag(rawSource)
  • 合并hash

const mergedEtag = cache.mergeEtags(cache.getLazyHashedEtag(rawSource),cache.getLazyHashedEtag(rawSource))
  • 获取|新建缓存对象

const cacheItem = cache.getItemCache(key,etag)
// 获取
const content = await cacheItem.getPromise();
// 设置
await cacheItem.storePromise(transformedAsset);

文件快照snapshot

  • 用于创建文件系统的快照。快照是文件系统状态的一份副本,包含了文件的元数据以及每个文件的最后修改时间戳

    • 在开始构建时:webpack 会在开始构建时创建一个快照,用于记录构建开始时文件系统的状态。
    • 在构建结束时:webpack 会在构建结束时创建一个快照,用于记录构建结束时文件系统的状态。
    • 在监听模式下:如果 webpack 在监听模式下运行,那么每次文件系统发生变化时,webpack 都会创建一个快照,用于记录文件系统的最新状态。
  • 创建快照

	/**
	 *
	 * @param {number} 开始时间戳
	 * @param {Iterable<string>} 文件列表,绝对路径,可选
	 * @param {Iterable<string>} 文件夹列表,同上
	 * @param {Iterable<string>} 配置忽略文件
	 * @param {Object} 其他选项配置
	 * 	@param {boolean=} 是否使用 hash 对于快照
	 * 	@param {boolean=} 是否使用时间戳对于快照
	 * @param {function((WebpackError | null)=, (Snapshot | null)=): void} callback callback function
	 * 	node 风格回调
	 */
new Promise((resolve, reject) => {
    
    compilation.fileSystemInfo.createSnapshot(
      startTime, 
      [dependency], // "/xxx/Desktop/webpack/wb/src/css/1.css"
      undefined,
      undefined, 
      null, 
      (error, snapshot) => {
    
        if (error) {
    
          reject(error);
          return;
        }
        resolve(snapshot);
      }
    );
  });
  • 检查快照是否有效

static async checkSnapshotValid(compilation, snapshot) {
    
  // eslint-disable-next-line consistent-return
  return new Promise((resolve, reject) => {
    
    compilation.fileSystemInfo.checkSnapshotValid(snapshot, (error, isValid) => {
    
      if (error) {
    
        reject(error);
        return;
      }

      resolve(isValid);
    });
  });
}

webpack 文件读入系统 inputFileSystem

  • webpack 中用于读取各种类型的文件,包括 JavaScript、CSS、图片等等,默认值是 fs 模块
const {
     inputFileSystem } = compiler;
  • 读取文件 stats 源信息
function stat(inputFileSystem, path) {
    
  return new Promise((resolve, reject) => {
    
    inputFileSystem.stat(path, // "/xxx/Desktop/webpack/wb/src/css" 绝对路径
    (err, stats) => {
    
      if (err) {
    
        reject(err);
        return;
      }
      resolve(stats);
    });
  });
}
  • 读取文件内容
function readFile(inputFileSystem, path) {
    
  return new Promise((resolve, reject) => {
    
    inputFileSystem.readFile(path, // 绝对路径
    (err, data) => {
    
      if (err) {
    
        reject(err);
        return;
      }
      resolve(data);
    });
  });
}

生成 webpack 格式文件 RawSource

const {
     RawSource } = compiler.webpack.sources; // 或者直接webpack.sources.RawSource

const source = new RawSource(data); // buffer | string
source.buffer(); // 返回 buffer 内容

实现

初始化选项

class CopyPlugin {
    
  constructor(options = {
     
    patterns: []
  }) {
    
    validate(
    schema, options, {
    
      name: "Copy Plugin",
      baseDataPath: "options"
    });
	
    this.patterns = options.patterns;
    this.options = options.options || {
    };
  }}
}

apply

apply(compiler) {
    
  const pluginName = this.constructor.name;
  // 在初始化编译时执行
  compiler.hooks.thisCompilation.tap(pluginName, compilation => {
    
    const logger = compilation.getLogger("copy-webpack-plugin");
    const cache = compilation.getCache("CopyWebpackPlugin"); // 用于获取指定插件的缓存对象,后续可以使用该对象来读取和写入缓存数据,下次构建时直接从缓存中读取,避免重复计算

    let globby;
    compilation.hooks.processAssets.tapAsync({
    
        name: "copy-webpack-plugin",
        stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL // 该阶段 webpack 已经完成了所有的文件处理操作
      }, async (unusedAssets, callback) => {
    
      	  
      	  if (typeof globby === "undefined") {
    
	          try {
    
	            // 动态导入 globby 库
	            ({
    
	              globby
	            } = await import("globby"));
	          } catch (error) {
    
	            callback(error);
	            return;
	          }
         }
         
         logger.log("starting to add additional assets...");
		 
		 const copiedResultMap = new Map(); // 拷贝内容
         const scheduledTasks = [];// 存放拷贝任务
	     
	     this.patterns.map((item, index) => scheduledTasks.push(async () => {
    
	     	// ... 拷贝任务
	     })
	     
	     // 限制任务并发量,默认最多一次处理100个拷贝任务
	     await throttleAll(this.options.concurrency || 100, scheduledTasks);
	     const copiedResult = [...copiedResultMap.entries()].sort((a, b) => a[0] - b[0]); // 拷贝结果进行优先级排序
	     
	     // 拷贝文件输出
	     //...
      }
    )
}

异步执行拷贝任务

this.patterns.map((item, index) => scheduledTasks.push(async () => {
    
    // 格式化配置
    const normalizedPattern = typeof item === "string" ? {
    
      from: item
    } : {
     ...item
    };
   
    // 设置上下文路径(项目根目录):// "/xxx/Desktop/webpack/wb"
    const context = typeof normalizedPattern.context === "undefined" ? compiler.context : path.isAbsolute(normalizedPattern.context) ? normalizedPattern.context : path.join(compiler.context, normalizedPattern.context);
    
	let copiedResult;
	try {
    
	// 返回匹配的文件信息以及文件内容 ( rawSource ) 列表
	  copiedResult = await CopyPlugin.runPattern(globby, compiler, compilation, logger, cache,normalizedPattern, index);
	} catch (error) {
    
      compilation.errors.push(error);
      return;
    }
	
	if (!copiedResult) {
    
      return;
    }
    
	// 过滤无效值
	let filteredCopiedResult = copiedResult.filter(result => Boolean(result));
	
	// 如果配置了 transformAll 修改要拷贝的所有资源
	if (typeof normalizedPattern.transformAll !== "undefined") {
    }){
    
		// ...
		filteredCopiedResult = [transformedAsset];
	}
    // 开发者传入了任务优先级配置相关
    const priority = normalizedPattern.priority || 0;

    if (!copiedResultMap.has(priority)) {
     
      copiedResultMap.set(priority, []);
    }

    copiedResultMap.get(priority).push(...filteredCopiedResult);
})

 

runPattern 匹配拷贝文件信息

CopyPlugin.runPattern

static async runPattern(globby, compiler, compilation, logger, cache, inputPattern, index) {
    
    // 格式化配置和路径
	const {
    
      RawSource
    } = compiler.webpack.sources; // 或者直接webpack.sources.RawSource
    
    const pattern = {
     ...inputPattern };
    const originalFrom = pattern.from;
    const normalizedOriginalFrom = path.normalize(originalFrom);
    logger.log(`starting to process a pattern from '${
      normalizedOriginalFrom}' using '${
      pattern.context}' context`);
	
	let absoluteFrom;

    if (path.isAbsolute(normalizedOriginalFrom)) {
    
      absoluteFrom = normalizedOriginalFrom;
    } else {
    
      absoluteFrom = path.resolve(pattern.context, normalizedOriginalFrom); // "/xxx/Desktop/webpack/wb/src/css"
    }
    logger.debug(`getting stats for '${
      absoluteFrom}'...`);
	
	// 根据决定路径 from 读取文件 stats 源信息,判断文件类型
	const {
    
      inputFileSystem  // webpack 中用于读取各种类型的文件,包括 JavaScript、CSS、图片等等,默认值是 fs 模块
    } = compiler;
    
    let stats;
    // 返回文件的一些属性:时间戳、大小、uid、判断文件类型方法等
    stats = await stat(inputFileSystem, absoluteFrom); // from 属性的绝对路径
	
	let fromType;

    if (stats) {
    
      if (stats.isDirectory()) {
    
        fromType = "dir";  // 本例为 dir
        logger.debug(`determined '${
      absoluteFrom}' is a directory`);
      } else if (stats.isFile()) {
    
        fromType = "file";
        logger.debug(`determined '${
      absoluteFrom}' is a file`);
      } else {
    
        // Fallback
        fromType = "glob";
        logger.debug(`determined '${
      absoluteFrom}' is unknown`);
      }
    } else {
    
      fromType = "glob";
      logger.debug(`determined '${
      absoluteFrom}' is a glob`);
    }
	
	// ...
}

stat

function stat(inputFileSystem, path) {
    
  return new Promise((resolve, reject) => {
    
    inputFileSystem.stat(path,
    (err, stats) => {
    
      if (err) {
    
        reject(err);
        return;
      }
      resolve(stats);
    });
  });
}

根据 from 得出的文件类型添加对应的依赖,更改时重新出发编译

static async runPattern(globby, compiler, compilation, logger, cache, inputPattern, index) {
    
    // ...
    let fromType; // 本例为 dir
    // ...
    const globOptions = {
     ...{
    
        followSymbolicLinks: true
      },
      ...(pattern.globOptions || {
    }),
      ...{
    
        cwd: pattern.context, // "/xxx/Desktop/webpack/wb"
        objectMode: true
      }
    }; 
    
    globOptions.fs = inputFileSystem; // 更改 globby 库的读取方式
    let glob;
	
	switch (fromType) {
    
	  case "dir":
	    compilation.contextDependencies.add(absoluteFrom); // 文件夹依赖,文件夹变更时重新触发编译
	    logger.debug(`added '${
      absoluteFrom}' as a context dependency`);
	    pattern.context = absoluteFrom; // "/xxx/Desktop/webpack/wb/src/css"
	    
	    // escapePath 转译 *?|(){}[] 等字符转,避免影响 glob 匹配规则
	    // glob: "/xxx/Desktop/webpack/wb/src/css/**/*"
	    glob = path.posix.join(fastGlob.escapePath(normalizePath(path.resolve(absoluteFrom))), "**/*");
	    
	    // absoluteFrom: "/xxx/Desktop/webpack/wb/src/css/**/*"
	    absoluteFrom = path.join(absoluteFrom, "**/*");
	
	    if (typeof globOptions.dot === "undefined") {
    
	      globOptions.dot = true;
	    }
	
	    break;
	
	  case "file":
	    compilation.fileDependencies.add(absoluteFrom);
	    logger.debug(`added '${
      absoluteFrom}' as a file dependency`);
	    pattern.context = path.dirname(absoluteFrom);
	    glob = fastGlob.escapePath(normalizePath(path.resolve(absoluteFrom)));
	
	    if (typeof globOptions.dot === "undefined") {
    
	      globOptions.dot = true;
	    }
	
	    break;
	
	  case "glob":
	  default:
	    {
    
	      const contextDependencies = path.normalize(globParent(absoluteFrom));
	      compilation.contextDependencies.add(contextDependencies);
	      logger.debug(`added '${
      contextDependencies}' as a context dependency`);
	      glob = path.isAbsolute(originalFrom) ? originalFrom : path.posix.join(fastGlob.escapePath(normalizePath(path.resolve(pattern.context))), originalFrom);
	    }
	}
	logger.log(`begin globbing '${
      glob}'...`);
}

根据 globby 库匹配出要拷贝的文件列表

static async runPattern(globby, compiler, compilation, logger, cache, inputPattern, index) {
    
	// ...
	
	let globEntries;
	try {
    
	  // glob:/xxx/Desktop/webpack/wb/src/css/**/*
      globEntries = await globby(glob, globOptions); // 返回匹配到的文件列表( 文件名 + 文件绝对路径 )
    } catch (error) {
    
      compilation.errors.push(
      /** @type {WebpackError} */
      error);
      return;
    }
	
	// 没有匹配结果的处理
	if (globEntries.length === 0) {
    
      if (pattern.noErrorOnMissing) {
     // 如果配置了无结果不报错
        logger.log(`finished to process a pattern from '${
      normalizedOriginalFrom}' using '${
      pattern.context}' context to '${
      pattern.to}'`);
        return;
      }

      const missingError = new Error(`unable to locate '${
      glob}' glob`);
      compilation.errors.push(missingError);
      return;
    }
}

根据匹配的文件列表,读取文件真实内容

  • 格式化各种文件路径
  • 优先从缓存里读取文件内容,没有则通过 inputFileSystem 读取
static async runPattern(globby, compiler, compilation, logger, cache, inputPattern, index) {
    
	// ...
	let globEntries;
	/**
		[
			{
			  dirent: { // 还有其他判断文件类型的方法
			    name: "1.css",
			    ...
			  },
			  name: "1.css",
			  path: "/xxx/webpack/wb/src/css/1.css",
			},
			...
		]
	*/
	// ...
	
	let copiedResult;
	try {
    
      copiedResult = await Promise.all(globEntries.map(async globEntry => {
    
        // 如果是非文件类型退出比如(文件夹)
        if (!globEntry.dirent.isFile()) {
    
          return;
        }
        
        // 如果配置了 filter 不拷贝某些文件
        if (pattern.filter) {
    
          let isFiltered;

          try {
    
            isFiltered = await pattern.filter(globEntry.path);
          } catch (error) {
    
            compilation.errors.push(error);
            return;
          }
          if (!isFiltered) {
    
            logger.log(`skip '${
      globEntry.path}', because it was filtered`);
            return;
          }
        }
      })
	  
	  const from = globEntry.path; // "/xxx/Desktop/webpack/wb/src/css/1.css"
      logger.debug(`found '${
      from}'`); // `globby`/`fast-glob` return the relative path when the path contains special characters on windows
      
      const absoluteFilename = path.resolve(pattern.context, from); // "/xxx/webpack/wb/src/css/1.css"
	  
	  // 拿到要去到的路径
	  // to: "css"
	  const to = typeof pattern.to === "function" ? await pattern.to({
    
        context: pattern.context,
        absoluteFilename
      }) : path.normalize(typeof pattern.to !== "undefined" ? pattern.to : "");
      
	  // 根据 to 结尾是否是'\'或者"" 判断是文件还是文件夹
	  // toType: "dir",path.sep: "/"
	  const toType = pattern.toType ? pattern.toType : template.test(to) ? "template" : path.extname(to) === "" || to.slice(-1) === path.sep ? "dir" : "file";
	  logger.log(`'to' option '${
      to}' determinated as '${
      toType}'`);
	  
	  const relativeFrom = path.relative(pattern.context, absoluteFilename); // 1.css
	  
	  // filename 转化成相对路径
	  let filename = toType === "dir" ? path.join(to, relativeFrom) : to; // css/1.css
	  if (path.isAbsolute(filename)) {
    
         filename = path.relative(compiler.options.output.path, filename);
      }
      logger.log(`determined that '${
      from}' should write to '${
      filename}'`);
	  
	  const sourceFilename = normalizePath(path.relative(compiler.context, absoluteFilename)); // "src/css/1.css"
      
      if (fromType === "dir" || fromType === "glob") {
    
      	compilation.fileDependencies.add(absoluteFilename); // 将源文件加入文件依赖 fileDependencies 中, "/xxx/Desktop/webpack/wb/src/css/1.css"
      	logger.debug(`added '${
      absoluteFilename}' as a file dependency`);
      }
      
      // ...

}

从缓存中读取内容

static async runPattern(globby, compiler, compilation, logger, cache, inputPattern, index) {
    
	// ...
	copiedResult = await Promise.all(globEntries.map(async globEntry => {
    
		// ... 各种路径转换
		let cacheEntry;
        logger.debug(`getting cache for '${
      absoluteFilename}'...`);

        try {
    
          cacheEntry = await cache.getPromise(`${
      sourceFilename}|${
      index}`, null); // 用于获取指定键名对应的缓存数据,第一个参数为 key ,第二个参数为etag
        } catch (error) {
    
          compilation.errors.push(error);
          return;
        }
		
		let source;

        if (cacheEntry) {
    
          logger.debug(`found cache for '${
      absoluteFilename}'...`);
          let isValidSnapshot;
          logger.debug(`checking snapshot on valid for '${
      absoluteFilename}'...`);

          try {
    
            isValidSnapshot = await CopyPlugin.checkSnapshotValid(compilation, cacheEntry.snapshot); // cacheEntry 里会存放文件快照 snapshot
          } catch (error) {
    
            compilation.errors.push(error);
            return;
          }
		  // 如果快照有效,则直接获取缓存中的内容
          if (isValidSnapshot) {
    
            logger.debug(`snapshot for '${
      absoluteFilename}' is valid`);
            ({
    
              source
            } = cacheEntry);
          } else {
    
            logger.debug(`snapshot for '${
      absoluteFilename}' is invalid`);
          }
        } else {
    
          logger.debug(`missed cache for '${
      absoluteFilename}'`);
        }
	})
}

checkSnapshotValid

  • 检查文件快照是否有效
static async checkSnapshotValid(compilation, snapshot) {
    
  return new Promise((resolve, reject) => {
    
    compilation.fileSystemInfo.checkSnapshotValid(snapshot, (error, isValid) => {
    
      if (error) {
    
        reject(error);
        return;
      }
      resolve(isValid);
    });
  });
}

无缓存通过 inputFileSystem 读取

static async runPattern(globby, compiler, compilation, logger, cache, inputPattern, index) {
    
	// ...
	copiedResult = await Promise.all(globEntries.map(async globEntry => {
    
		// ...
		if (!source) {
    
          const startTime = Date.now();
          logger.debug(`reading '${
      absoluteFilename}'...`);
          let data;

          try {
    
            // data 是 buffer 类型
            data = await readFile(inputFileSystem, absoluteFilename); // inputFileSystem.readFile 读取文件内容
          } catch (error) {
    
            compilation.errors.push(error);
            return;
          }

          logger.debug(`read '${
      absoluteFilename}'`);
          source = new RawSource(data); // 生成 webpack 类型文件
          
          let snapshot;
          logger.debug(`creating snapshot for '${
      absoluteFilename}'...`);

          try {
    
            snapshot = await CopyPlugin.createSnapshot(compilation, startTime, absoluteFilename); // 文件的快照信息
          } catch (error) {
    
            compilation.errors.push(error);
            return;
          }

          if (snapshot) {
    
            logger.debug(`created snapshot for '${
      absoluteFilename}'`);
            logger.debug(`storing cache for '${
      absoluteFilename}'...`);

            try {
    
              // 将文件相关信息存入缓存
              await cache.storePromise(`${
      sourceFilename}|${
      index}`, null, {
    
                source,
                snapshot
              });
            } catch (error) {
    
              compilation.errors.push(error);
              return;
            }

            logger.debug(`stored cache for '${
      absoluteFilename}'`);
          }
        }
	})
}

readFile

function readFile(inputFileSystem, path) {
    
  return new Promise((resolve, reject) => {
    
    inputFileSystem.readFile(path,
    (err, data) => {
    
      if (err) {
    
        reject(err);
        return;
      }
      resolve(data);
    });
  });
}

createSnapshot

  • 创建文件快照
static async createSnapshot(compilation, startTime, dependency) {
    
  // dependency: "/xxx/Desktop/webpack/wb/src/css/1.css"
  return new Promise((resolve, reject) => {
    
    compilation.fileSystemInfo.createSnapshot(
      startTime, 
      [dependency],
      undefined,
      undefined, 
      null, 
      (error, snapshot) => {
    
        if (error) {
    
          reject(error);
          return;
        }
        resolve(snapshot);
      }
    );
  });
}

如果配置了 transform,拷贝过程中修改源文件内容

static async runPattern(globby, compiler, compilation, logger, cache, inputPattern, index) {
    
	// ...
	copiedResult = await Promise.all(globEntries.map(async globEntry => {
    
		// ...
		if (pattern.transform) {
    
		
          const transformObj = typeof pattern.transform === "function" ? {
    
            transformer: pattern.transform
          } : pattern.transform;

          if (transformObj.transformer) {
    
            logger.log(`transforming content for '${
      absoluteFilename}'...`);
            
            // 拿到源文件 buffer
            const buffer = source.buffer();

            if (transformObj.cache) {
     // 如果配置了cache,没怎么看懂
              // TODO: remove in the next major release
              const hasher = compiler.webpack && compiler.webpack.util && compiler.webpack.util.createHash ? compiler.webpack.util.createHash("xxhash64") : // eslint-disable-next-line global-require
              require("crypto").createHash("md4");
              const defaultCacheKeys = {
    
                version,
                sourceFilename,
                transform: transformObj.transformer,
                contentHash: hasher.update(buffer).digest("hex"),
                index
              };
              const cacheKeys = `transform|${
      serialize(typeof transformObj.cache === "boolean" ? defaultCacheKeys : typeof transformObj.cache.keys === "function" ? await transformObj.cache.keys(defaultCacheKeys, absoluteFilename) : {
       ...defaultCacheKeys,
                ...transformObj.cache.keys
              })}`;
              logger.debug(`getting transformation cache for '${
      absoluteFilename}'...`);
              const cacheItem = cache.getItemCache(cacheKeys, cache.getLazyHashedEtag(source));
              source = await cacheItem.getPromise();
              logger.debug(source ? `found transformation cache for '${
      absoluteFilename}'` : `no transformation cache for '${
      absoluteFilename}'`);

              if (!source) {
    
                const transformed = await transformObj.transformer(buffer, absoluteFilename);
                source = new RawSource(transformed);
                logger.debug(`caching transformation for '${
      absoluteFilename}'...`);
                await cacheItem.storePromise(source);
                logger.debug(`cached transformation for '${
      absoluteFilename}'`);
              }
            } else {
    
              // transformer(buffer, absoluteFilename) 将源文件 buffer 和 文件绝对路径传递给 transformer,然后将返回内容重新生成 RawSource
              source = new RawSource(await transformObj.transformer(buffer, absoluteFilename));
            }
          }
        }
	})
}

最后转换输出文件信息

static async runPattern(globby, compiler, compilation, logger, cache, inputPattern, index) {
    
	// ...
	copiedResult = await Promise.all(globEntries.map(async globEntry => {
    
		// ...
		
		let info = typeof pattern.info === "undefined" ? {
    } : typeof pattern.info === "function" ? pattern.info({
    
          absoluteFilename,
          sourceFilename,
          filename,
          toType
        }) || {
    } : pattern.info || {
    };
        
        if (toType === "template") {
     // toType 为 'dir',这里未进入
          logger.log(`interpolating template '${
      filename}' for '${
      sourceFilename}'...`);
          const contentHash = CopyPlugin.getContentHash(compiler, compilation, source.buffer());
          const ext = path.extname(sourceFilename);
          const base = path.basename(sourceFilename);
          const name = base.slice(0, base.length - ext.length);
          const data = {
    
            filename: normalizePath(path.relative(pattern.context, absoluteFilename)),
            contentHash,
            chunk: {
    
              name,
              id:
              /** @type {string} */
              sourceFilename,
              hash: contentHash
            }
          };
          const {
    
            path: interpolatedFilename,
            info: assetInfo
          } = compilation.getPathWithInfo(normalizePath(filename), data);
          info = {
     ...info,
            ...assetInfo
          };
          filename = interpolatedFilename;
          logger.log(`interpolated template '${
      filename}' for '${
      sourceFilename}'`);
        } else {
    
          filename = normalizePath(filename); // 序列化'\\'
        } 
		
       
       return {
    
          sourceFilename, // "src/css/1.css"
          absoluteFilename, // "/xxx/Desktop/webpack/wb/src/css/1.css"
          filename, // "css/1.css"
          source, // rawSource
          info, // {}
          force: pattern.force // undefined
        };
	})
}

限制 runPattern 并发量 throttleAll

apply(compiler) {
    
  const pluginName = this.constructor.name;
  // 在初始化编译时执行
  compiler.hooks.thisCompilation.tap(pluginName, compilation => {
    
    const logger = compilation.getLogger("copy-webpack-plugin");
    const cache = compilation.getCache("CopyWebpackPlugin"); // 用于获取指定插件的缓存对象,后续可以使用该对象来读取和写入缓存数据,下次构建时直接从缓存中读取,避免重复计算

    let globby;
    compilation.hooks.processAssets.tapAsync({
    
        name: "copy-webpack-plugin",
        stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL // 该阶段 webpack 已经完成了所有的文件处理操作
      }, async (unusedAssets, callback) => {
    
      	  
		 // ...		 
		 const copiedResultMap = new Map(); // 拷贝内容
         const scheduledTasks = [];// 存放拷贝任务
	     
	     this.patterns.map((item, index) => scheduledTasks.push(async () => {
    
	     	// ... 拷贝任务
	     })
	     
	     // 限制任务并发量,默认最多一次处理100个拷贝任务
	     await throttleAll(this.options.concurrency || 100, scheduledTasks);
	     const copiedResult = [...copiedResultMap.entries()].sort((a, b) => a[0] - b[0]); // 拷贝结果进行优先级排序
	     
	     // 拷贝文件输出
	     //...
      }
    )
}
function throttleAll(limit, tasks) {
    
  if (!Number.isInteger(limit) || limit < 1) {
    
    throw new TypeError(`Expected \`limit\` to be a finite number > 0, got \`${
      limit}\` (${
      typeof limit})`);
  }

  if (!Array.isArray(tasks) || !tasks.every(task => typeof task === `function`)) {
    
    throw new TypeError(`Expected \`tasks\` to be a list of functions returning a promise`);
  }

  return new Promise((resolve, reject) => {
    
    const result = Array(tasks.length).fill(notSettled);
    const entries = tasks.entries();

    const next = () => {
    
      const {
    
        done,
        value
      } = entries.next();

      if (done) {
    
        const isLast = !result.includes(notSettled); // result 已经全部解析

        if (isLast) {
    
          resolve(result);
        }

        return;
      }

      const [index, task] = value;
      
      const onFulfilled = x => {
     // 异步任务完成后,执行下一个任务
        result[index] = x;
        next();
      };

      task().then(onFulfilled, reject);
    };

    Array(limit).fill(0).forEach(next);// 执行 100 次 next
  });
}

transformAll

  • 如果开发者配置了transformAll,在资源全部解析匹配后,输出前统一修改资源文件
apply(compiler) {
    
  const pluginName = this.constructor.name;
  // 在初始化编译时执行
  compiler.hooks.thisCompilation.tap(pluginName, compilation => {
    
    const logger = compilation.getLogger("copy-webpack-plugin");
    const cache = compilation.getCache("CopyWebpackPlugin"); // 用于获取指定插件的缓存对象,后续可以使用该对象来读取和写入缓存数据,下次构建时直接从缓存中读取,避免重复计算

    let globby;
    compilation.hooks.processAssets.tapAsync({
    
        name: "copy-webpack-plugin",
        stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL // 该阶段 webpack 已经完成了所有的文件处理操作
      }, async (unusedAssets, callback) => {
    
      	  
		 // ...		 
		 const copiedResultMap = new Map(); // 拷贝内容
         const scheduledTasks = [];// 存放拷贝任务
	     
	     this.patterns.map((item, index) => scheduledTasks.push(async () => {
    
			// 返回匹配的文件信息以及文件内容 ( rawSource ) 列表
            copiedResult = await CopyPlugin.runPattern(globby, compiler, compilation, logger, cache,normalizedPattern, index);
			
			let filteredCopiedResult = copiedResult.filter(result => Boolean(result));
            
	     	if (typeof normalizedPattern.transformAll !== "undefined") {
    
	     		filteredCopiedResult.sort((a, b) => a.absoluteFilename > b.absoluteFilename ? 1 : a.absoluteFilename < b.absoluteFilename ? -1 : 0);
	     		
				// cache.getLazyHashedEtag 计算指定源代码的哈希值,已经存在则返回
				
				// 合并所有文件的hash值
	            const mergedEtag = filteredCopiedResult.length === 1 ? cache.getLazyHashedEtag(filteredCopiedResult[0].source) : filteredCopiedResult.reduce(
		            (accumulator, asset, i) => {
    
		              // cache.mergeEtags 将多个哈希值合并为一个哈希值。通过合并多个哈希值为一个哈希值
		              accumulator = cache.mergeEtags(i === 1 ? cache.getLazyHashedEtag(accumulator.source) : accumulator, cache.getLazyHashedEtag(asset.source));
		              return accumulator;
		            });

				// 为所有文件 创建|返回 对应的 cache ,传入 key 和 etag,存储合并后的 hash 值
	            const cacheItem = cache.getItemCache(`transformAll|${
      serialize({
      
	              version,
	              from: normalizedPattern.from,
	              to: normalizedPattern.to,
	              transformAll: normalizedPattern.transformAll
	            })}`, mergedEtag);
	            
	            let transformedAsset = await cacheItem.getPromise();
	            
	            // 如果缓存中没有值
	            if (!transformedAsset) {
    
	              transformedAsset = {
    
	                filename: normalizedPattern.to
	              };
				  
				  try {
    
	                // 将已经获取到的所有文件信息传递给 transformAll
	                transformedAsset.data = await normalizedPattern.transformAll(filteredCopiedResult.map(asset => {
    
	                  return {
    
	                    data: asset.source.buffer(),
	                    sourceFilename: asset.sourceFilename,
	                    absoluteFilename: asset.absoluteFilename
	                  };
	                }));
	              } catch (error) {
    
	                compilation.errors.push(error);
	                return;
	              }
				 
				   const filename = typeof normalizedPattern.to === "function" ? await normalizedPattern.to({
    
	                context
	              }) : normalizedPattern.to;
	              
	              const {
    
	                RawSource
	              } = compiler.webpack.sources;
	              transformedAsset.source = new RawSource(transformedAsset.data);
	              transformedAsset.force = normalizedPattern.force;
	              // 缓存存储转换结果
	              await cacheItem.storePromise(transformedAsset);
	              
	            }
	             filteredCopiedResult = [transformedAsset];
		     }
	         const priority = normalizedPattern.priority || 0;
	
	         if (!copiedResultMap.has(priority)) {
     // 开发者传入了优先级配置相关
	           copiedResultMap.set(priority, []);
	         }
	
	         copiedResultMap.get(priority).push(...filteredCopiedResult);
	     })
		 
		 // ...
	     
	   }
    )
}

输出拷贝文件 compilation.emitAsset

// ...

/**
 * [
    {
      sourceFilename: "src/css/1.css",
      absoluteFilename: "/xxx/Desktop/webpack/wb/src/css/1.css",
      filename: "css/1.css",
      source: RawSource
      info: {
      },
      force: undefined,
    },
    ...
  ]
 */
copiedResult.reduce((acc, val) => acc.concat(val[1]), []).filter(Boolean).forEach( result => {
    

 const {
    
   absoluteFilename,
   sourceFilename,
   filename,
   source,
   force
 } = result;
 
 const existingAsset = compilation.getAsset(filename);

 if (existingAsset) {
     // 判断是否已存在文件,已存在则根据开发者传入配置来更新或跳过
 
   if (force) {
     // 已存在更新
     const info = {
    
       copied: true,
       sourceFilename
     };
     logger.log(`force updating '${
      filename}' from '${
      absoluteFilename}' to compilation assets, because it already exists...`);
     compilation.updateAsset(filename, source, {
     ...info,
       ...result.info
     });
     logger.log(`force updated '${
      filename}' from '${
      absoluteFilename}' to compilation assets, because it already exists`);
     return;
   }
   // 否则跳过
   logger.log(`skip adding '${
      filename}' from '${
      absoluteFilename}' to compilation assets, because it already exists`);
   return;
 }

 const info = {
    
   copied: true,
   sourceFilename
 };
 logger.log(`writing '${
      filename}' from '${
      absoluteFilename}' to compilation assets...`);
 // 通过 emitAsset 输出拷贝文件
 compilation.emitAsset(filename, source, {
     ...info,
   ...result.info
 });
 
 logger.log(`written '${
      filename}' from '${
      absoluteFilename}' to compilation assets`);


// ...
logger.log("finished to adding additional assets");
callback();
});


// ...
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43294560/article/details/130123781

智能推荐

粒子群算法matlab代码(注释很详细哦,图像也美美哒,任意维度)_多种群并行粒子群算法matlab-程序员宅基地

文章浏览阅读4w次,点赞564次,收藏1.1k次。整个程序分为5个脚本pso1_mian.m:主程序,在此脚本内设置参数。pso1_im.m:画出函数图像(仅1维和2维)pso1_in.m:初始化pso1_in2.m:迭代寻优并输出结果另外还有一个目标函数,单独为一个脚本。推荐的测试函数—>这里先上运行结果图下面是源码1.pso1_mian.m这里的目标函数用函数句柄的形式调用(第15行)%% 粒子群算法%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% pso1_im_多种群并行粒子群算法matlab

使用 Python 的铅笔素描图像-程序员宅基地

文章浏览阅读1k次。图片在 Python 中表示为一组数字。所以我们可以进行各种矩阵操作来得到令人兴奋的结果。在本教程中,将向你展示如何只用几行代码创建“铅笔”草图图像。这个过程非常简单:灰度图像反转颜色模糊倒置图像将减淡混合应用于模糊和灰度图像我们可以为此选择任何我们想要的图像。将演示如何创建可以应用于任何图像、视频或实时流的对象。导入库OpenCV 和 Numpy 是项目所需的唯一库。我们使用以下两行代码导入它们..._mac怎样用python画素描版本的图

php laravel实战项目,Laravel框架应用:7个实战项目-程序员宅基地

文章浏览阅读1.4k次。很多PHP开发者在进行项目开发的时候,第一时间就会想到Laravel,如果你也正想学习这个优雅简洁的Laravel框架,不妨可以从以下8个教程入手,从基础到实战项目都包含了。Laravel框架简介:Laravel是一套简洁、优雅的PHP Web开发框架(PHP Web Framework)。它可以让你从面条一样杂乱的代码中解脱出来,帮你构建一个完美的网络APP,而且每行代码都可以简洁、富于表达力。..._laravel项目实例

consider using the '--user' option or check the permissions错误_consider using the `--user` option or check the pe-程序员宅基地

文章浏览阅读4.9w次,点赞23次,收藏37次。 win7下使用pip install numpy出现错误consider using the '--user' option or check the permissions纠正方法:pip install --user numpy参考链接:https://github.com/googlesamples/assistant-sdk-python/issues/244 ..._consider using the `--user` option or check the permissions.

就读体验丨香港科技大学工学院科技领导及创业(TLE)理学硕士学位课程(上)_technology leadership and entrepreneurship-程序员宅基地

文章浏览阅读3.3k次,点赞2次,收藏2次。本文转载自公众号:HKUSTGSAA项目概览香港科技大学工学院的Technology Leadership and Entrepreneurship(TLE)MSc program项目时长Full-time1.5年,Part-time 3年,学生必须修满至少30学分的课程才能毕业。其中包括9学分的核心课程,6学分的工程学院提供的技术类选修课,6学分的工商管理学院、工学院、理学院或公共政策学院提供的选修课,以及9个学分的 Project。TLE项目希望同学在项目期间初步完成自己的创业产品雏形,所以非.._technology leadership and entrepreneurship

毕设开源 基于协同过滤的电影推荐系统-程序员宅基地

文章浏览阅读780次,点赞28次,收藏26次。今天学长向大家介绍一个学长帮助往届学生做的毕业设计项目基于协同过滤的电影推荐系统选题指导, 项目分享:见文末**毕设帮助, 选题指导, 项目分享: **

随便推点

2021-05-21 仓库温控系统(Winform) 05 获取类属性静态方法PropertyHelper_properties = properties.where(p => listcols.contai-程序员宅基地

文章浏览阅读7.1w次。public class PropertyHelper{ /// <summary> /// 返回指定类型的指定列名的属性数组 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="cols"></param> /// <returns></returns> public _properties = properties.where(p => listcols.contains(p.getcolname().tolower(

移动开发技术:APP门户页面设计与开发_移动门户系统设计-程序员宅基地

文章浏览阅读145次。Fragment:Fragment是一种可以嵌入在Activity当中的UI片段,用来组建Activity界面的局部模块, 也可以说一个Actiivty界面可以由多个Fragment组成,其行为与Activity很相似,,有自己对应的布局(包含具体的View),,它有自己的生命周期,接收自己的输入事件,并且可以从运行中的activity中添加或移除。用imageview设计导入图片的格式,其中用 android:src="@drawable/bottom_4"导入需要用到的图片。_移动门户系统设计

Integer.MAX_VALUE_int max = integer.max_value;-程序员宅基地

文章浏览阅读252次。Integer.MAX_VALUE表示int的最大值int max=Integer.MAX_VALUE;//2147483647Integer.MIN_VALUE表示int的最小值int min=Integer.MIN_VALUE;//-2147483648_int max = integer.max_value;

JavaWeb项目:航班信息管理系统(tomcat+jsp)_javaweb航空管理系统-程序员宅基地

文章浏览阅读975次,点赞15次,收藏23次。该项目着重学生的实际应用场景来设计,模拟 机场中的航班系统的业务实现以及扩展,能够实现航班信息管理的的所有功能,包括航班信息,航 班目的地查询等功能的实现;用户输入用户名,真实姓名,密码进行注册,注册验证通过后,将用户存储到数据库中,如果数据库中已有相同用户名,则需要重新注册。:对管理员输入的用户名,密码进行验证,验证通过后,管理员可以使用航班信息管 理系统中所有权限的功能,否则重新登录该系统。:管理员登录成功,可以删除航班信息,删除完毕以后跳转到主页面显示所 以航班信息。_javaweb航空管理系统

Hutool基本用法介绍-程序员宅基地

文章浏览阅读1.5k次。在使用过程中,可以查阅 Hutool 的官方文档或者源码,以便更好地了解每个工具的具体用法和参数。它提供了丰富的工具类和方法,涵盖了字符串处理、日期时间操作、网络请求、文件操作、加解密、数据校验、反射操作等领域。你可以根据自己的项目需求选择适合的工具,来简化你的 Java 开发过程。发起 HTTP 请求(GET、POST、PUT、DELETE 等),支持设置请求头、请求参数、响应处理等。提供文件复制、移动、删除、读取、写入等操作,还支持递归遍历文件夹。支持 XML 数据的解析、生成和操作。_hutool

基于autojs脚本语言引擎开发的安卓ipv6聊天室开源了,全网独家-程序员宅基地

文章浏览阅读1.3k次。说明本文提供的代码仅供参考。可能有些地方在最新版本的Auto.js上面需要做修改,才能运行。Auto.js简介Auto.js是利用安卓系统的“辅助功能”实现类似于按键精灵一样,可以通过代码模拟一系列界面动作的辅助工作。与“按键精灵”不同的是,它的模拟动作并不是简单的使用在界面定坐标点来实现,而是类似与win一般,找窗口句柄来实现的。Auto.js使用JavaScript作为脚本语言,目...