搜索
您的当前位置:首页正文

webpack源码分析——cleverMerge、parseObject、cachedParseObject、cachedCleverMerge、mergeEntries等函数

来源:步旅网

一、cleverMerge 函数

函数功能

源码
cleverMerge 函数中代码相对比较简单,主要的逻辑在 _cleverMerge 函数中。下面只做展示

const cleverMerge = (first, second) => {
	if (second === undefined) return first;
	if (first === undefined) return second;
	if (typeof second !== "object" || second === null) return second;
	if (typeof first !== "object" || first === null) return first;

	return _cleverMerge(first, second, false);
};

二、parseObject 函数

函数功能

主要功能是对给定的对象进行结构化解析,并组织信息以便于后续的访问和处理

函数分析

  1. 初始化信息存储:创建一个 Map 对象 info 来存储解析后的信息。
    	const info = new Map();
    
  2. 获取或创建信息条目:定义 getInfo 函数,用于获取 info 中的条目或创建新的条目。
    	const getInfo = p => {
    		const entry = info.get(p); // 获取p在info中对于的值
    		if (entry !== undefined) return entry; // 在info中存在p对于的值,就直接返回 entry
    		const newEntry = { // 否则就创建一个对象
    			base: undefined,
    			byProperty: undefined,
    			byValues: undefined
    		};
    		info.set(p, newEntry); // 并按照键值对的形式存入
    		return newEntry; // 返回新的对象
    	};
    
  3. 遍历对象属性:遍历 obj 的所有属性。
    for (const key of Object.keys(obj)) {
    	if (key.startsWith("by")) {
    		const byProperty = key;
    		const byObj = obj[byProperty];
    		if (typeof byObj === "object") {
    			for (const byValue of Object.keys(byObj)) {
    				const obj = byObj[byValue];
    				for (const key of Object.keys(obj)) {
    					const entry = getInfo(key);
    					if (entry.byProperty === undefined) {
    						entry.byProperty = byProperty;
    						entry.byValues = new Map();
    					} else if (entry.byProperty !== byProperty) {
    						throw new Error(
    							`${byProperty} and ${entry.byProperty} for a single property is not supported`
    						);
    					}
    					entry.byValues.set(byValue, obj[key]);
    					if (byValue === "default") {
    						for (const otherByValue of Object.keys(byObj)) {
    							if (!entry.byValues.has(otherByValue))
    								entry.byValues.set(otherByValue, undefined);
    						}
    					}
    				}
    			}
    		} else if (typeof byObj === "function") {
    			if (dynamicInfo === undefined) {
    				dynamicInfo = {
    					byProperty: key,
    					fn: byObj
    				};
    			} else {
    				throw new Error(
    					`${key} and ${dynamicInfo.byProperty} when both are functions is not supported`
    				);
    			}
    		} else {
    			const entry = getInfo(key);
    			entry.base = obj[key];
    		}
    	} else {
    		const entry = getInfo(key);
    		entry.base = obj[key];
    	}
    }	
    
    1. 处理按属性分组的数据:如果属性名以 “by” 开头,表示该属性下的对象是按"by"开头的属性分组的数据。
      if (key.startsWith("by")) {
      		const byProperty = key; // 获取赋值当前 obj 的key给byProperty
      		const byObj = obj[byProperty]; // 并获取当前key对应的属性值赋值给byObj
      		if (typeof byObj === "object") { // 判断 byObj 的类型
      			for (const byValue of Object.keys(byObj)) { // 遍历对象
      				const obj = byObj[byValue]; // 获取对象内的值,这里的obj也是对象
      				for (const key of Object.keys(obj)) {
      					const entry = getInfo(key); // 通过 key 获取在info中对应的值
      					if (entry.byProperty === undefined) { // 如果为undefined
      						entry.byProperty = byProperty;
      						entry.byValues = new Map(); 
      					} else if (entry.byProperty !== byProperty) { // 两个不相同是不支持的
      						throw new Error(
      							`${byProperty} and ${entry.byProperty} for a single property is not supported`
      						);
      					}
      					entry.byValues.set(byValue, obj[key]); 
      					if (byValue === "default") { // 为 default时
      						for (const otherByValue of Object.keys(byObj)) {
      							if (!entry.byValues.has(otherByValue)) // 判断当前entry下的byValues 中是否存在 当前otherByValue
      								entry.byValues.set(otherByValue, undefined);//存在时,设置为undefined
      						}
      					}
      				}
      			}
      		} 
      		} else {
      			const entry = getInfo(key);
      			entry.base = obj[key];
      		}
      	}
      
      这里byProperty、byObj、byValue和obj几个变量比较相近,如果没有实际案例作为参照不太好理解。下面做一个参照例子
    2. 处理动态信息:如果分组数据是一个函数,将其视为动态信息并处理。
      else if (typeof byObj === "function") { // 判断是否为函数
      	if (dynamicInfo === undefined) {
      		dynamicInfo = { // 设置动态信息
      			byProperty: key,
      			fn: byObj
      		};
      	} 
      	}
      
    3. 处理基础属性:如果属性名不以 “by” 开头,将其视为基础属性并存储其值。
      else {
      	const entry = getInfo(key);
      	entry.base = obj[key];
      }
      
    4. 错误处理:如果遇到不支持的情况,例如一个属性对应多个分组或多个函数,抛出错误。
      	else {
      		throw new Error(
      			`${key} and ${dynamicInfo.byProperty} when both are functions is not supported`
      		);
      
  4. 返回解析结果:将解析后的静态信息(info)和动态信息(dynamicInfo)作为结果返回。
    return {
    	static: info,
    	dynamic: dynamicInfo
    };
    

案例分析

let resolve = {
    byDependency: {
        esm: {
            mainFields: ['browser', 'module'],
        },
        commonjs: {
            aliasFields: ['browser'],
        },
        url: {
            preferRelative: true,
        },
    }
}
console.dir(parseObject(resolve), { depth: 10 });

/**
 * @param {object} obj the object
 * @returns {ParsedObject} parsed object
 */
const parseObject = obj => {
	const info = new Map();
	let dynamicInfo;
	const getInfo = p => {
		const entry = info.get(p);
		if (entry !== undefined) return entry;
		const newEntry = {
			base: undefined,
			byProperty: undefined,
			byValues: undefined
		};
		info.set(p, newEntry);
		return newEntry;
	};
	for (const key of Object.keys(obj)) {
		if (key.startsWith("by")) {
			const byProperty = key;
			const byObj = obj[byProperty];
			if (typeof byObj === "object") {
				for (const byValue of Object.keys(byObj)) {
					const obj = byObj[byValue];
					for (const key of Object.keys(obj)) {
						const entry = getInfo(key);
						if (entry.byProperty === undefined) {
							entry.byProperty = byProperty;
							entry.byValues = new Map();
						} else if (entry.byProperty !== byProperty) {
							throw new Error(
								`${byProperty} and ${entry.byProperty} for a single property is not supported`
							);
						}
						entry.byValues.set(byValue, obj[key]);
						if (byValue === "default") {
							for (const otherByValue of Object.keys(byObj)) {
								if (!entry.byValues.has(otherByValue))
									entry.byValues.set(otherByValue, undefined);
							}
						}
					}
				}
			} else if (typeof byObj === "function") {
				if (dynamicInfo === undefined) {
					dynamicInfo = {
						byProperty: key,
						fn: byObj
					};
				} else {
					throw new Error(
						`${key} and ${dynamicInfo.byProperty} when both are functions is not supported`
					);
				}
			} else {
				const entry = getInfo(key);
				entry.base = obj[key];
			}
		} else {
			const entry = getInfo(key);
			entry.base = obj[key];
		}
	}
	return {
		static: info,
		dynamic: dynamicInfo
	};
};

三、cachedParseObject 函数

函数功能

主要功能是解析给定的对象并缓存结果以提高后续解析性能

函数分析

  1. 查找缓存:函数首先尝试从一个名为 parseCache 的缓存中获取对象的解析结果。
    const parseCache = new WeakMap();
    const entry = parseCache.get(obj);
    if (entry !== undefined) return entry;
    
  2. 解析对象:如果缓存中没有找到解析结果,它会调用 parseObject 函数来解析对象。
    const result = parseObject(obj);
    
  3. 更新缓存:解析后的结果会被存储在 parseCache 中,以便将来相同的对象可以直接从缓存中获取解析结果,而不需要重新解析。
    parseCache.set(obj, result);
    

源码

const parseCache = new WeakMap();
/**
 * @param {object} obj the object
 * @returns {ParsedObject} parsed object
 */
const cachedParseObject = obj => {
	const entry = parseCache.get(obj);
	if (entry !== undefined) return entry;
	const result = parseObject(obj);
	parseCache.set(obj, result);
	return result;
};

四、cachedCleverMerge 函数

函数功能

主要功能是合并两个给定的对象,并且使用缓存来存储合并结果,以便在将来传入相同的对象时可以直接返回缓存的结果,而不需要重新计算。

函数前半部分判断和cleverMerge 函数中的一致,如下

if (second === undefined) return first;
if (first === undefined) return second;
if (typeof second !== "object" || second === null) return second;
if (typeof first !== "object" || first === null) return first;

函数分析

  1. 缓存检索:使用 WeakMap 对象 mergeCache 来存储之前合并的结果。首先检查 mergeCache 中是否已经有针对第一个参数 first 的缓存。
    const mergeCache = new WeakMap();
    let innerCache = mergeCache.get(first);
    
  2. 创建新缓存:如果没有找到缓存,则为 first 创建一个新的 WeakMap 缓存,并存储在 mergeCache 中。
    if (innerCache === undefined) {
    	innerCache = new WeakMap();
    	mergeCache.set(first, innerCache);
    }
    
  3. 获取缓存结果:尝试从 innerCache 中获取第二个参数 second 对应的缓存结果。
    const prevMerge = innerCache.get(second);
    
  4. 返回缓存结果:如果缓存结果存在,则直接返回该结果。
    if (prevMerge !== undefined) return prevMerge;
    
  5. 合并对象:如果缓存中没有找到对应的结果,调用 _cleverMerge 函数合并两个对象,并将结果缓存。
    const newMerge = _cleverMerge(first, second, true);
    innerCache.set(second, newMerge);
    
  6. 返回合并结果:返回合并后的新对象。
    return newMerge;
    

源码

const mergeCache = new WeakMap();
const cachedCleverMerge = (first, second) => {
	if (second === undefined) return first;
	if (first === undefined) return second;
	if (typeof second !== "object" || second === null) return second;
	if (typeof first !== "object" || first === null) return first;

	let innerCache = mergeCache.get(first);
	if (innerCache === undefined) {
		innerCache = new WeakMap();
		mergeCache.set(first, innerCache);
	}
	const prevMerge = innerCache.get(second);
	if (prevMerge !== undefined) return prevMerge;
	const newMerge = _cleverMerge(first, second, true);
	innerCache.set(second, newMerge);
	return newMerge;
};

五、getValueType 函数

函数功能

该函数的功能是判断并返回传入值的类型。它根据传入不同的值。返回不同类型。

函数分析

  1. 传入为 undefined,返回为VALUE_TYPE_UNDEFINED(0)
    if (value === undefined) {
       return VALUE_TYPE_UNDEFINED;
    }
    
  2. 传入为DELETE(Symbol(“DELETE”)),返回为VALUE_TYPE_DELETE(4)
    else if (value === DELETE) {
    	return VALUE_TYPE_DELETE;
    }
    
  3. 传入为数组
    1. 当数组中有‘…’字符时,返回VALUE_TYPE_ARRAY_EXTEND(2)
    2. 其余情况直接返回VALUE_TYPE_ATOM(1)
          else if (Array.isArray(value)) {
       	if (value.lastIndexOf("...") !== -1) return VALUE_TYPE_ARRAY_EXTEND;
       	return VALUE_TYPE_ATOM;
       }
      
  4. 传入对象,返回VALUE_TYPE_OBJECT(3)
    else if (
    	typeof value === "object" &&
    	value !== null &&
    	(!value.constructor || value.constructor === Object)
    ) {
    	return VALUE_TYPE_OBJECT;
    }
    
  5. 其余情况,返回

源码

const VALUE_TYPE_UNDEFINED = 0;
const VALUE_TYPE_ATOM = 1;
const VALUE_TYPE_ARRAY_EXTEND = 2;
const VALUE_TYPE_OBJECT = 3;
const VALUE_TYPE_DELETE = 4;

/**
 * @param {any} value a single value
 * @returns {VALUE_TYPE_UNDEFINED | VALUE_TYPE_ATOM | VALUE_TYPE_ARRAY_EXTEND | VALUE_TYPE_OBJECT | VALUE_TYPE_DELETE} value type
 */
const getValueType = value => {
	if (value === undefined) {
		return VALUE_TYPE_UNDEFINED;
	} else if (value === DELETE) {
		return VALUE_TYPE_DELETE;
	} else if (Array.isArray(value)) {
		if (value.lastIndexOf("...") !== -1) return VALUE_TYPE_ARRAY_EXTEND;
		return VALUE_TYPE_ATOM;
	} else if (
		typeof value === "object" &&
		value !== null &&
		(!value.constructor || value.constructor === Object)
	) {
		return VALUE_TYPE_OBJECT;
	}
	return VALUE_TYPE_ATOM;
};

六、mergeEntries 函数

函数功能

mergeEntries 函数的主要功能是合并两个对象,这些对象可能包含基础值、按属性分类的值,以及按值分类的映射

函数分析

  1. 类型判断:根据 secondEntry.base 的值类型,决定合并策略。
    switch (getValueType(secondEntry.base)) {
    
    }
    
  2. 当为VALUE_TYPE_ATOM或VALUE_TYPE_DELETE,直接返回secondEntry
    case VALUE_TYPE_ATOM:
    case VALUE_TYPE_DELETE:
    	// No need to consider firstEntry at all
    	// second value override everything
    	// = second.base + second.byProperty
    	return secondEntry;
    
  3. 当为VALUE_TYPE_UNDEFINED
    1. 并且firstEntry.byProperty没有值时,合并firstEntry和secondEntry值,返回新对象
      if (!firstEntry.byProperty) {
      		// = first.base + second.byProperty
      		return {
      			base: firstEntry.base,
      			byProperty: secondEntry.byProperty,
      			byValues: secondEntry.byValues
      		};
      	} 
      
    2. 如果 firstEntry 和 secondEntry 的按属性分类的标识不匹配,则抛出错误
      else if (firstEntry.byProperty !== secondEntry.byProperty) {
      		throw new Error(
      			`${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported`
      		);
      	}
      
    3. 其余情况处理
      else {
       		// = first.base + (first.byProperty + second.byProperty)
       		// need to merge first and second byValues
       		const newByValues = new Map(firstEntry.byValues);
       		for (const [key, value] of secondEntry.byValues) {
       			const firstValue = getFromByValues(firstEntry.byValues, key);
       			newByValues.set(
       				key,
       				mergeSingleValue(firstValue, value, internalCaching)
       			);
       		}
       		return {
       			base: firstEntry.base,
       			byProperty: firstEntry.byProperty,
       			byValues: newByValues
       		};
       	}
      
  4. 默认情况处理
    default: {
    	if (!firstEntry.byProperty) {
    		// The simple case
    		// = (first.base + second.base) + second.byProperty
    		return {
    			base: mergeSingleValue(
    				firstEntry.base,
    				secondEntry.base,
    				internalCaching
    			),
    			byProperty: secondEntry.byProperty,
    			byValues: secondEntry.byValues
    		};
    	}
    	let newBase;
    	const intermediateByValues = new Map(firstEntry.byValues);
    	for (const [key, value] of intermediateByValues) {
    		intermediateByValues.set(
    			key,
    			mergeSingleValue(value, secondEntry.base, internalCaching)
    		);
    	}
    	if (
    		Array.from(firstEntry.byValues.values()).every(value => {
    			const type = getValueType(value);
    			return type === VALUE_TYPE_ATOM || type === VALUE_TYPE_DELETE;
    		})
    	) {
    		// = (first.base + second.base) + ((first.byProperty + second.base) + second.byProperty)
    		newBase = mergeSingleValue(
    			firstEntry.base,
    			secondEntry.base,
    			internalCaching
    		);
    	} else {
    		// = first.base + ((first.byProperty (+default) + second.base) + second.byProperty)
    		newBase = firstEntry.base;
    		if (!intermediateByValues.has("default"))
    			intermediateByValues.set("default", secondEntry.base);
    	}
    	if (!secondEntry.byProperty) {
    		// = first.base + (first.byProperty + second.base)
    		return {
    			base: newBase,
    			byProperty: firstEntry.byProperty,
    			byValues: intermediateByValues
    		};
    	} else if (firstEntry.byProperty !== secondEntry.byProperty) {
    		throw new Error(
    			`${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported`
    		);
    	}
    	const newByValues = new Map(intermediateByValues);
    	for (const [key, value] of secondEntry.byValues) {
    		const firstValue = getFromByValues(intermediateByValues, key);
    		newByValues.set(
    			key,
    			mergeSingleValue(firstValue, value, internalCaching)
    		);
    	}
    	return {
    		base: newBase,
    		byProperty: firstEntry.byProperty,
    		byValues: newByValues
    	};
    }
    

源码

/**
 * @param {ObjectParsedPropertyEntry} firstEntry a
 * @param {ObjectParsedPropertyEntry} secondEntry b
 * @param {boolean} internalCaching should parsing of objects and nested merges be cached
 * @returns {ObjectParsedPropertyEntry} new entry
 */
const mergeEntries = (firstEntry, secondEntry, internalCaching) => {
	switch (getValueType(secondEntry.base)) {
		case VALUE_TYPE_ATOM:
		case VALUE_TYPE_DELETE:
			// No need to consider firstEntry at all
			// second value override everything
			// = second.base + second.byProperty
			return secondEntry;
		case VALUE_TYPE_UNDEFINED:
			if (!firstEntry.byProperty) {
				// = first.base + second.byProperty
				return {
					base: firstEntry.base,
					byProperty: secondEntry.byProperty,
					byValues: secondEntry.byValues
				};
			} else if (firstEntry.byProperty !== secondEntry.byProperty) {
				throw new Error(
					`${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported`
				);
			} else {
				// = first.base + (first.byProperty + second.byProperty)
				// need to merge first and second byValues
				const newByValues = new Map(firstEntry.byValues);
				for (const [key, value] of secondEntry.byValues) {
					const firstValue = getFromByValues(firstEntry.byValues, key);
					newByValues.set(
						key,
						mergeSingleValue(firstValue, value, internalCaching)
					);
				}
				return {
					base: firstEntry.base,
					byProperty: firstEntry.byProperty,
					byValues: newByValues
				};
			}
		default: {
			if (!firstEntry.byProperty) {
				// The simple case
				// = (first.base + second.base) + second.byProperty
				return {
					base: mergeSingleValue(
						firstEntry.base,
						secondEntry.base,
						internalCaching
					),
					byProperty: secondEntry.byProperty,
					byValues: secondEntry.byValues
				};
			}
			let newBase;
			const intermediateByValues = new Map(firstEntry.byValues);
			for (const [key, value] of intermediateByValues) {
				intermediateByValues.set(
					key,
					mergeSingleValue(value, secondEntry.base, internalCaching)
				);
			}
			if (
				Array.from(firstEntry.byValues.values()).every(value => {
					const type = getValueType(value);
					return type === VALUE_TYPE_ATOM || type === VALUE_TYPE_DELETE;
				})
			) {
				// = (first.base + second.base) + ((first.byProperty + second.base) + second.byProperty)
				newBase = mergeSingleValue(
					firstEntry.base,
					secondEntry.base,
					internalCaching
				);
			} else {
				// = first.base + ((first.byProperty (+default) + second.base) + second.byProperty)
				newBase = firstEntry.base;
				if (!intermediateByValues.has("default"))
					intermediateByValues.set("default", secondEntry.base);
			}
			if (!secondEntry.byProperty) {
				// = first.base + (first.byProperty + second.base)
				return {
					base: newBase,
					byProperty: firstEntry.byProperty,
					byValues: intermediateByValues
				};
			} else if (firstEntry.byProperty !== secondEntry.byProperty) {
				throw new Error(
					`${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported`
				);
			}
			const newByValues = new Map(intermediateByValues);
			for (const [key, value] of secondEntry.byValues) {
				const firstValue = getFromByValues(intermediateByValues, key);
				newByValues.set(
					key,
					mergeSingleValue(firstValue, value, internalCaching)
				);
			}
			return {
				base: newBase,
				byProperty: firstEntry.byProperty,
				byValues: newByValues
			};
		}
	}
};

因篇幅问题不能全部显示,请点此查看更多更全内容

Top