redux-profiler
Version:
A Redux store enhancer which uses User Timing API to profile redux actions and time spent on notifying store listeners
249 lines (202 loc) • 7.05 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ReduxProfiler = factory());
})(this, (function () { 'use strict';
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) {
symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
}
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
/*eslint-env browser*/
var PROFILER_MARK_TYPE_REDUCE = 'reduce';
var PROFILER_MARK_TYPE_NOTIFY = 'notify';
var MARK_NAME_DELIMITER = ':';
var START = MARK_NAME_DELIMITER + 'start';
var END = MARK_NAME_DELIMITER + 'end';
function getMarkInfo(action) {
var name = 'UNKNOWN_ACTION';
var type = 'unknown';
if (isReduxAction(action)) {
name = action.type;
type = '';
} else if (isReduxThunkAction(action)) {
type = 'thunk';
if (action.name) {
name = action.name;
} else {
name = 'anonymous';
}
}
return {
name: name,
type: type
};
}
function getMarkLabel(markInfo, profilerMarkType) {
return "\u267B ".concat(markInfo.name, " (").concat(markInfo.type.length > 0 ? markInfo.type : profilerMarkType, ")");
}
function isReduxThunkAction(action) {
return typeof action === 'function';
}
function isReduxAction(action) {
return action !== null && _typeof(action) === 'object' && action.type;
}
function isValidPerformanceObject(perf) {
return perf && _typeof(perf) === 'object' && ['mark', 'measure', 'clearMarks', 'clearMeasures'].every(function (fn) {
return typeof perf[fn] === 'function';
});
}
function profileStore() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var performance;
if (options.performance) {
performance = options.performance;
} else if (typeof window !== 'undefined') {
performance = window.performance;
}
if (!isValidPerformanceObject(performance)) {
performance = undefined;
}
var performProfiledOperation = function () {
var counter = {};
function getMarkName(markInfo) {
var markName = markInfo.name + MARK_NAME_DELIMITER + counter[markInfo.name];
if (markInfo.type.length > 0) {
markName += MARK_NAME_DELIMITER + markInfo.type;
}
return markName;
}
return function (markInfo, markType, op) {
counter[markInfo.name] = counter[markInfo.name] === undefined ? 0 : counter[markInfo.name] + 1;
var markName = getMarkName(markInfo);
performance.mark(markName + START);
var res = op();
performance.mark(markName + END);
performance.measure(getMarkLabel(markInfo, markType), markName + START, markName + END);
performance.clearMarks(markName + START);
performance.clearMarks(markName + END);
performance.clearMeasures(getMarkLabel(markInfo, markType));
if (counter[markInfo.name]) {
counter[markInfo.name]--;
} else {
delete counter[markInfo.name];
}
return res;
};
}();
var currentListeners = [];
var nextListeners = currentListeners;
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.');
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}
function notifyListeners() {
var listeners = currentListeners = nextListeners;
listeners.forEach(function (listener) {
return listener();
});
}
return function (next) {
return function () {
var store = next.apply(void 0, arguments);
if (typeof performance === 'undefined') {
return store;
}
function dispatch(action) {
var info = getMarkInfo(action);
var res = performProfiledOperation(getMarkInfo(action), PROFILER_MARK_TYPE_REDUCE, function () {
if (info.type === 'thunk') {
var reduxThunkExtraArgument = store.dispatch(function (d, s, extraArgument) {
return extraArgument;
});
return action(dispatch, store.getState, reduxThunkExtraArgument);
} else {
return store.dispatch(action);
}
}); // thunk should not notify listeners but rather just dispatch other actions
if (info.type !== 'thunk') {
performProfiledOperation(getMarkInfo(action), PROFILER_MARK_TYPE_NOTIFY, function () {
return notifyListeners();
});
}
return res;
}
return _objectSpread2(_objectSpread2({}, store), {}, {
dispatch: dispatch,
subscribe: subscribe
});
};
};
}
return profileStore;
}));