我正在编写一个相当简约的配置系统.这个 idea 是有config.template.js
分和config.custom.js
分.现在,custom中的所有设置值都应覆盖template中的值.custom中缺失的值将从template中读取.
其中的逻辑似乎是这样的:
const isObject = item => item && typeof item === "object" && !Array.isArray(item);
const deepMerge = function(target, source){
if (isObject(target) && isObject(source)){
for (const key in source){
if (isObject(source[key])){
if (!target[key]) target[key] = {};
deepMerge(target[key], source[key]);
}
else target[key] = source[key];
}
}
return target;
};
// ...
const configCustom = (await import("./config.custom.js")).default;
const configBase = (await import("./config.template.js")).default;
export const config = {
...deepMerge(configBase, configCustom),
};
现在我的问题是:
VSCode对结果配置的实际外观一无所知.所以没有自动补全或键类型.
如果我只需执行以下操作,VSCode would将能够提供自动完成功能:
export const config = {
...configBase,
...configCustom,
};
然而,这会导致嵌套键的浅表副本,有效地覆盖整个对象/array.
因为我已经大量使用了JSDoc,所以我想我可以像这样注释deepMerge
函数
/**
* @param {object} target
* @param {object} source
* @return {import("./config.template.js").default}
*/
但这当然是一厢情愿的 idea ,行不通.
所以我的问题是:
如何才能在不依赖浅层副本的情况下为此配置系统提供自动完成/类型?
I know that there are a ton of config systems and this is kinda re-inventing the wheel. I still want to understand and learn.
And yes, TypeScript would make this easier.
最新情况:
@creepsore型S answer的性能非常好.但是,我不得不更改了几个注释,因为我遇到了一些重载错误
(由VSCode提出,"js/ts.implicitProjectConfig.checkJs": true
):
/**
* @template {object} T
* @template {object} T2
* @param {T} target
* @param {T2 & Partial<T>} source
* @returns {T & T2}
*/
const deepMerge = function(target, source){
if (isObject(target) && isObject(source)){
for (const key in source){
if (isObject(source[key])){
if (!target[key]) target[key] = {};
deepMerge(target[key], source[key]);
}
else target[key] = source[key];
}
}
return /** @type {T & T2} */ (target);
};
// ...
export const config = {
...deepMerge(
configBase,
/** @type {Partial<typeof configBase>} */ (configCustom),
),
};
可能不是最干净的方法,但效果非常好!