@merkur/core
Version:
Merkur is tiny and extensible library for creating front-end microservices.
218 lines (212 loc) • 5.88 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;