function mergeConfigFragmentArrayByKey(baseArray, extensionArray, keyFn) {
  /** @type {Map<string, {index: number, item: T}>} */
  const itemsByKey = new Map();
  const mergedArray = baseArray || [];
  for (let i = 0; i < mergedArray.length; i++) {
    const item = mergedArray[i];
    itemsByKey.set(keyFn(item), {index: i, item});
  }

  for (const item of extensionArray || []) {
    const baseItemEntry = itemsByKey.get(keyFn(item));
    if (baseItemEntry) {
      const baseItem = baseItemEntry.item;
      const merged = typeof item === 'object' && typeof baseItem === 'object' ?
        mergeConfigFragment(baseItem, item, true) :
        item;
      mergedArray[baseItemEntry.index] = merged;
    } else {
      mergedArray.push(item);
    }
  }

  return mergedArray;
}
Merge an array of items by a caller-defined key. `mergeConfigFragment` is used to merge any items
with a matching key.

@template {Record<string, any>} T
@param {Array<T>|null|undefined} baseArray
@param {Array<T>|null|undefined} extensionArray
@param {(item: T) => string} keyFn
@return {Array<T>}