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;
// }
},
],
}),
compilation.errors.push(new Error(`Invalid "pattern.to" for the "pattern.from": "${normalizedPattern.from}" and "pattern.transformAll" function. The "to" option must be specified.`));
const logger = compilation.getLogger("copy-webpack-plugin");
logger.log("starting to add additional assets...");
const cache = compilation.getCache("CopyWebpackPlugin");
// 获取
const source = await cache.getPromise(key, etag);
// 设置
await cache.storePromise(key, etag, data);
const etag = cache.getLazyHashedEtag(rawSource)
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);
用于创建文件系统的快照。快照是文件系统状态的一份副本,包含了文件的元数据以及每个文件的最后修改时间戳
/**
*
* @param {number} 开始时间戳
* @param {Iterable} 文件列表,绝对路径,可选
* @param {Iterable} 文件夹列表,同上
* @param {Iterable} 配置忽略文件
* @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);
});
});
}
const { inputFileSystem } = compiler;
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);
});
});
}
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(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);
})
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;
}
}
根据匹配的文件列表,读取文件真实内容
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
};
})
}
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
});
}
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);
})
// ...
}
)
}
// ...
/**
* [
{
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();
});
// ...