@the-control-group/react-split-test
Advanced tools
Comparing version 1.0.0 to 2.0.0
@@ -1,104 +0,117 @@ | ||
import React, { Component } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const react_1 = __importStar(require("react")); | ||
const variationPromiseCache = new Map(); | ||
export default class Experiment extends Component { | ||
static propTypes = { | ||
children: PropTypes.node.isRequired, | ||
id: PropTypes.string.isRequired, | ||
session: PropTypes.bool, | ||
onParticipate: PropTypes.func, | ||
variationDecider: PropTypes.func | ||
}; | ||
static defaultProps = { | ||
onParticipate: () => {}, | ||
/** | ||
* Stub A/B decider | ||
* @param {React.node} experimentChildren - React children nodes of the experiment | ||
* @return {Promise<variation id>} Selected variation ID | ||
*/ | ||
variationDecider: experimentChildren => { | ||
const variations = []; | ||
React.Children.forEach(experimentChildren, c => { | ||
if(c.props.isVariation) variations.push(c.props.id); | ||
}); | ||
return Promise.resolve(variations[Math.floor(Math.random() * variations.length)]); | ||
} | ||
}; | ||
constructor(props) { | ||
super(props); | ||
this.cacheKey = `@tcg-split-test:${this.props.id}`; | ||
this.storage = this.props.session ? sessionStorage : localStorage; | ||
this.state = { | ||
selectedVariation: this.chooseVariation() | ||
}; | ||
} | ||
chooseVariation() { | ||
let cachedData; | ||
try { | ||
cachedData = JSON.parse(this.storage.getItem(this.cacheKey)); | ||
} catch (e) { | ||
// No/invalid cache | ||
} | ||
if(cachedData) { | ||
return cachedData.selectedVariation; | ||
} else { | ||
// Cache promises so only one decision is made per experiment ID | ||
let variationDecider; | ||
if(variationPromiseCache.has(this.props.id)) { | ||
variationDecider = variationPromiseCache.get(this.props.id); | ||
} else { | ||
variationDecider = this.props.variationDecider(this.props.children); | ||
variationPromiseCache.set(this.props.id, variationDecider); | ||
} | ||
variationDecider | ||
.then(selectedVariation => { | ||
const experimentData = { | ||
id: this.props.id, | ||
selectedVariation | ||
}; | ||
// Since this is async, we need to check if another component with the same experiment | ||
// has already made this decision in a potential race condition | ||
let raceData; | ||
try { | ||
raceData = JSON.parse(this.storage.getItem(this.cacheKey)); | ||
} catch(e) { | ||
// No/invalid data | ||
} | ||
if(!raceData) { | ||
this.storage.setItem(this.cacheKey, JSON.stringify(experimentData)); | ||
this.props.onParticipate(experimentData); | ||
// Remove the initial promise from the cache since subsequent renders/mounts will work off of the cache | ||
variationPromiseCache.delete(this.props.id); | ||
} | ||
this.setState({ | ||
selectedVariation: raceData ? raceData.selectedVariation : selectedVariation | ||
}); | ||
}); | ||
return null; | ||
} | ||
} | ||
render() { | ||
const { selectedVariation } = this.state; | ||
return React.Children.map(this.props.children, child => { | ||
if(child.props.isVariation && child.props.id !== selectedVariation) return; | ||
return child; | ||
}); | ||
} | ||
class Experiment extends react_1.Component { | ||
constructor(props) { | ||
super(props); | ||
this.cacheKey = `@tcg-split-test:${this.props.id}`; | ||
this.storage = this.props.session ? sessionStorage : localStorage; | ||
this.state = { | ||
selectedVariation: this.chooseVariation() | ||
}; | ||
} | ||
chooseVariation() { | ||
let cachedData; | ||
const cachedString = this.storage.getItem(this.cacheKey); | ||
if (cachedString) { | ||
try { | ||
cachedData = JSON.parse(cachedString); | ||
} | ||
catch (e) { | ||
console.error('Invalid experiment cache data', e); | ||
} | ||
} | ||
if (cachedData) { | ||
return cachedData.selectedVariation; | ||
} | ||
else { | ||
// Cache promises so only one decision is made per experiment ID | ||
let variationDecider; | ||
if (variationPromiseCache.has(this.props.id)) { | ||
variationDecider = variationPromiseCache.get(this.props.id); | ||
} | ||
else { | ||
variationDecider = this.props.variationDecider(this.props.children); | ||
variationPromiseCache.set(this.props.id, variationDecider); | ||
} | ||
variationDecider | ||
.then(selectedVariation => { | ||
const experimentData = { | ||
id: this.props.id, | ||
selectedVariation | ||
}; | ||
// Since this is async, we need to check if another component with the same experiment | ||
// has already made this decision in a potential race condition | ||
let raceData; | ||
const cachedRaceString = this.storage.getItem(this.cacheKey); | ||
if (cachedRaceString) { | ||
try { | ||
raceData = JSON.parse(cachedRaceString); | ||
} | ||
catch (e) { | ||
console.error('Invalid experiment race cache data', e); | ||
} | ||
} | ||
if (!raceData) { | ||
this.storage.setItem(this.cacheKey, JSON.stringify(experimentData)); | ||
this.props.onParticipate(experimentData); | ||
// Remove the initial promise from the cache since subsequent renders/mounts will work off of the cache | ||
variationPromiseCache.delete(this.props.id); | ||
} | ||
this.setState({ | ||
selectedVariation: raceData ? raceData.selectedVariation : selectedVariation | ||
}); | ||
}); | ||
return null; | ||
} | ||
} | ||
render() { | ||
const { selectedVariation } = this.state; | ||
return react_1.default.Children.map(this.props.children, child => { | ||
if (react_1.default.isValidElement(child) && child.props.isVariation && child.props.id !== selectedVariation) | ||
return; | ||
return child; | ||
}); | ||
} | ||
} | ||
Experiment.defaultProps = { | ||
onParticipate: () => { }, | ||
/** | ||
* Stub A/B decider | ||
* @param {React.node} experimentChildren - React children nodes of the experiment | ||
* @return {Promise<variation id>} Selected variation ID | ||
*/ | ||
variationDecider: (experimentChildren) => { | ||
const variations = []; | ||
react_1.default.Children.forEach(experimentChildren, c => { | ||
if (react_1.default.isValidElement(c) && c.props.isVariation) | ||
variations.push(c.props.id); | ||
}); | ||
return Promise.resolve(variations[Math.floor(Math.random() * variations.length)]); | ||
} | ||
}; | ||
exports.default = Experiment; |
@@ -1,15 +0,31 @@ | ||
export { default as Experiment } from './Experiment'; | ||
export { default as Variation } from './Variation'; | ||
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.clearAllExperimentCaches = exports.clearExperimentCache = exports.getCachedVariation = exports.Variation = exports.Experiment = void 0; | ||
var Experiment_1 = require("./Experiment"); | ||
Object.defineProperty(exports, "Experiment", { enumerable: true, get: function () { return __importDefault(Experiment_1).default; } }); | ||
var Variation_1 = require("./Variation"); | ||
Object.defineProperty(exports, "Variation", { enumerable: true, get: function () { return __importDefault(Variation_1).default; } }); | ||
// These only work for persisted variations | ||
export function getCachedVariation(id) { | ||
return JSON.parse(localStorage.getItem(`@tcg-split-test:${id}`)); | ||
function getCachedVariation(id) { | ||
const cachedVariation = localStorage.getItem(`@tcg-split-test:${id}`); | ||
if (cachedVariation) { | ||
try { | ||
return JSON.parse(cachedVariation); | ||
} | ||
catch (e) { | ||
console.error('Invalid variation data', e); | ||
} | ||
} | ||
} | ||
export function clearExperimentCache(id) { | ||
localStorage.removeItem(`@tcg-split-test:${id}`); | ||
exports.getCachedVariation = getCachedVariation; | ||
function clearExperimentCache(id) { | ||
localStorage.removeItem(`@tcg-split-test:${id}`); | ||
} | ||
export function clearAllExperimentCaches() { | ||
Object.keys(localStorage).forEach(k => /^@tcg-split-test:/.test(k) && localStorage.removeItem(k)); | ||
exports.clearExperimentCache = clearExperimentCache; | ||
function clearAllExperimentCaches() { | ||
Object.keys(localStorage).forEach(k => /^@tcg-split-test:/.test(k) && localStorage.removeItem(k)); | ||
} | ||
exports.clearAllExperimentCaches = clearAllExperimentCaches; |
@@ -1,18 +0,11 @@ | ||
import React, { Fragment } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
const Variation = ({ children }) => ( | ||
<Fragment>{children}</Fragment> | ||
); | ||
Variation.propTypes = { | ||
children: PropTypes.node.isRequired, | ||
isVariation: PropTypes.bool, | ||
id: PropTypes.string.isRequired | ||
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const react_1 = __importDefault(require("react")); | ||
const Variation = ({ children }) => (react_1.default.createElement(react_1.default.Fragment, null, children)); | ||
Variation.defaultProps = { | ||
isVariation: true | ||
isVariation: true | ||
}; | ||
export default Variation; | ||
exports.default = Variation; |
{ | ||
"name": "@the-control-group/react-split-test", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"description": "A/B Split Testing Component for React", | ||
@@ -10,7 +10,6 @@ "files": [ | ||
"scripts": { | ||
"watch": "cpx \"src/**/*.{less,js}\" lib --watch", | ||
"copy": "rm -rf lib/ && cpx \"src/*.js\" lib", | ||
"prepublishOnly": "npm test && npm run copy", | ||
"start": "react-devtools & webpack-dev-server --hot --mode development", | ||
"test": "eslint src" | ||
"prepublishOnly": "npm test && npm run build", | ||
"start": "webpack serve --mode development", | ||
"build": "tsc", | ||
"test": "tsc --noEmit && eslint src" | ||
}, | ||
@@ -35,25 +34,23 @@ "repository": { | ||
"devDependencies": { | ||
"@babel/cli": "^7.6.4", | ||
"@babel/core": "^7.6.4", | ||
"@babel/plugin-proposal-class-properties": "^7.5.5", | ||
"@babel/preset-env": "^7.6.3", | ||
"@babel/preset-react": "^7.6.3", | ||
"babel-eslint": "^10.0.3", | ||
"babel-loader": "^8.0.6", | ||
"cpx": "^1.5.0", | ||
"eslint": "^6.5.1", | ||
"eslint-plugin-react": "^7.16.0", | ||
"html-webpack-plugin": "^3.2.0", | ||
"prop-types": "^15.6.2", | ||
"react": "^16.4.2", | ||
"react-devtools": "^4.2.0", | ||
"react-dom": "^16.10.2", | ||
"webpack": "^4.41.0", | ||
"webpack-cli": "^3.3.9", | ||
"webpack-dev-server": "^3.8.2" | ||
"@types/react": "^18.2.25", | ||
"@types/react-dom": "^18.2.10", | ||
"@typescript-eslint/eslint-plugin": "^6.7.4", | ||
"@typescript-eslint/parser": "^6.7.4", | ||
"eslint": "^8.50.0", | ||
"eslint-plugin-react": "^7.33.2", | ||
"eslint-plugin-react-hooks": "^4.6.0", | ||
"fork-ts-checker-webpack-plugin": "^9.0.0", | ||
"html-webpack-plugin": "^5.5.3", | ||
"npm-check-updates": "^16.14.5", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"ts-loader": "^9.4.4", | ||
"typescript": "^5.2.2", | ||
"webpack": "^5.88.2", | ||
"webpack-cli": "^5.1.4", | ||
"webpack-dev-server": "^4.15.1" | ||
}, | ||
"peerDependencies": { | ||
"react": ">=16", | ||
"prop-types": ">=15" | ||
"react": ">=17" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
10390
1
17
8
205
1