@merkur/core
Version:
Merkur is tiny and extensible library for creating front-end microservices.
265 lines (216 loc) • 6.04 kB
JavaScript
;
function createBoundedFunction(widget, originalFunction) {
return (...rest) => {
return originalFunction(widget, ...rest);
};
}
// @TODO in future use structuredClone instead of JSON.parse
function clone(object) {
try {
return JSON.parse(JSON.stringify(object));
} catch (_) {
return object;
}
}
function setDefaultValueForUndefined(object, keys, value = {}) {
let objectClone = { ...object };
keys.forEach((key) => {
objectClone[key] = objectClone[key] || clone(value);
});
return objectClone;
}
function bindWidgetToFunctions(widget, target) {
target = target || widget;
Object.keys(target).forEach((key) => {
if (isFunction(target[key])) {
let originalFunction = target[key];
target[key] = createBoundedFunction(widget, originalFunction);
}
});
}
function hookMethod(widget, path, handler) {
const { target, methodName } = parsePath(widget, path);
const originalFunction = createBoundedFunction(widget, target[methodName]);
target[methodName] = function (widget, ...rest) {
return handler(widget, originalFunction, ...rest);
};
return originalFunction;
}
function parsePath(widget, path = '') {
const paths = path.split('.');
const methodName = paths.pop();
const target = paths.reduce((target, path) => target[path] || {}, widget);
if (!isFunction(target[methodName])) {
throw new Error(
`Defined path '${path}' is incorrect. Check your widget structure.`,
);
}
return { target, methodName };
}
function isFunction(value) {
return typeof value === 'function';
}
function assignMissingKeys(target, ...sources) {
sources.forEach((source) => {
Object.keys(source || {}).forEach((key) => {
if (!(key in target)) {
target[key] = source[key];
}
});
});
return target;
}
async function callPluginMethod(widget, method, args) {
for (const plugin of widget.$plugins) {
if (isFunction(plugin[method])) {
widget = await plugin[method](widget, ...args);
}
}
return widget;
}
/**
* Typed helper to make it easier to define widget properties.
*
* @type import('@merkur/core').defineWidget
*/
function defineWidget(widgetDefinition) {
return widgetDefinition;
}
function setDefinitionDefaults(widgetDefinition) {
return {
...widgetDefinition,
...setDefaultValueForUndefined(
widgetDefinition,
['containerSelector'],
null,
),
...setDefaultValueForUndefined(widgetDefinition, [
'slot',
'$dependencies',
'$external',
]),
...setDefaultValueForUndefined(
widgetDefinition,
['setup', 'create'],
(widget) => widget,
),
};
}
/**
* @type import('@merkur/core').createMerkurWidget
*/
async function createMerkurWidget(widgetDefinition = {}) {
const definition = setDefinitionDefaults(widgetDefinition);
const { setup, create } = definition;
let widget = {
async setup(widget, ...rest) {
widget = await callPluginMethod(widget, 'setup', rest);
return setup(widget, ...rest);
},
async create(widget, ...rest) {
widget = await callPluginMethod(widget, 'create', rest);
return create(widget, ...rest);
},
$plugins: (definition.$plugins || []).map((pluginFactory) =>
pluginFactory(),
),
};
// TODO refactoring
widget.name = definition.name;
widget.version = definition.version;
widget.$dependencies = definition.$dependencies;
widget.$external = definition.$external;
widget.$in = {};
delete definition.name;
delete definition.version;
delete definition.$dependencies;
delete definition.$external;
delete definition.$plugins;
delete definition.setup;
delete definition.create;
widget = await widget.setup(widget, definition);
widget = await widget.create(widget, definition);
bindWidgetToFunctions(widget);
Object.seal(widget);
return widget;
}
function register({ name, version, createWidget }) {
const merkur = getMerkur();
merkur.$in.widgetFactory[name + version] = createWidget;
}
function create(widgetProperties = {}) {
const merkur = getMerkur();
const { name, version } = widgetProperties;
const factory = merkur.$in.widgetFactory[name + version];
if (!isFunction(factory)) {
throw new Error(
`The widget with defined name and version "${
name + version
}" is not defined.`,
);
}
return factory(widgetProperties);
}
function createMerkur({ $plugins = [] } = {}) {
const merkur = getMerkur();
$plugins.forEach((plugin) => {
if (plugin && isFunction(plugin.setup)) {
plugin.setup(merkur);
}
});
return merkur;
}
function removeMerkur() {
const globalContext = getGlobalContext();
delete globalContext.__merkur__;
}
function getMerkur() {
const globalContext = getGlobalContext();
if (!globalContext.__merkur__) {
globalContext.__merkur__ = {
$in: {
widgets: [],
widgetFactory: {},
},
$external: {},
$dependencies: {},
isRegistered,
register,
create,
};
}
return globalContext.__merkur__;
}
function isRegistered(name) {
const merkur = getMerkur();
return Boolean(
Object.keys(merkur.$in.widgetFactory).find((key) =>
new RegExp(`^${name}`).test(key),
),
);
}
function getGlobalContext() {
if (typeof globalThis !== 'undefined') {
return globalThis;
}
if (typeof self !== 'undefined') {
return self;
}
if (typeof window !== 'undefined') {
return window;
}
if (typeof global !== 'undefined') {
return global;
}
return {};
}
exports.assignMissingKeys = assignMissingKeys;
exports.bindWidgetToFunctions = bindWidgetToFunctions;
exports.createMerkur = createMerkur;
exports.createMerkurWidget = createMerkurWidget;
exports.defineWidget = defineWidget;
exports.getMerkur = getMerkur;
exports.hookMethod = hookMethod;
exports.isFunction = isFunction;
exports.removeMerkur = removeMerkur;
exports.setDefaultValueForUndefined = setDefaultValueForUndefined;