UNPKG

@ima/devtools-scripts

Version:

IMA.js devtool script used in the @ima/devtools.

931 lines (822 loc) 22.9 kB
'use strict'; function t() { return Date.now().toString(32).substring(5) + Math.random().toString(32).substring(2); } const AOP_PATTERN = Symbol('AopPattern'); const AOP_HOOKS = Symbol('AopHooks'); const AOP_STATIC_ALLOW = Symbol('AopStaticAllow'); const AOP_FILTER_FUNCTION = Symbol('AopFilterFunction'); function invokePattern(pattern, meta) { if (!pattern) { return; } if (Array.isArray(pattern)) { return pattern.map(rule => { return Reflect.apply(rule, rule.context, [meta]); }); } else { const method = typeof pattern === 'function' ? pattern : pattern.method; return Reflect.apply(method, pattern.context, [meta]); } } function isConstructable(func) { return !!(func && func.prototype && func.prototype.constructor); } function createCallTrap({ target, object, property, pattern, context, method }) { function callTrap(...rest) { const self = this; let payload = undefined; let meta = {}; callTrap[AOP_HOOKS].forEach(({ target, object, property, pattern, context }) => { invokePattern(pattern.beforeMethod, { target, object, property, context: context || self, args: rest, meta }); }); { const { target, object, property, pattern, context } = callTrap[AOP_HOOKS][callTrap[AOP_HOOKS].length - 1]; const aroundPattern = Array.isArray(pattern.aroundMethod) ? pattern.aroundMethod[pattern.aroundMethod.length - 1] : pattern.aroundMethod; if (aroundPattern) { payload = invokePattern(aroundPattern, { target, object, property, context: context || self, args: rest, original: typeof object[property] === 'function' ? object[property] : method, meta }); } else { const { object, property, context } = callTrap[AOP_HOOKS][callTrap[AOP_HOOKS].length - 1]; if (object && typeof object[property] === 'function') { if (property === 'constructor' && isConstructable(object)) { payload = Reflect.construct(object, rest); } else { payload = Reflect.apply(object[property], context || self, rest); } } else { payload = Reflect.apply(method, context || self, rest); } } } callTrap[AOP_HOOKS].forEach(({ target, object, property, pattern, context }) => { invokePattern(pattern.afterMethod, { target, object, property, context: context || self, args: rest, payload, meta }); }); return payload; } callTrap[AOP_HOOKS] = [{ target, object, property, pattern, context }]; return callTrap; } function createHook(name, regular, callback) { function hook(meta) { return hookFor(meta, regular, callback); } hook[AOP_FILTER_FUNCTION] = function ({ property, target, prototype }) { return typeof regular === 'string' && property.includes(regular) || regular instanceof RegExp && regular.test(property) || typeof regular === 'function' && regular({ property, target, name, prototype }); }; return { [name]: hook }; } function hookFor(meta, regular, callback) { if (typeof regular === 'string') { if (meta.property.includes(regular)) { return callback(meta); } return null; } if (regular instanceof RegExp) { if (regular.test(meta.property)) { return callback(meta); } return null; } if (typeof regular === 'function') { if (regular(meta)) { return callback(meta); } return null; } if (Array.isArray(regular)) { return regular.map(({ rule, action }) => { return hookFor(meta, rule, action || callback); }); } throw new TypeError("Invalid rule type ".concat(typeof regular, ". Method accept string, regexp, function and array.")); } function hasToRegisterHook(hooks) { return (pattern, props) => { return hooks.reduce((result, hook) => { return result || pattern[hook] && typeof pattern[hook][AOP_FILTER_FUNCTION] === 'function' && pattern[hook][AOP_FILTER_FUNCTION](props); }, false); }; } const hookName = Object.freeze({ beforeMethod: 'beforeMethod', afterMethod: 'afterMethod', aroundMethod: 'aroundMethod', beforeGetter: 'beforeGetter', afterGetter: 'afterGetter', aroundGetter: 'aroundGetter', beforeSetter: 'beforeSetter', afterSetter: 'afterSetter', aroundSetter: 'aroundSetter' }); const hasToRegisterMethodHook = hasToRegisterHook([hookName.beforeMethod, hookName.afterMethod, hookName.aroundMethod]); function overOwnProperty({ target, pattern, original, object }) { Object.entries(Object.getOwnPropertyDescriptors(object)).forEach(function ([property]) { try { if (!hasToRegisterMethodHook(pattern, { property, target, object })) { original[property] = object[property]; return; } if (property in original) { return; } if (!object[property]) { return; } let aopHooks = object[property][AOP_HOOKS]; if (aopHooks) { const { object: lastObject } = aopHooks[aopHooks.length - 1]; aopHooks.push({ target, object: lastObject, property, pattern }); if (!(property in original)) { original[property] = () => {}; } return; } if (typeof object[property] === 'function' && !isConstructable(object[property])) { original[property] = object[property]; object[property] = createCallTrap({ target, object: original, property, pattern }); } } catch (_) {} }); } function aopForMethods(target, pattern) { let original = {}; let prototype = target.prototype; while (prototype) { overOwnProperty({ target, pattern, original, object: prototype }); prototype = Reflect.getPrototypeOf(prototype); } } function createGetTrap({ target, object, property, pattern, context, method }) { function getTrap() { const hasProperty = getTrap[AOP_HOOKS].reduce((result, { object, property }) => { return result && Reflect.has(object, property); }, true); if (!hasProperty) { return; } let payload = undefined; let meta = {}; getTrap[AOP_HOOKS].forEach(({ target, object, property, pattern, context }) => { invokePattern(pattern.beforeGetter, { target, object, property, context, meta }); }); const { target, object, property, pattern, context } = getTrap[AOP_HOOKS][getTrap[AOP_HOOKS].length - 1]; const aroundGetter = Array.isArray(pattern.aroundGetter) ? pattern.aroundGetter[pattern.aroundGetter.length - 1] : pattern.aroundGetter; payload = aroundGetter ? invokePattern(aroundGetter, { target, object, property, context, meta }) : Reflect.get(object, property); getTrap[AOP_HOOKS].forEach(({ target, object, property, pattern, context }) => { invokePattern(pattern.afterGetter, { target, object, property, context, payload, meta }); }); if (typeof payload === 'function') { payload = createCallTrap({ target, object, property, pattern, context, method }); } return payload; } getTrap[AOP_HOOKS] = [{ target, object, property, pattern, context }]; return getTrap; } function createSetTrap({ target, object, property, pattern, context }) { function setTrap(payload) { let meta = {}; setTrap[AOP_HOOKS].forEach(({ target, object, property, context, pattern }) => { invokePattern(pattern.beforeSetter, { target, object, property, payload, context, meta }); }); const { target, object, property, pattern, context } = setTrap[AOP_HOOKS][setTrap[AOP_HOOKS].length - 1]; const aroundSetter = Array.isArray(pattern.aroundSetter) ? pattern.aroundSetter[pattern.aroundSetter.length - 1] : pattern.aroundSetter; let result = aroundSetter ? invokePattern(aroundSetter, { target, object, property, payload, context, meta }) : Reflect.set(object, property, payload); setTrap[AOP_HOOKS].forEach(({ target, object, property, context, pattern }) => { invokePattern(pattern.afterSetter, { target, object, property, payload, context, meta }); }); return result; } setTrap[AOP_HOOKS] = [{ target, object, property, pattern, context }]; return setTrap; } const hasToRegisterGetterSetterHook = hasToRegisterHook([hookName.beforeGetter, hookName.afterGetter, hookName.aroundGetter, hookName.beforeSetter, hookName.afterSetter, hookName.aroundSetter]); function aopForStatic(target, pattern) { let original = {}; let originalTarget = target; if (!Object.prototype.hasOwnProperty.call(originalTarget, AOP_STATIC_ALLOW)) { Reflect.defineProperty(originalTarget, AOP_STATIC_ALLOW, { value: false, enumerable: false, writable: true }); } else { originalTarget[AOP_STATIC_ALLOW] = false; } while (target && target !== Function.prototype) { Object.entries(Object.getOwnPropertyDescriptors(target)).forEach(([property, descriptor]) => { if (typeof descriptor.get === 'function' || typeof descriptor.set === 'function') { if (Object.prototype.hasOwnProperty.call(original, property)) { return; } Reflect.defineProperty(original, property, descriptor); if (!hasToRegisterGetterSetterHook(pattern, { target, property, object: original })) { return; } Reflect.defineProperty(target, property, Object.assign({}, descriptor, { get: (...rest) => { if (originalTarget[AOP_STATIC_ALLOW] === true) { let aopHooks = descriptor.get[AOP_HOOKS]; if (aopHooks) { const { object } = aopHooks[aopHooks.length - 1]; aopHooks.push({ target, object, property, pattern }); return typeof descriptor.get === 'function' ? descriptor.get(...rest) : undefined; } return createGetTrap({ target, object: original, property, pattern })(...rest); } else { return typeof descriptor.get === 'function' ? descriptor.get(...rest) : undefined; } }, set: payload => { if (originalTarget[AOP_STATIC_ALLOW] === true) { return createSetTrap({ target, object: original, property, pattern })(payload); } } })); } }); overOwnProperty({ target, pattern, original, object: target }); target = Reflect.getPrototypeOf(target); } originalTarget[AOP_STATIC_ALLOW] = true; } function createProxy(target, pattern, context) { pattern = pattern || target[AOP_PATTERN] || {}; const proxy = new Proxy(target, { get(object, property) { let original = object[property]; let value = createGetTrap({ target, object, property, pattern })(); if (value === undefined || original === undefined) { return; } if (typeof original !== 'object' && typeof original !== 'function') { return value; } return createProxy(original, pattern, object); }, set(object, property, payload) { return createSetTrap({ target, object, property, pattern })(payload); }, apply(method, object, args) { return createCallTrap({ target, object: context || object, property: method.name, pattern, context: context || object, method })(...args); } }); return proxy; } function createAspect(pattern) { return function applyAop(target) { return aop(target, pattern); }; } function aop(target, pattern, settings = { constructor: false }) { if (settings && settings.constructor && typeof target === 'function') { return aopWithConstructor(target, pattern); } return applyAop(target, pattern); } function aopWithConstructor(target, pattern) { let prototype = target.prototype; function AOPConstructor(...rest) { return createCallTrap({ target, object: target, property: 'constructor', pattern, context: this })(...rest); } AOPConstructor.prototype = prototype; applyAop(AOPConstructor, pattern); return AOPConstructor; } function unAop(target) { if (target[AOP_PATTERN]) { target[AOP_PATTERN] = Object.keys(target[AOP_PATTERN]).reduce((pattern, hookName) => { pattern[hookName] = undefined; return pattern; }, target[AOP_PATTERN]); target[AOP_PATTERN] = undefined; } } function applyAop(target, pattern) { if (target[AOP_PATTERN]) { if (typeof target === 'function') { aopForStatic(target, pattern); aopForMethods(target, pattern); return; } mergePattern(target, pattern); return; } Reflect.defineProperty(target, AOP_PATTERN, { value: Object.assign({}, pattern), enumerable: false, writable: true }); if (typeof target === 'function') { return applyAopToClass(target); } if (typeof target === 'object') { return applyAopToInstance(target); } throw new TypeError("aop accept only object and class. You gave type of ".concat(typeof target, ".")); } function applyAopToInstance(instance) { return createProxy(instance); } function applyAopToClass(target) { let pattern = target[AOP_PATTERN]; aopForStatic(target, pattern); aopForMethods(target, pattern); } function mergePattern(target, pattern) { let currentTargetPattern = target[AOP_PATTERN]; target[AOP_PATTERN] = Object.entries(pattern).reduce((resultPattern, [hookName, hookValue]) => { if (!resultPattern[hookName]) { resultPattern = hookName; } if (resultPattern[hookName]) { if (!Array.isArray(resultPattern[hookName])) { resultPattern[hookName] = [resultPattern[hookName]]; } if (!Array.isArray(hookValue)) { hookValue = [hookValue]; } resultPattern[hookName] = resultPattern[hookName].concat(hookValue); } return resultPattern; }, currentTargetPattern); } class DevManager { static get $dependencies() { return ['$PageManager', '$PageStateManager', '$Window', '$Dispatcher', '$EventBus', '$SessionMapStorage']; } constructor(pageManager, stateManager, window, dispatcher, eventBus, sessionMapStorage) { this._pageManager = pageManager; this._stateManager = stateManager; this._window = window; this._dispatcher = dispatcher; this._eventBus = eventBus; this._sessionMapStorage = sessionMapStorage; } init() { let window = this._window.getWindow(); this._window.bindEventListener(window, 'keydown', e => { if (e.altKey && e.keyCode === 83) { console.log('%cApp state:', 'background: #f03e3e; color: white; padding: 2px 4px; margin: 2px 0; font-weight: bold; font-size: 90%;'); console.log(this._stateManager.getState()); } if (e.altKey && e.keyCode === 67) { console.log('%cCache keys:', 'background: #f03e3e; color: white; padding: 2px 4px; margin: 2px 0; font-weight: bold; font-size: 90%;'); console.log(this._sessionMapStorage.keys()); } if (e.altKey && e.keyCode === 86) { console.log('%c$IMA.$Version:', 'background: #f03e3e; color: white; padding: 2px 4px; margin: 2px 0; font-weight: bold; font-size: 90%;'); console.log($IMA.$Version); } }); console.log('%cIMA.js Devtools are initialized, you can use following keyboard shortcuts:%c' + '\\n%cAlt + S%c - App state' + '\\n%cAlt + C%c - Cache keys' + '\\n%cAlt + V%c - $IMA.$Version', 'margin-top: 6px; background: #f03e3e; color: white; padding: 4px 8px; font-weight: bold;', 'font-weight: bold;', 'padding: 2px 4px; color: #c92a2a; background: #ffe3e3; border-radius: 3px; border: 1px solid #ffa8a8; margin: 10px 2px 2px 10px;', 'font-weight: bold;', 'padding: 2px 4px; color: #c92a2a; background: #ffe3e3; border-radius: 3px; border: 1px solid #ffa8a8; margin: 2px 2px 2px 10px;', 'font-weight: bold;', 'padding: 2px 4px; color: #c92a2a; background: #ffe3e3; border-radius: 3px; border: 1px solid #ffa8a8; margin: 2px 2px 2px 10px;', 'font-weight: bold; margin-bottom: 10px;'); } setState(statePatch) { this._stateManager.setState(statePatch); } getState() { return this._stateManager.getState(); } clearAppSource() { this._pageManager.destroy(); this._dispatcher.clear(); } } const ImaMainModules = ['onLoad', 'getInitialImaConfigFunctions', 'getNamespace', 'getInitialPluginConfig', 'createImaApp', 'getClientBootConfig', 'bootClientApp', 'routeClientApp', 'reviveClientApp']; function createDevtool(registerHook) { $IMA.devtool = $IMA.devtool || {}; function separatePromisesAndValues(dataMap) { let promises = {}; let values = {}; if (dataMap === undefined) { return { promises, values }; } for (let field of Object.keys(dataMap)) { let value = dataMap[field]; if (value instanceof Promise) { promises[field] = value; } else { values[field] = value; } } return { promises, values }; } function clone(data, deep = 1) { if (data === undefined) { return 'undefined'; } try { return JSON.parse(JSON.stringify(data)); } catch (e) { if (deep === 0) { return e.message; } return Object.keys(data).reduce((result, property) => { return result[property] = clone(data[property], deep - 1); }, {}); } } function generateState(meta, overrides) { if (overrides.state) { return overrides.state; } return { args: meta.args, payload: meta.payload }; } function emit(identifier, meta, options, overrides = {}) { const id = t(); const label = overrides.label ? overrides.label : identifier + ':' + meta.property; let state = generateState(meta, overrides); let { promises: argsPromise } = separatePromisesAndValues(state.args); let { promises: payloadPromise } = separatePromisesAndValues(state.payload); const promise = { args: argsPromise, payload: payloadPromise }; const stateKeys = ['args', 'payload']; let pendingPromises = stateKeys.map(key => Object.keys(promise[key]).length).reduce((sum, value) => sum + value, 0); stateKeys.map(key => { Object.keys(promise[key]).map(property => { promise[key][property].then(value => { pendingPromises--; state[key][property] = value; $IMA.devtool.postMessage({ id, label, type: meta.property, origin: identifier, time: new Date().getTime(), state: clone(state, 2), color: options.color, promises: pendingPromises > 0 ? 'pending' : 'resolved' }); }); }); }); if (state.payload instanceof Promise) { pendingPromises += 1; state.payload.then(value => { pendingPromises--; state.payload = value; $IMA.devtool.postMessage({ id, label, type: meta.property, origin: identifier, time: new Date().getTime(), state: clone(state, 2), color: options.color, promises: pendingPromises > 0 ? 'pending' : 'resolved' }); }); } $IMA.devtool.postMessage({ id, label, type: meta.property, origin: identifier, time: new Date().getTime(), state: clone(state, 2), color: options.color, promises: pendingPromises > 0 ? 'pending' : null }); } function importIMAClass(path, module = null) { try { const file = $IMA.Loader.importSync(path); const key = module ? module : 'default'; return file[key] ? file[key] : file; } catch (e) { console.error('[IMA Devtools]', e.message); } } $IMA.Runner.registerPreRunCommand(function () { if (!window.__IMA_DEVTOOLS_INIT) { return; } registerHook({ importIMAClass, clone, aop, createHook, hookName, createCallTrap, emit }); let revivePattern = createHook(hookName.afterMethod, 'reviveClientApp', meta => { if (meta.payload && typeof meta.payload.then === 'function') { meta.payload.then(page => { if (page.app) { const oc = page.app.oc; $IMA.devtool.oc = oc; $IMA.devtool.bootstrap = page.app.bootstrap; $IMA.devtool.manager = oc.create(DevManager); $IMA.devtool.manager.init(); } }); } }); let imaCore = importIMAClass('@ima/core', true); ImaMainModules.forEach(moduleName => { const key = "__".concat(moduleName, "__"); Object.defineProperty(imaCore, key, { value: imaCore[moduleName], enumerable: false, configurable: false, writable: false }); imaCore[key] = imaCore[moduleName]; imaCore[moduleName] = createCallTrap({ target: imaCore, object: imaCore, property: key, pattern: revivePattern }); }); }); } $IMA.Runner.registerPreRunCommand(function () { try { window.__IMA_DEVTOOLS_INIT = true; $IMA.Loader.importSync('@ima/core'); } catch (_) { $IMA.devtool.postMessage({ message: 'Current IMA.js version is not supported' }, 'ima:devtool:unsupported'); window.__IMA_DEVTOOLS_INIT = false; } }); //# sourceMappingURL=main.cjs.map