@miyagi/core
Version:
miyagi is a component development tool for JavaScript template engines.
206 lines (178 loc) • 4.54 kB
JavaScript
import deepMerge from "deepmerge";
import * as helpers from "../helpers.js";
import { resolveRefs } from "./resolve/ref.js";
import { resolveTpls } from "./resolve/tpl.js";
import { extendTemplateData } from "../render/helpers.js";
/**
* @param {string} method
* @returns {Function}
*/
function getMergeMethod(method) {
const methods = {
combine: (target, source, options) => {
const destination = target.slice();
source.forEach((item, index) => {
if (options.isMergeableObject(item)) {
if (typeof destination[index] === "undefined") {
destination[index] = options.cloneUnlessOtherwiseSpecified(
item,
options,
);
} else {
destination[index] = deepMerge(target[index], item, options);
}
} else {
destination[index] = options.cloneUnlessOtherwiseSpecified(
item,
options,
);
}
});
return destination;
},
overwrite: (destinationArray, sourceArray) => sourceArray,
};
return methods[method];
}
/**
* @param {object} data - the mock data object that will be passed into the component
* @param {object} component
* @param {object} [rootData] - the root mock data object
* @returns {Promise<{ merged, resolved, messages }>} the resolved data object
*/
export const resolveData = async function (data, component, rootData) {
const mergedWithGlobalData = mergeWithGlobalData(
rootData ? mergeRootDataWithVariationData(rootData, data) : data,
);
const { data: refsResolved, messages: refMessages } = await resolveRefs(
{ ...mergedWithGlobalData },
component,
);
const extended = await extendTemplateData(
global.config,
refsResolved,
component,
);
const { data: tplsResolved, messages: tplMessages } =
await resolveTpls(extended);
const resolved = overwriteRenderKey(tplsResolved);
return {
merged: mergedWithGlobalData,
resolved,
messages: [...refMessages, ...tplMessages],
};
};
/**
* @param {object} data - the mock data object that will be passed into the component
* @returns {object} the resolved data object
*/
export const overwriteRenderKey = function (data) {
let o;
if (
["string", "number", "boolean"].includes(typeof data) ||
data instanceof Map
) {
return data;
}
if (Array.isArray(data)) {
for (let item of data) {
item = overwriteRenderKey(item);
}
return data;
}
if (data) {
o = { ...data };
const entries = Object.entries(o);
for (const [key, val] of entries) {
if (key === "$render") {
let str = "";
if (val) {
for (const html of val) {
str += html;
}
}
o = str;
} else {
if (
typeof val == "string" ||
typeof val === "number" ||
typeof val === "boolean" ||
val instanceof Map ||
val === null
) {
o[key] = val;
} else if (Array.isArray(val)) {
val.forEach((v, i) => {
if (
typeof v == "string" ||
typeof v === "number" ||
typeof v === "boolean" ||
v instanceof Map ||
v === null
) {
o[key][i] = v;
} else {
o[key][i] = overwriteRenderKey(v);
}
});
} else {
o[key] = overwriteRenderKey(val);
}
}
}
}
return o;
};
/**
* @param {object} rootData - the root mock data of a component
* @param {object} variationData - a variation mock data of a component
* @returns {object} the merged data
*/
export const mergeRootDataWithVariationData = function (
rootData,
variationData,
) {
if (!rootData) {
return variationData;
}
if (!variationData) {
return rootData;
}
const merged = deepMerge(rootData, variationData, {
customMerge: (key) => {
const options = variationData.$opts || rootData.$opts;
if (options) {
const option = options[key];
if (option) {
return getMergeMethod(option);
}
}
return undefined;
},
});
if (merged.$opts) {
delete merged.$opts;
}
return merged;
};
/**
* @param {object} data - the mock data object that will be passed into the component
* @returns {object} the merged data object
*/
function mergeWithGlobalData(data) {
const defaultFile = helpers.getFullPathFromShortPath(
`${global.config.files.mocks.name}.${global.config.files.mocks.extension[0]}`,
);
const jsFile = helpers.getFullPathFromShortPath(
`${global.config.files.mocks.name}.${global.config.files.mocks.extension[1]}`,
);
const globalData = {
...(global.state.fileContents[defaultFile] ||
global.state.fileContents[jsFile]),
};
delete globalData.$defs;
return {
...globalData,
...data,
};
}