mirror of https://github.com/webpack/webpack.git
allow function in byProperty configurations
This commit is contained in:
parent
67d2e227f4
commit
b067bad545
|
|
@ -10,6 +10,7 @@ const mergeCache = new WeakMap();
|
|||
/** @type {WeakMap<object, Map<string, Map<string|number|boolean, object>>>} */
|
||||
const setPropertyCache = new WeakMap();
|
||||
const DELETE = Symbol("DELETE");
|
||||
const DYNAMIC_INFO = Symbol("cleverMerge dynamic info");
|
||||
|
||||
/**
|
||||
* Merges two given objects and caches the result to avoid computation if same objects passed as arguments again.
|
||||
|
|
@ -81,12 +82,18 @@ const cachedSetProperty = (obj, property, value) => {
|
|||
* @property {Map<string, any>} byValues value depending on selector property, merged with base
|
||||
*/
|
||||
|
||||
/** @type {WeakMap<object, Map<string, ObjectParsedPropertyEntry>>} */
|
||||
/**
|
||||
* @typedef {Object} ParsedObject
|
||||
* @property {Map<string, ObjectParsedPropertyEntry>} static static properties (key is property name)
|
||||
* @property {{ byProperty: string, fn: Function } | undefined} dynamic dynamic part
|
||||
*/
|
||||
|
||||
/** @type {WeakMap<object, ParsedObject>} */
|
||||
const parseCache = new WeakMap();
|
||||
|
||||
/**
|
||||
* @param {object} obj the object
|
||||
* @returns {Map<string, ObjectParsedPropertyEntry>} parsed properties
|
||||
* @returns {ParsedObject} parsed object
|
||||
*/
|
||||
const cachedParseObject = obj => {
|
||||
const entry = parseCache.get(obj);
|
||||
|
|
@ -98,10 +105,11 @@ const cachedParseObject = obj => {
|
|||
|
||||
/**
|
||||
* @param {object} obj the object
|
||||
* @returns {Map<string, ObjectParsedPropertyEntry>} parsed properties
|
||||
* @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;
|
||||
|
|
@ -117,40 +125,60 @@ const parseObject = obj => {
|
|||
if (key.startsWith("by")) {
|
||||
const byProperty = key;
|
||||
const byObj = obj[byProperty];
|
||||
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);
|
||||
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 info;
|
||||
return {
|
||||
static: info,
|
||||
dynamic: dynamicInfo
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Map<string, ObjectParsedPropertyEntry>} info property entries
|
||||
* @param {Map<string, ObjectParsedPropertyEntry>} info static properties (key is property name)
|
||||
* @param {{ byProperty: string, fn: Function } | undefined} dynamicInfo dynamic part
|
||||
* @returns {object} the object
|
||||
*/
|
||||
const serializeObject = info => {
|
||||
const serializeObject = (info, dynamicInfo) => {
|
||||
const obj = {};
|
||||
// Setup byProperty structure
|
||||
for (const entry of info.values()) {
|
||||
|
|
@ -174,6 +202,9 @@ const serializeObject = info => {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (dynamicInfo !== undefined) {
|
||||
obj[dynamicInfo.byProperty] = dynamicInfo.fn;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
|
@ -216,28 +247,53 @@ const getValueType = value => {
|
|||
const cleverMerge = (first, second, internalCaching = false) => {
|
||||
if (second === undefined) return first;
|
||||
if (first === undefined) return second;
|
||||
const firstInfo = internalCaching
|
||||
const firstObject = internalCaching
|
||||
? cachedParseObject(first)
|
||||
: parseObject(first);
|
||||
const secondInfo = internalCaching
|
||||
const { static: firstInfo, dynamic: firstDynamicInfo } = firstObject;
|
||||
|
||||
// If the first argument has a dynamic part we modify the dynamic part to merge the second argument
|
||||
if (firstDynamicInfo !== undefined) {
|
||||
let { byProperty, fn } = firstDynamicInfo;
|
||||
const fnInfo = fn[DYNAMIC_INFO];
|
||||
if (fnInfo) {
|
||||
second = internalCaching
|
||||
? cachedCleverMerge(fnInfo[1], second)
|
||||
: cleverMerge(fnInfo[1], second, false);
|
||||
fn = fnInfo[0];
|
||||
}
|
||||
const newFn = (...args) => {
|
||||
const fnResult = fn(...args);
|
||||
if (typeof fnResult !== "object" || fnResult === null) return fnResult;
|
||||
return internalCaching
|
||||
? cachedCleverMerge(fnResult, second)
|
||||
: cleverMerge(fnResult, second, false);
|
||||
};
|
||||
newFn[DYNAMIC_INFO] = [fn, second];
|
||||
return serializeObject(firstObject.static, { byProperty, fn: newFn });
|
||||
}
|
||||
|
||||
// If the first part is static only, we merge the static parts and keep the dynamic part of the second argument
|
||||
const secondObject = internalCaching
|
||||
? cachedParseObject(second)
|
||||
: parseObject(second);
|
||||
const { static: secondInfo, dynamic: secondDynamicInfo } = secondObject;
|
||||
/** @type {Map<string, ObjectParsedPropertyEntry>} */
|
||||
const result = new Map();
|
||||
const resultInfo = new Map();
|
||||
for (const [key, firstEntry] of firstInfo) {
|
||||
const secondEntry = secondInfo.get(key);
|
||||
const entry =
|
||||
secondEntry !== undefined
|
||||
? mergeEntries(firstEntry, secondEntry, internalCaching)
|
||||
: firstEntry;
|
||||
result.set(key, entry);
|
||||
resultInfo.set(key, entry);
|
||||
}
|
||||
for (const [key, secondEntry] of secondInfo) {
|
||||
if (!firstInfo.has(key)) {
|
||||
result.set(key, secondEntry);
|
||||
resultInfo.set(key, secondEntry);
|
||||
}
|
||||
}
|
||||
return serializeObject(result);
|
||||
return serializeObject(resultInfo, secondDynamicInfo);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -469,12 +525,12 @@ const resolveByProperty = (obj, byProperty, ...values) => {
|
|||
return remaining;
|
||||
}
|
||||
} else if (typeof byValue === "function") {
|
||||
const result = resolveByProperty(
|
||||
byValue.apply(null, values),
|
||||
byProperty,
|
||||
...values
|
||||
const result = byValue.apply(null, values);
|
||||
if (typeof result !== "object" || result === null) return result;
|
||||
return cleverMerge(
|
||||
remaining,
|
||||
resolveByProperty(result, byProperty, ...values)
|
||||
);
|
||||
return cleverMerge(remaining, result);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
const {
|
||||
cleverMerge,
|
||||
DELETE,
|
||||
removeOperations
|
||||
removeOperations,
|
||||
resolveByProperty
|
||||
} = require("../lib/util/cleverMerge");
|
||||
|
||||
describe("cleverMerge", () => {
|
||||
|
|
@ -517,12 +518,144 @@ describe("cleverMerge", () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
dynamicSecond: [
|
||||
{
|
||||
a: 4, // keep
|
||||
b: 5, // static override
|
||||
c: 6 // dynamic override
|
||||
},
|
||||
{
|
||||
b: 50,
|
||||
y: 20,
|
||||
byArguments: (x, y, z) => ({
|
||||
c: 60,
|
||||
x,
|
||||
y,
|
||||
z
|
||||
})
|
||||
},
|
||||
{
|
||||
a: 4,
|
||||
b: 50,
|
||||
c: 60,
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: 3
|
||||
}
|
||||
],
|
||||
dynamicBoth: [
|
||||
{
|
||||
a: 4, // keep
|
||||
b: 5, // static override
|
||||
c: 6, // dynamic override
|
||||
byArguments: (x, y, z) => ({
|
||||
x, // keep
|
||||
y, // static override
|
||||
z // dynamic override
|
||||
})
|
||||
},
|
||||
{
|
||||
b: 50,
|
||||
y: 20,
|
||||
byArguments: (x, y, z) => ({
|
||||
c: 60,
|
||||
z: z * 10
|
||||
})
|
||||
},
|
||||
{
|
||||
a: 4,
|
||||
b: 50,
|
||||
c: 60,
|
||||
x: 1,
|
||||
y: 20,
|
||||
z: 30
|
||||
}
|
||||
],
|
||||
dynamicChained: [
|
||||
cleverMerge(
|
||||
{
|
||||
a: 6, // keep
|
||||
b: 7, // static override
|
||||
c: 8, // dynamic override
|
||||
d: 9, // static override (3rd)
|
||||
e: 10, // dynamic override (3rd)
|
||||
byArguments: (x, y, z, v, w) => ({
|
||||
x, // keep
|
||||
y, // static override
|
||||
z, // dynamic override
|
||||
v, // static override (3rd)
|
||||
w // dynamic override (3rd)
|
||||
})
|
||||
},
|
||||
{
|
||||
b: 70,
|
||||
y: 20,
|
||||
byArguments: (x, y, z) => ({
|
||||
c: 80,
|
||||
z: z * 10
|
||||
})
|
||||
}
|
||||
),
|
||||
{
|
||||
d: 90,
|
||||
v: 40,
|
||||
byArguments: (x, y, z, v, w) => ({
|
||||
e: 100,
|
||||
w: w * 10
|
||||
})
|
||||
},
|
||||
{
|
||||
a: 6,
|
||||
b: 70,
|
||||
c: 80,
|
||||
d: 90,
|
||||
e: 100,
|
||||
x: 1,
|
||||
y: 20,
|
||||
z: 30,
|
||||
v: 40,
|
||||
w: 50
|
||||
}
|
||||
],
|
||||
dynamicFalse1: [
|
||||
{
|
||||
a: 1,
|
||||
byArguments: () => false
|
||||
},
|
||||
{
|
||||
b: 2
|
||||
},
|
||||
false
|
||||
],
|
||||
dynamicFalse2: [
|
||||
{
|
||||
a: 1
|
||||
},
|
||||
{
|
||||
b: 2,
|
||||
byArguments: () => false
|
||||
},
|
||||
false
|
||||
],
|
||||
dynamicFalse3: [
|
||||
{
|
||||
a: 1,
|
||||
byArguments: () => false
|
||||
},
|
||||
{
|
||||
b: 2,
|
||||
byArguments: () => false
|
||||
},
|
||||
false
|
||||
]
|
||||
};
|
||||
for (const key of Object.keys(cases)) {
|
||||
const testCase = cases[key];
|
||||
it(`should merge ${key} correctly`, () => {
|
||||
expect(cleverMerge(testCase[0], testCase[1])).toEqual(testCase[2]);
|
||||
let merged = cleverMerge(testCase[0], testCase[1]);
|
||||
merged = resolveByProperty(merged, "byArguments", 1, 2, 3, 4, 5);
|
||||
expect(merged).toEqual(testCase[2]);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue