@ima/devtools-scripts
Version:
IMA.js devtool script used in the @ima/devtools.
929 lines (821 loc) • 22.9 kB
JavaScript
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.mjs.map