react-split-testing
Version:
Simple A/B testing component for React.
423 lines (340 loc) • 11 kB
JavaScript
import React, { Component } from 'react';
import PropTypes from 'prop-types';
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var classCallCheck = _classCallCheck;
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var createClass = _createClass;
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var _typeof_1 = createCommonjsModule(function (module) {
function _typeof2(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof2 = function _typeof2(obj) { return typeof obj; }; } else { _typeof2 = function _typeof2(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof2(obj); }
function _typeof(obj) {
if (typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol") {
module.exports = _typeof = function _typeof(obj) {
return _typeof2(obj);
};
} else {
module.exports = _typeof = function _typeof(obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : _typeof2(obj);
};
}
return _typeof(obj);
}
module.exports = _typeof;
});
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
var assertThisInitialized = _assertThisInitialized;
function _possibleConstructorReturn(self, call) {
if (call && (_typeof_1(call) === "object" || typeof call === "function")) {
return call;
}
return assertThisInitialized(self);
}
var possibleConstructorReturn = _possibleConstructorReturn;
var getPrototypeOf = createCommonjsModule(function (module) {
function _getPrototypeOf(o) {
module.exports = _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
module.exports = _getPrototypeOf;
});
var setPrototypeOf = createCommonjsModule(function (module) {
function _setPrototypeOf(o, p) {
module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
module.exports = _setPrototypeOf;
});
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) setPrototypeOf(subClass, superClass);
}
var inherits = _inherits;
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;
}
var defineProperty = _defineProperty;
function getRandom(seed) {
var newSeed = 0;
for (var i = 0; i < seed.length; i++) {
newSeed += seed.charCodeAt(i) * Math.pow(10, i);
}
var x = Math.sin(newSeed) * 10000;
return x - Math.floor(x);
}
function weightedRandom(weights, randomSeed) {
// randomSeed must be a string
if (!randomSeed) {
return -1;
}
var totalWeight = 0;
for (var i = 0; i < weights.length; i++) {
totalWeight += weights[i];
}
var random = getRandom(randomSeed) * totalWeight;
for (var _i = 0; _i < weights.length; _i++) {
if (random < weights[_i]) {
return _i;
}
random -= weights[_i];
}
return -1;
}
function generateIdentifier() {
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var text = '';
for (var i = 0; i < 20; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
function getIdentifier(userIdentifier) {
if (userIdentifier || userIdentifier === 0) {
return String(userIdentifier);
}
if (typeof window === 'undefined' || !('localStorage' in window)) {
return null;
}
var key = '__ab_experiment_identifier__';
try {
userIdentifier = localStorage.getItem(key);
userIdentifier = userIdentifier && String(userIdentifier);
if (!userIdentifier) {
userIdentifier = generateIdentifier();
localStorage.setItem(key, userIdentifier);
}
} catch (exception) {
return null;
}
return userIdentifier;
}
function getWeight(weight) {
return weight || weight === 0 ? parseFloat(weight) : 1;
}
var Experiment =
/*#__PURE__*/
function (_Component) {
inherits(Experiment, _Component);
function Experiment(props) {
var _this;
classCallCheck(this, Experiment);
_this = possibleConstructorReturn(this, getPrototypeOf(Experiment).call(this, props));
_this.state = {};
_this.state.activeVariant = _this._chooseVariant(true);
return _this;
}
createClass(Experiment, [{
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps) {
var _this$props = this.props,
name = _this$props.name,
children = _this$props.children,
userIdentifier = _this$props.userIdentifier,
variantName = _this$props.variantName;
var isNewExperiment = prevProps.name !== name || prevProps.userIdentifier !== userIdentifier || prevProps.variantName !== variantName;
if (!isNewExperiment && prevProps.children === children) {
return;
}
this.setState({
activeVariant: this._chooseVariant(prevProps, isNewExperiment)
});
}
}, {
key: "getActiveVariant",
value: function getActiveVariant() {
return this.state.activeVariant;
}
}, {
key: "getActiveVariantName",
value: function getActiveVariantName() {
return this.state.activeVariant && this.state.activeVariant.props.name;
}
}, {
key: "getName",
value: function getName() {
return this.props.name;
}
}, {
key: "getVariant",
value: function getVariant(variantName) {
var _this2 = this;
var children = React.Children.toArray(this.props.children);
return children.find(function (element) {
return _this2._isVariant(element) && element.props.name === variantName;
});
}
}, {
key: "_chooseVariant",
value: function _chooseVariant(isNewExperiment) {
var _this$props2 = this.props,
variantName = _this$props2.variantName,
userIdentifier = _this$props2.userIdentifier;
if (variantName) {
var _variant = this.getVariant(variantName);
if (isNewExperiment && _variant) {
this._onChoice(_variant);
}
return _variant;
}
var children = React.Children.toArray(this.props.children);
var activeVariantName = this.state.activeVariant && this.state.activeVariant.props.name;
var variants = [],
weights = [];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = children[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var element = _step.value;
if (!this._isVariant(element)) {
continue;
}
if (!isNewExperiment && activeVariantName === element.props.name) {
return element;
}
variants.push(element);
weights.push(getWeight(element.props.weight));
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator["return"] != null) {
_iterator["return"]();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var randomSeed = getIdentifier(userIdentifier);
var index = weightedRandom(weights, randomSeed);
if (index === -1) {
return null;
}
var variant = variants[index];
this._onChoice(variant);
return variant;
}
}, {
key: "_isVariant",
value: function _isVariant(element) {
if (!React.isValidElement(element)) {
return false;
}
if (!element.type.isVariant) {
throw new Error('Experiment children must be Variant components.');
}
return true;
}
}, {
key: "_onChoice",
value: function _onChoice(variant) {
var _this$props3 = this.props,
onChoice = _this$props3.onChoice,
onRawChoice = _this$props3.onRawChoice,
name = _this$props3.name;
if (onChoice instanceof Function) {
onChoice(name, variant.props.name);
}
if (onRawChoice instanceof Function) {
onRawChoice(this, variant);
}
}
}, {
key: "render",
value: function render() {
return this.state.activeVariant;
}
}]);
return Experiment;
}(Component);
defineProperty(Experiment, "propTypes", {
name: PropTypes.string.isRequired,
onChoice: PropTypes.func,
onRawChoice: PropTypes.func,
userIdentifier: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
variantName: PropTypes.string
});
var Variant =
/*#__PURE__*/
function (_Component) {
inherits(Variant, _Component);
function Variant() {
classCallCheck(this, Variant);
return possibleConstructorReturn(this, getPrototypeOf(Variant).apply(this, arguments));
}
createClass(Variant, [{
key: "getName",
value: function getName() {
return this.props.name;
}
}, {
key: "getWeight",
value: function getWeight$1() {
return getWeight(this.props.weight);
}
}, {
key: "render",
value: function render() {
return this.props.children;
}
}]);
return Variant;
}(Component);
defineProperty(Variant, "propTypes", {
name: PropTypes.string.isRequired,
weight: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
});
defineProperty(Variant, "isVariant", true);
export { Experiment, Variant };
//# sourceMappingURL=index.es.js.map