NodeJS 在JavaScript中深度合并后为自动完成保留键/值

zynd9foi  于 2023-06-29  发布在  Node.js
关注(0)|答案(1)|浏览(108)

我正在写一个相当简约的配置系统。这个想法是有一个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 * 将 * 能够提供自动完成,如果我只是做:

export const config = {
    ...configBase,
    ...configCustom,
};

然而,这会导致嵌套键的浅副本,有效地覆盖整个对象/数组,什么也没有。
由于我已经大量使用了JSDoc,我想我可以像这样注解deepMerge函数

/**
 * @param {object} target
 * @param {object} source
 * @return {import("./config.template.js").default}
 */

但这当然是一厢情愿的想法,行不通。
所以我的问题是:
如何在不依赖浅拷贝的情况下为该配置系统提供自动完成/类型?
我知道有很多配置系统,这有点像重新发明轮子。我仍然想了解和学习。
是的,TypeScript会让这变得更容易。

更新:

@creepsore的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),
    ),
};

可能不是最干净的方法,但效果非常好!

dojqjjoe

dojqjjoe1#

当你将deepMerge的两个参数定义为泛型,并将它们用作它的返回类型时,它就可以工作:

const isObject = item => item && typeof item === "object" && !Array.isArray(item);

/**
 * @template T
 * @template T2
 * @param {T} target 
 * @param {T2} 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 target;
};

const configCustom = (await import("./config.custom.js")).default;
const configBase = (await import("./config.template.js")).default;

export const config = {
    ...deepMerge(configBase, configCustom),
};

以下是我用于测试的示例配置:

// config.template.js
export default {
    a: 420,
    b: 1337,
    c: 360
};

// config.custom.js
export default {
    b: 420
};

相关问题