UNPKG

@merkur/core

Version:

Merkur is tiny and extensible library for creating front-end microservices.

254 lines (206 loc) 5.77 kB
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 {}; } export { assignMissingKeys, bindWidgetToFunctions, createMerkur, createMerkurWidget, defineWidget, getMerkur, hookMethod, isFunction, removeMerkur, setDefaultValueForUndefined };