UNPKG

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
(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; }));