├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── dist ├── react-stampit-with-addons.js ├── react-stampit-with-addons.min.js ├── react-stampit.js └── react-stampit.min.js ├── docs ├── advanced.md ├── api.md └── composition.md ├── package.json ├── src ├── addons.js ├── index.js └── utils │ ├── cache.js │ ├── compose.js │ ├── decorator.js │ └── index.js ├── test ├── advanced.js ├── basics.js ├── compose.js ├── methods.js ├── state.js ├── statics.js ├── test.js └── wrapMethods.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | 4 | "env": { 5 | "node": true 6 | }, 7 | 8 | "rules": { 9 | "brace-style": 2, 10 | "comma-dangle": [2, "always-multiline"], 11 | "consistent-return": 2, 12 | "curly": [2, "multi-line"], 13 | "dot-notation": 2, 14 | "eol-last": 2, 15 | "indent": [2, 2, {"SwitchCase": 1, "VariableDeclarator": 2}], 16 | "no-else-return": 2, 17 | "no-eq-null": 2, 18 | "no-floating-decimal": 2, 19 | "no-param-reassign": 2, 20 | "no-self-compare": 2, 21 | "no-shadow": 0, 22 | "no-throw-literal": 2, 23 | "no-underscore-dangle": 0, 24 | "object-shorthand": 2, 25 | "quotes": [2, "single"], 26 | "space-after-keywords": 2, 27 | "strict": 0, 28 | "vars-on-top": 2, 29 | "wrap-iife": 2, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "node" 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/stampit-org/react-stampit.svg)](https://travis-ci.org/stampit-org/react-stampit)![Greenkeeper Badge](https://badges.greenkeeper.io/stampit-org/react-stampit.svg) 2 | 3 | # react-stampit 4 | 5 | > A specialized [stampit](https://github.com/stampit-org/stampit) factory for [React](https://github.com/facebook/react). 6 | 7 | Create React components in a way analogous to `React.createClass`, but powered by a [subset](docs/api.md#api-differences) of the [stampit](https://github.com/stampit-org/stampit) API. 8 | 9 | **This library has been replaced by [react-stamp](https://github.com/stampit-org/react-stamp).** 10 | 11 | ## Install 12 | 13 | react-stampit can be [installed via npm](https://www.npmjs.com/package/react-stampit) 14 | 15 | ``` 16 | npm install react-stampit 17 | ``` 18 | 19 | or by [downloading the latest release](https://github.com/stampit-org/react-stampit/releases). 20 | 21 | ## What is this 22 | 23 | This library is the result of wondering about what other ways a React component could be represented. [Stamps](https://github.com/stampit-org/stampit#what-is-a-stamp) are a cool concept, and more importantly have proven to be a great alternative to `React.createClass` and the ES2015 `class` due to their flexibility and use of multiple kinds of prototypal inheritance. 24 | 25 | react-stampit has an API similar to `React.createClass`. The factory accepts two parameters, the React library and a description object. 26 | 27 | ```js 28 | stampit(React, { 29 | init: [], 30 | state: {}, 31 | statics: {}, 32 | 33 | // convenience props for React statics 34 | contextTypes: {}, 35 | childContextTypes: {}. 36 | propTypes: {}, 37 | defaultProps: {}, 38 | 39 | // ...methods 40 | }); 41 | ``` 42 | 43 | The best part about [stamps](https://github.com/stampit-org/stampit#what-is-a-stamp) is their composability. What this means is that `n` number of stamps can be combined into a new stamp which inherits each passed stamp's behavior. This is perfect for React, since `class` is being pushed as the new norm and does not provide an idiomatic way to use mixins. (classical inheritance :disappointed:). While stamp composability on the surface is not idiomatic, the conventions used underneath are; it is these conventions that provide a limitless way to extend any React component. 44 | 45 | __mixin1.jsx__ 46 | 47 | ```js 48 | export default { 49 | componentWillMount() { 50 | this.state.mixin1 = true; 51 | }, 52 | }; 53 | ``` 54 | 55 | __mixin2.jsx__ 56 | 57 | ```js 58 | export default { 59 | componentWillMount() { 60 | this.state.mixin2 = true; 61 | }, 62 | }; 63 | ``` 64 | 65 | __component.jsx__ 66 | 67 | ```js 68 | import stampit from 'react-stampit'; 69 | import * as cache from 'react-stampit/utils/cache'; 70 | 71 | const id = cache.uniqueId(); 72 | 73 | export default React => { 74 | return cache.find(id) || cache.save( 75 | stampit(React, { 76 | state: { 77 | comp: false, 78 | mixin1: false, 79 | mixin2: false, 80 | }, 81 | 82 | _onClick() { 83 | return this.state; 84 | }, 85 | 86 | componentWillMount() { 87 | this.state.comp = true; 88 | }, 89 | 90 | render() { 91 | return this._onClick()} />; 92 | }, 93 | }), id 94 | ); 95 | }; 96 | ``` 97 | 98 | __app.jsx__ 99 | 100 | ```js 101 | import React from 'react'; 102 | 103 | import componentFactory from './component'; 104 | import mixin1 from './mixin1'; 105 | import mixin2 from './mixin2'; 106 | 107 | const Component = componentFactory(React).compose(mixin1, mixin2); 108 | 109 | /** 110 | * Do things 111 | */ 112 | ``` 113 | 114 | ```js 115 | shallowRenderer.render(); 116 | const button = shallowRenderer.getRenderOutput(); 117 | 118 | assert.deepEqual( 119 | button.props.onClick(), { comp: true, mixin1: true, mixin2: true }, 120 | 'should return component state with all truthy props' 121 | ); 122 | >> ok 123 | ``` 124 | 125 | You may have noticed several interesting behaviors. 126 | 127 | * component is a factory 128 | 129 | This design pattern is optional, but recommended. Component factories are react-stampit's solution for avoiding the often hard to debug problems created by multiple instances of React. Read more about that [here](https://medium.com/@dan_abramov/two-weird-tricks-that-fix-react-7cf9bbdef375). By injecting the React library, we are able to guarantee the version and instance of React that a component will receive. 130 | 131 | * caching 132 | 133 | This goes hand in hand with designing components as factories. Node.js's internal caching will not work as expected for component factories, react-stampit's cache utility can be used as a replacement. 134 | 135 | * no autobinding 136 | 137 | Event handlers require explicit binding. No magic. This can be done using `.bind` or through lexical binding with ES2015 [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) as shown in the example. 138 | * no `call super` 139 | 140 | React methods are wrapped during composition, providing functional inheritance. Sweet. 141 | * mixins are POJOs 142 | 143 | This is shorthand syntax for: 144 | ```js 145 | stampit(null, { 146 | // stuff 147 | }); 148 | ``` 149 | 150 | If you feel limited by `class`, or want a fresh take on `React.createClass`, maybe give react-stampit a try and learn more about what [stampit](https://github.com/stampit-org/stampit) is all about. And please report any issues you encounter! 151 | 152 | ## Docs 153 | * [API](docs/api.md) 154 | * [Composition logic](docs/composition.md) 155 | * [Advanced use cases](docs/advanced.md) 156 | 157 | ## Examples 158 | * [react-hello](https://github.com/stampit-org/react-hello) 159 | 160 | ## Pending Issues 161 | * [x] [childContextTypes](https://github.com/facebook/react/pull/3940) 162 | * [x] [component testing](https://github.com/facebook/react/pull/3941) 163 | 164 | ## License 165 | [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](http://troutowicz.mit-license.org) 166 | -------------------------------------------------------------------------------- /dist/react-stampit-with-addons.min.js: -------------------------------------------------------------------------------- 1 | !function(t,r){"object"==typeof exports&&"object"==typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):"object"==typeof exports?exports.stampit=r():t.stampit=r()}(this,function(){return function(t){function r(n){if(e[n])return e[n].exports;var o=e[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports,o,o.exports,r),o.loaded=!0,o.exports}var e={};return r.m=t,r.c=e,r.p="",r(0)}([function(t,r,e){t.exports=e(101)},function(t,r,e){"use strict";var n=e(12),o=e(7),u=e(3),i="[object Array]",c=Object.prototype,a=c.toString,s=n(Array,"isArray"),f=s||function(t){return u(t)&&o(t.length)&&a.call(t)==i};t.exports=f},function(t,r){"use strict";function e(t){var r=typeof t;return!!t&&("object"==r||"function"==r)}t.exports=e},function(t,r){"use strict";function e(t){return!!t&&"object"==typeof t}t.exports=e},function(t,r,e){"use strict";function n(t){return o(t)?t:Object(t)}var o=e(2);t.exports=n},function(t,r,e){"use strict";function n(t,r,e){if("function"!=typeof t)return o;if(void 0===r)return t;switch(e){case 1:return function(e){return t.call(r,e)};case 3:return function(e,n,o){return t.call(r,e,n,o)};case 4:return function(e,n,o,u){return t.call(r,e,n,o,u)};case 5:return function(e,n,o,u,i){return t.call(r,e,n,o,u,i)}}return function(){return t.apply(r,arguments)}}var o=e(42);t.exports=n},function(t,r,e){"use strict";function n(t){return null!=t&&u(o(t))}var o=e(36),u=e(7);t.exports=n},function(t,r){"use strict";function e(t){return"number"==typeof t&&t>-1&&t%1==0&&n>=t}var n=9007199254740991;t.exports=e},function(t,r,e){"use strict";function n(t){return u(t)&&o(t)&&c.call(t,"callee")&&!a.call(t,"callee")}var o=e(6),u=e(3),i=Object.prototype,c=i.hasOwnProperty,a=i.propertyIsEnumerable;t.exports=n},function(t,r,e){"use strict";var n=e(12),o=e(6),u=e(2),i=e(81),c=n(Object,"keys"),a=c?function(t){var r=null==t?void 0:t.constructor;return"function"==typeof r&&r.prototype===t||"function"!=typeof t&&o(t)?i(t):u(t)?c(t):[]}:i;t.exports=a},function(t,r,e){"use strict";function n(t){if(null==t)return[];a(t)||(t=Object(t));var r=t.length;r=r&&c(r)&&(u(t)||o(t))&&r||0;for(var e=t.constructor,n=-1,s="function"==typeof e&&e.prototype===t,l=Array(r),p=r>0;++n-1&&t%1==0&&r>t}var n=/^\d+$/,o=9007199254740991;t.exports=e},function(t,r,e){"use strict";function n(t){return o(t)&&c.call(t)==u}var o=e(2),u="[object Function]",i=Object.prototype,c=i.toString;t.exports=n},function(t,r,e){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t){return"function"==typeof t&&"function"==typeof t.compose&&"object"==typeof t.fixed}function u(t){return delete t.create,delete t.init,delete t.methods,delete t.state,delete t.refs,delete t.props,delete t.enclose,delete t["static"],t}Object.defineProperty(r,"__esModule",{value:!0}),r.isStamp=o,r.stripStamp=u;var i=e(98),c=n(i),a=e(99),s=n(a);r.compose=c["default"],r.stamp=s["default"]},function(t,r){"use strict";function e(t,r){if("function"!=typeof t)throw new TypeError(n);return r=o(void 0===r?t.length-1:+r||0,0),function(){for(var e=arguments,n=-1,u=o(e.length-r,0),i=Array(u);++nn;)t=t[r[n++]];return n&&n==u?t:void 0}}var o=e(4);t.exports=n},function(t,r,e){"use strict";function n(t,r){var e=typeof t;if("string"==e&&c.test(t)||"number"==e)return!0;if(o(t))return!1;var n=!i.test(t);return n||null!=r&&t in u(r)}var o=e(1),u=e(4),i=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,c=/^\w*$/;t.exports=n},function(t,r,e){"use strict";function n(t){if(u(t))return t;var r=[];return o(t).replace(i,function(t,e,n,o){r.push(n?o.replace(c,"$1"):e||t)}),r}var o=e(44),u=e(1),i=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,c=/\\(\\)?/g;t.exports=n},function(t,r,e){"use strict";function n(t){return u(t)&&o(t.length)&&!!U[E.call(t)]}var o=e(7),u=e(3),i="[object Arguments]",c="[object Array]",a="[object Boolean]",s="[object Date]",f="[object Error]",l="[object Function]",p="[object Map]",d="[object Number]",v="[object Object]",y="[object RegExp]",x="[object Set]",m="[object String]",h="[object WeakMap]",g="[object ArrayBuffer]",b="[object Float32Array]",j="[object Float64Array]",_="[object Int8Array]",A="[object Int16Array]",O="[object Int32Array]",w="[object Uint8Array]",M="[object Uint8ClampedArray]",S="[object Uint16Array]",P="[object Uint32Array]",U={};U[b]=U[j]=U[_]=U[A]=U[O]=U[w]=U[M]=U[S]=U[P]=!0,U[i]=U[c]=U[g]=U[a]=U[s]=U[f]=U[l]=U[p]=U[d]=U[v]=U[y]=U[x]=U[m]=U[h]=!1;var C=Object.prototype,E=C.toString;t.exports=n},function(t,r,e){"use strict";var n=e(49),o=e(28),u=e(35),i=u(function(t,r,e){return e?n(t,r,e):o(t,r)});t.exports=i},function(t,r,e){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t){return t&&g["default"](t.then)}function u(){for(var t=[],r=arguments.length,e=Array(r),n=0;r>n;n++)e[n]=arguments[n];return g["default"](e[0])?m["default"](e,function(r){g["default"](r)&&t.push(r)}):j["default"](e[0])&&m["default"](e,function(r){m["default"](r,function(r){g["default"](r)&&t.push(r)})}),t}function i(t){for(var r=arguments.length,e=Array(r>1?r-1:0),n=1;r>n;n++)e[n-1]=arguments[n];return _.mixinFunctions.apply(void 0,[t.methods].concat(e))}function c(t){for(var r=arguments.length,e=Array(r>1?r-1:0),n=1;r>n;n++)e[n-1]=arguments[n];return t.refs=t.state=_.mixin.apply(void 0,[t.refs].concat(e)),t.refs}function a(t){for(var r=arguments.length,e=Array(r>1?r-1:0),n=1;r>n;n++)e[n-1]=arguments[n];var o=u.apply(void 0,e);return t.init=t.enclose=t.init.concat(o),t.init}function s(t){for(var r=arguments.length,e=Array(r>1?r-1:0),n=1;r>n;n++)e[n-1]=arguments[n];return _.merge.apply(void 0,[t.props].concat(e))}function f(t){for(var r=arguments.length,e=Array(r>1?r-1:0),n=1;r>n;n++)e[n-1]=arguments[n];return _.mixin.apply(void 0,[t["static"]].concat(e))}function l(t,r){for(var e=O(t),n=arguments.length,o=Array(n>2?n-2:0),u=2;n>u;u++)o[u-2]=arguments[u];return r.apply(void 0,[e.fixed].concat(o)),e}function p(){for(var t=O(),r=arguments.length,e=Array(r),n=0;r>n;n++)e[n]=arguments[n];return m["default"](e,function(r){r&&r.fixed&&(i(t.fixed,r.fixed.methods),c(t.fixed,r.fixed.refs||r.fixed.state),a(t.fixed,r.fixed.init||r.fixed.enclose),s(t.fixed,r.fixed.props),f(t.fixed,r.fixed["static"]))}),_.mixin(t,t.fixed["static"])}function d(t){return g["default"](t)&&g["default"](t.methods)&&(g["default"](t.refs)||g["default"](t.state))&&(g["default"](t.init)||g["default"](t.enclose))&&g["default"](t.props)&&g["default"](t["static"])&&j["default"](t.fixed)}function v(t){var r=O();return r.fixed.refs=r.fixed.state=_.mergeChainNonFunctions(r.fixed.refs,t.prototype),_.mixin(r,_.mixin(r.fixed["static"],t)),_.mixinChainFunctions(r.fixed.methods,t.prototype),a(r.fixed,function(r){var e=r.instance,n=r.args;return t.apply(e,n)}),r}function y(t){for(var r=O(),e=arguments.length,n=Array(e>1?e-1:0),o=1;e>o;o++)n[o-1]=arguments[o];return t.apply(void 0,[r.fixed].concat(n)),r}Object.defineProperty(r,"__esModule",{value:!0});var x=e(26),m=n(x),h=e(14),g=n(h),b=e(2),j=n(b),_=e(96),A=Object.create,O=function(t){var r={methods:{},refs:{},init:[],props:{},"static":{}};r.state=r.refs,r.enclose=r.init,t&&(i(r,t.methods),c(r,t.refs),a(r,t.init),s(r,t.props),f(r,t["static"]));var e=function(t){for(var n=arguments.length,u=Array(n>1?n-1:0),i=1;n>i;i++)u[i-1]=arguments[i];var c=_.mixin(A(r.methods),r.refs,t);_.mergeUnique(c,r.props);var a=null;return r.init.length>0&&m["default"](r.init,function(t){if(g["default"](t))if(a)a=a.then(function(r){c=r||c;var n=t.call(c,{args:u,instance:c,stamp:e});return n?o(n)?n:c=n:c});else{var r=t.call(c,{args:u,instance:c,stamp:e});if(!r)return;if(!o(r))return void(c=r);a=r}}),a?a.then(function(t){return t||c}):c},n=l.bind(null,r,c),u=l.bind(null,r,a);return _.mixin(e,{create:e,fixed:r,methods:l.bind(null,r,i),refs:n,state:n,init:u,enclose:u,props:l.bind(null,r,s),"static":function(){for(var t=arguments.length,r=Array(t),n=0;t>n;n++)r[n]=arguments[n];var o=l.apply(void 0,[e.fixed,f].concat(r));return _.mixin(o,o.fixed["static"])},compose:function(){for(var t=arguments.length,r=Array(t),n=0;t>n;n++)r[n]=arguments[n];return p.apply(void 0,[e].concat(r))}},r["static"])};r["default"]=_.mixin(O,{methods:y.bind(null,i),refs:y.bind(null,c),init:y.bind(null,a),props:y.bind(null,s),"static":function(){for(var t=arguments.length,r=Array(t),e=0;t>e;e++)r[e]=arguments[e];var n=y.apply(void 0,[f].concat(r));return _.mixin(n,n.fixed["static"])},compose:p,mixin:_.mixin,extend:_.mixin,mixIn:_.mixin,assign:_.mixin,isStamp:d,convertConstructor:v}),t.exports=r["default"]},function(t,r){"use strict";function e(t){var r=t?t.length:0;return r?t[r-1]:void 0}t.exports=e},function(t,r,e){"use strict";var n=e(17),o=e(53),u=e(68),i=u(n,o);t.exports=i},function(t,r){"use strict";function e(t,r){var e=-1,n=t.length;for(r||(r=Array(n));++er&&(r=-r>o?0:o+r),e=void 0===e||e>o?o:+e||0,0>e&&(e+=o),o=r>e?0:e-r>>>0,r>>>=0;for(var u=Array(o);++n2?e[i-2]:void 0,a=i>2?e[2]:void 0,s=i>1?e[i-1]:void 0;for("function"==typeof c?(c=o(c,s,5),i-=2):(c="function"==typeof s?s:void 0,i-=c?1:0),a&&u(e[0],e[1],a)&&(c=3>i?void 0:c,i=1);++n=0}),e=e.init(o).refs(n).methods(u)["static"](c),e.compose=m.compose,m.stripStamp(e))}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=o;var u=e(23),i=n(u),c=e(40),a=n(c),s=e(83),f=n(s),l=e(41),p=n(l),d=e(94),v=n(d),y=e(24),x=n(y),m=e(15);t.exports=r["default"]},function(t,r){"use strict";function e(t){return null==t?"":t+""}t.exports=e},function(t,r,e){(function(r){"use strict";function n(t){var r=t?t.length:0;for(this.data={hash:c(null),set:new i};r--;)this.push(t[r])}var o=e(64),u=e(12),i=u(r,"Set"),c=u(Object,"create");n.prototype.push=o,t.exports=n}).call(r,function(){return this}())},function(t,r){"use strict";function e(t,r){for(var e=-1,n=t.length,o=Array(n);++e=c?i(r):null,p=r.length;l&&(s=u,f=!1,r=l);t:for(;++as))return!1;for(;++a1?n-1:0),u=1;n>u;u++)o[u-1]=arguments[u];if(v["default"](r)||!t.noOverwrite&&!p["default"](r))return o.length>1?t._innerMixer.apply(t,[{}].concat(o)):f["default"](o[0]);if(t.noOverwrite&&(!p["default"](r)||!p["default"](o[0])))return r;var c=t.chain?a["default"]:i["default"];return o.forEach(function(t){c(t,e)}),r}}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=o;var u=e(90),i=n(u),c=e(89),a=n(c),s=e(82),f=n(s),l=e(2),p=n(l),d=e(87),v=n(d);t.exports=r["default"]},function(t,r,e){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t,r){var e=void 0;return e=v["default"](r,function(r,e){if("function"==typeof r)switch(A[e]){case"many":return function(){t[e]&&t[e].apply(this,arguments),r.apply(this,arguments)};case"many_merged_dupe":return function(){var n=t[e]&&t[e].apply(this,arguments),o=r.apply(this,arguments);return n?a["default"](n,o,_):o};case"once":default:if(!t[e])return r;throw new TypeError("Cannot mixin `"+e+"` because it has a unique constraint.")}}),a["default"]({},t,e)}function u(t,r){var e=a["default"]({},t);return f["default"](r,function(t,r){"many_merged_dupe"===A[r]?e[r]=a["default"]({},e[r],t,_):"many_merged"===A[r]?e[r]=a["default"]({},e[r],t):e[r]=t}),e}function i(){for(var t=h["default"](),r={state:{}},e=[],n={},c={},s=arguments.length,l=Array(s),d=0;s>d;d++)l[d]=arguments[d];return j.isStamp(this)&&l.push(this),f["default"](l,function(i){i=j.isStamp(i)?i:b["default"](null,i),e=e.concat(i.fixed.init),n=o(n,i.fixed.methods),c=u(c,x["default"](i,function(r,e){return p["default"](t,e)})),i.fixed.refs&&i.fixed.refs.state&&a["default"](r.state,i.fixed.refs.state,_)}),t=t.init(e).refs(r).methods(n)["static"](c),t.compose=i,j.stripStamp(t)}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=i;var c=e(23),a=n(c),s=e(26),f=n(s),l=e(40),p=n(l),d=e(91),v=n(d),y=e(41),x=n(y),m=e(24),h=n(m),g=e(43),b=n(g),j=e(15),_=function(t,r,e,n){if(n[e])throw new TypeError("Cannot mixin key `"+e+"` because it is provided by multiple sources.");return r},A={propTypes:"many_merged_dupe",defaultProps:"many_merged_dupe",contextTypes:"many_merged",childContextTypes:"many_merged",getChildContext:"many_merged_dupe",render:"once",componentWillMount:"many",componentDidMount:"many",componentWillReceiveProps:"many",shouldComponentUpdate:"once",componentWillUpdate:"many",componentDidUpdate:"many",componentWillUnmount:"many"};t.exports=r["default"]},function(t,r,e){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t){var r=Object.getOwnPropertyNames(t),e=Object.keys(t),n={};return r.forEach(function(r){var o=e.indexOf(r);-1===o&&"constructor"!==r&&(n[r]=t[r])}),n}function u(t){var r=Object.keys(t),e={};return r.forEach(function(r){e[r]=t[r]}),e}function i(t){var r=function(){f["default"](this,new t)},e=a["default"]({},Object.getPrototypeOf(t).prototype,o(t.prototype)),n=p["default"].init(r).methods(e)["static"](u(t));return n.compose=d.compose,d.stripStamp(n)}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=i;var c=e(23),a=n(c),s=e(92),f=n(s),l=e(24),p=n(l),d=e(15);t.exports=r["default"]},function(t,r,e){"use strict";function n(t){var r=++u;return o(t)+r}var o=e(44),u=0;t.exports=n},function(t,r,e){"use strict";function n(t){if(t&&t.__esModule)return t;var r={};if(null!=t)for(var e in t)Object.prototype.hasOwnProperty.call(t,e)&&(r[e]=t[e]);return r["default"]=t,r}function o(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(r,"__esModule",{value:!0});var u=e(43),i=o(u),c=e(102),a=n(c),s=e(15);i["default"].addons={cache:a,compose:s.compose,isStamp:s.isStamp,stamp:s.stamp},r["default"]=i["default"],t.exports=r["default"]},function(t,r,e){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t){return a[t]}function u(t,r){return r&&(a[r]=t),t}Object.defineProperty(r,"__esModule",{value:!0}),r.find=o,r.save=u;var i=e(100),c=n(i),a={};r.uniqueId=c["default"]}])}); -------------------------------------------------------------------------------- /dist/react-stampit.min.js: -------------------------------------------------------------------------------- 1 | !function(t,r){"object"==typeof exports&&"object"==typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):"object"==typeof exports?exports.stampit=r():t.stampit=r()}(this,function(){return function(t){function r(n){if(e[n])return e[n].exports;var o=e[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports,o,o.exports,r),o.loaded=!0,o.exports}var e={};return r.m=t,r.c=e,r.p="",r(0)}([function(t,r,e){t.exports=e(43)},function(t,r,e){"use strict";var n=e(12),o=e(7),u=e(3),i="[object Array]",c=Object.prototype,a=c.toString,s=n(Array,"isArray"),f=s||function(t){return u(t)&&o(t.length)&&a.call(t)==i};t.exports=f},function(t,r){"use strict";function e(t){var r=typeof t;return!!t&&("object"==r||"function"==r)}t.exports=e},function(t,r){"use strict";function e(t){return!!t&&"object"==typeof t}t.exports=e},function(t,r,e){"use strict";function n(t){return o(t)?t:Object(t)}var o=e(2);t.exports=n},function(t,r,e){"use strict";function n(t,r,e){if("function"!=typeof t)return o;if(void 0===r)return t;switch(e){case 1:return function(e){return t.call(r,e)};case 3:return function(e,n,o){return t.call(r,e,n,o)};case 4:return function(e,n,o,u){return t.call(r,e,n,o,u)};case 5:return function(e,n,o,u,i){return t.call(r,e,n,o,u,i)}}return function(){return t.apply(r,arguments)}}var o=e(42);t.exports=n},function(t,r,e){"use strict";function n(t){return null!=t&&u(o(t))}var o=e(36),u=e(7);t.exports=n},function(t,r){"use strict";function e(t){return"number"==typeof t&&t>-1&&t%1==0&&n>=t}var n=9007199254740991;t.exports=e},function(t,r,e){"use strict";function n(t){return u(t)&&o(t)&&c.call(t,"callee")&&!a.call(t,"callee")}var o=e(6),u=e(3),i=Object.prototype,c=i.hasOwnProperty,a=i.propertyIsEnumerable;t.exports=n},function(t,r,e){"use strict";var n=e(12),o=e(6),u=e(2),i=e(81),c=n(Object,"keys"),a=c?function(t){var r=null==t?void 0:t.constructor;return"function"==typeof r&&r.prototype===t||"function"!=typeof t&&o(t)?i(t):u(t)?c(t):[]}:i;t.exports=a},function(t,r,e){"use strict";function n(t){if(null==t)return[];a(t)||(t=Object(t));var r=t.length;r=r&&c(r)&&(u(t)||o(t))&&r||0;for(var e=t.constructor,n=-1,s="function"==typeof e&&e.prototype===t,l=Array(r),p=r>0;++n-1&&t%1==0&&r>t}var n=/^\d+$/,o=9007199254740991;t.exports=e},function(t,r,e){"use strict";function n(t){return o(t)&&c.call(t)==u}var o=e(2),u="[object Function]",i=Object.prototype,c=i.toString;t.exports=n},function(t,r,e){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t){return"function"==typeof t&&"function"==typeof t.compose&&"object"==typeof t.fixed}function u(t){return delete t.create,delete t.init,delete t.methods,delete t.state,delete t.refs,delete t.props,delete t.enclose,delete t["static"],t}Object.defineProperty(r,"__esModule",{value:!0}),r.isStamp=o,r.stripStamp=u;var i=e(98),c=n(i),a=e(99),s=n(a);r.compose=c["default"],r.stamp=s["default"]},function(t,r){"use strict";function e(t,r){if("function"!=typeof t)throw new TypeError(n);return r=o(void 0===r?t.length-1:+r||0,0),function(){for(var e=arguments,n=-1,u=o(e.length-r,0),i=Array(u);++nn;)t=t[r[n++]];return n&&n==u?t:void 0}}var o=e(4);t.exports=n},function(t,r,e){"use strict";function n(t,r){var e=typeof t;if("string"==e&&c.test(t)||"number"==e)return!0;if(o(t))return!1;var n=!i.test(t);return n||null!=r&&t in u(r)}var o=e(1),u=e(4),i=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,c=/^\w*$/;t.exports=n},function(t,r,e){"use strict";function n(t){if(u(t))return t;var r=[];return o(t).replace(i,function(t,e,n,o){r.push(n?o.replace(c,"$1"):e||t)}),r}var o=e(44),u=e(1),i=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,c=/\\(\\)?/g;t.exports=n},function(t,r,e){"use strict";function n(t){return u(t)&&o(t.length)&&!!U[E.call(t)]}var o=e(7),u=e(3),i="[object Arguments]",c="[object Array]",a="[object Boolean]",s="[object Date]",f="[object Error]",l="[object Function]",p="[object Map]",d="[object Number]",v="[object Object]",y="[object RegExp]",x="[object Set]",m="[object String]",h="[object WeakMap]",g="[object ArrayBuffer]",b="[object Float32Array]",j="[object Float64Array]",A="[object Int8Array]",_="[object Int16Array]",O="[object Int32Array]",w="[object Uint8Array]",S="[object Uint8ClampedArray]",M="[object Uint16Array]",P="[object Uint32Array]",U={};U[b]=U[j]=U[A]=U[_]=U[O]=U[w]=U[S]=U[M]=U[P]=!0,U[i]=U[c]=U[g]=U[a]=U[s]=U[f]=U[l]=U[p]=U[d]=U[v]=U[y]=U[x]=U[m]=U[h]=!1;var C=Object.prototype,E=C.toString;t.exports=n},function(t,r,e){"use strict";var n=e(49),o=e(28),u=e(35),i=u(function(t,r,e){return e?n(t,r,e):o(t,r)});t.exports=i},function(t,r,e){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t){return t&&g["default"](t.then)}function u(){for(var t=[],r=arguments.length,e=Array(r),n=0;r>n;n++)e[n]=arguments[n];return g["default"](e[0])?m["default"](e,function(r){g["default"](r)&&t.push(r)}):j["default"](e[0])&&m["default"](e,function(r){m["default"](r,function(r){g["default"](r)&&t.push(r)})}),t}function i(t){for(var r=arguments.length,e=Array(r>1?r-1:0),n=1;r>n;n++)e[n-1]=arguments[n];return A.mixinFunctions.apply(void 0,[t.methods].concat(e))}function c(t){for(var r=arguments.length,e=Array(r>1?r-1:0),n=1;r>n;n++)e[n-1]=arguments[n];return t.refs=t.state=A.mixin.apply(void 0,[t.refs].concat(e)),t.refs}function a(t){for(var r=arguments.length,e=Array(r>1?r-1:0),n=1;r>n;n++)e[n-1]=arguments[n];var o=u.apply(void 0,e);return t.init=t.enclose=t.init.concat(o),t.init}function s(t){for(var r=arguments.length,e=Array(r>1?r-1:0),n=1;r>n;n++)e[n-1]=arguments[n];return A.merge.apply(void 0,[t.props].concat(e))}function f(t){for(var r=arguments.length,e=Array(r>1?r-1:0),n=1;r>n;n++)e[n-1]=arguments[n];return A.mixin.apply(void 0,[t["static"]].concat(e))}function l(t,r){for(var e=O(t),n=arguments.length,o=Array(n>2?n-2:0),u=2;n>u;u++)o[u-2]=arguments[u];return r.apply(void 0,[e.fixed].concat(o)),e}function p(){for(var t=O(),r=arguments.length,e=Array(r),n=0;r>n;n++)e[n]=arguments[n];return m["default"](e,function(r){r&&r.fixed&&(i(t.fixed,r.fixed.methods),c(t.fixed,r.fixed.refs||r.fixed.state),a(t.fixed,r.fixed.init||r.fixed.enclose),s(t.fixed,r.fixed.props),f(t.fixed,r.fixed["static"]))}),A.mixin(t,t.fixed["static"])}function d(t){return g["default"](t)&&g["default"](t.methods)&&(g["default"](t.refs)||g["default"](t.state))&&(g["default"](t.init)||g["default"](t.enclose))&&g["default"](t.props)&&g["default"](t["static"])&&j["default"](t.fixed)}function v(t){var r=O();return r.fixed.refs=r.fixed.state=A.mergeChainNonFunctions(r.fixed.refs,t.prototype),A.mixin(r,A.mixin(r.fixed["static"],t)),A.mixinChainFunctions(r.fixed.methods,t.prototype),a(r.fixed,function(r){var e=r.instance,n=r.args;return t.apply(e,n)}),r}function y(t){for(var r=O(),e=arguments.length,n=Array(e>1?e-1:0),o=1;e>o;o++)n[o-1]=arguments[o];return t.apply(void 0,[r.fixed].concat(n)),r}Object.defineProperty(r,"__esModule",{value:!0});var x=e(26),m=n(x),h=e(14),g=n(h),b=e(2),j=n(b),A=e(96),_=Object.create,O=function(t){var r={methods:{},refs:{},init:[],props:{},"static":{}};r.state=r.refs,r.enclose=r.init,t&&(i(r,t.methods),c(r,t.refs),a(r,t.init),s(r,t.props),f(r,t["static"]));var e=function(t){for(var n=arguments.length,u=Array(n>1?n-1:0),i=1;n>i;i++)u[i-1]=arguments[i];var c=A.mixin(_(r.methods),r.refs,t);A.mergeUnique(c,r.props);var a=null;return r.init.length>0&&m["default"](r.init,function(t){if(g["default"](t))if(a)a=a.then(function(r){c=r||c;var n=t.call(c,{args:u,instance:c,stamp:e});return n?o(n)?n:c=n:c});else{var r=t.call(c,{args:u,instance:c,stamp:e});if(!r)return;if(!o(r))return void(c=r);a=r}}),a?a.then(function(t){return t||c}):c},n=l.bind(null,r,c),u=l.bind(null,r,a);return A.mixin(e,{create:e,fixed:r,methods:l.bind(null,r,i),refs:n,state:n,init:u,enclose:u,props:l.bind(null,r,s),"static":function(){for(var t=arguments.length,r=Array(t),n=0;t>n;n++)r[n]=arguments[n];var o=l.apply(void 0,[e.fixed,f].concat(r));return A.mixin(o,o.fixed["static"])},compose:function(){for(var t=arguments.length,r=Array(t),n=0;t>n;n++)r[n]=arguments[n];return p.apply(void 0,[e].concat(r))}},r["static"])};r["default"]=A.mixin(O,{methods:y.bind(null,i),refs:y.bind(null,c),init:y.bind(null,a),props:y.bind(null,s),"static":function(){for(var t=arguments.length,r=Array(t),e=0;t>e;e++)r[e]=arguments[e];var n=y.apply(void 0,[f].concat(r));return A.mixin(n,n.fixed["static"])},compose:p,mixin:A.mixin,extend:A.mixin,mixIn:A.mixin,assign:A.mixin,isStamp:d,convertConstructor:v}),t.exports=r["default"]},function(t,r){"use strict";function e(t){var r=t?t.length:0;return r?t[r-1]:void 0}t.exports=e},function(t,r,e){"use strict";var n=e(17),o=e(53),u=e(68),i=u(n,o);t.exports=i},function(t,r){"use strict";function e(t,r){var e=-1,n=t.length;for(r||(r=Array(n));++er&&(r=-r>o?0:o+r),e=void 0===e||e>o?o:+e||0,0>e&&(e+=o),o=r>e?0:e-r>>>0,r>>>=0;for(var u=Array(o);++n2?e[i-2]:void 0,a=i>2?e[2]:void 0,s=i>1?e[i-1]:void 0;for("function"==typeof c?(c=o(c,s,5),i-=2):(c="function"==typeof s?s:void 0,i-=c?1:0),a&&u(e[0],e[1],a)&&(c=3>i?void 0:c,i=1);++n=0}),e=e.init(o).refs(n).methods(u)["static"](c),e.compose=m.compose,m.stripStamp(e))}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=o;var u=e(23),i=n(u),c=e(40),a=n(c),s=e(83),f=n(s),l=e(41),p=n(l),d=e(94),v=n(d),y=e(24),x=n(y),m=e(15);t.exports=r["default"]},function(t,r){"use strict";function e(t){return null==t?"":t+""}t.exports=e},function(t,r,e){(function(r){"use strict";function n(t){var r=t?t.length:0;for(this.data={hash:c(null),set:new i};r--;)this.push(t[r])}var o=e(64),u=e(12),i=u(r,"Set"),c=u(Object,"create");n.prototype.push=o,t.exports=n}).call(r,function(){return this}())},function(t,r){"use strict";function e(t,r){for(var e=-1,n=t.length,o=Array(n);++e=c?i(r):null,p=r.length;l&&(s=u,f=!1,r=l);t:for(;++as))return!1;for(;++a1?n-1:0),u=1;n>u;u++)o[u-1]=arguments[u];if(v["default"](r)||!t.noOverwrite&&!p["default"](r))return o.length>1?t._innerMixer.apply(t,[{}].concat(o)):f["default"](o[0]);if(t.noOverwrite&&(!p["default"](r)||!p["default"](o[0])))return r;var c=t.chain?a["default"]:i["default"];return o.forEach(function(t){c(t,e)}),r}}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=o;var u=e(90),i=n(u),c=e(89),a=n(c),s=e(82),f=n(s),l=e(2),p=n(l),d=e(87),v=n(d);t.exports=r["default"]},function(t,r,e){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t,r){var e=void 0;return e=v["default"](r,function(r,e){if("function"==typeof r)switch(_[e]){case"many":return function(){t[e]&&t[e].apply(this,arguments),r.apply(this,arguments)};case"many_merged_dupe":return function(){var n=t[e]&&t[e].apply(this,arguments),o=r.apply(this,arguments);return n?a["default"](n,o,A):o};case"once":default:if(!t[e])return r;throw new TypeError("Cannot mixin `"+e+"` because it has a unique constraint.")}}),a["default"]({},t,e)}function u(t,r){var e=a["default"]({},t);return f["default"](r,function(t,r){"many_merged_dupe"===_[r]?e[r]=a["default"]({},e[r],t,A):"many_merged"===_[r]?e[r]=a["default"]({},e[r],t):e[r]=t}),e}function i(){for(var t=h["default"](),r={state:{}},e=[],n={},c={},s=arguments.length,l=Array(s),d=0;s>d;d++)l[d]=arguments[d];return j.isStamp(this)&&l.push(this),f["default"](l,function(i){i=j.isStamp(i)?i:b["default"](null,i),e=e.concat(i.fixed.init),n=o(n,i.fixed.methods),c=u(c,x["default"](i,function(r,e){return p["default"](t,e)})),i.fixed.refs&&i.fixed.refs.state&&a["default"](r.state,i.fixed.refs.state,A)}),t=t.init(e).refs(r).methods(n)["static"](c),t.compose=i,j.stripStamp(t)}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=i;var c=e(23),a=n(c),s=e(26),f=n(s),l=e(40),p=n(l),d=e(91),v=n(d),y=e(41),x=n(y),m=e(24),h=n(m),g=e(43),b=n(g),j=e(15),A=function(t,r,e,n){if(n[e])throw new TypeError("Cannot mixin key `"+e+"` because it is provided by multiple sources.");return r},_={propTypes:"many_merged_dupe",defaultProps:"many_merged_dupe",contextTypes:"many_merged",childContextTypes:"many_merged",getChildContext:"many_merged_dupe",render:"once",componentWillMount:"many",componentDidMount:"many",componentWillReceiveProps:"many",shouldComponentUpdate:"once",componentWillUpdate:"many",componentDidUpdate:"many",componentWillUnmount:"many"};t.exports=r["default"]},function(t,r,e){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t){var r=Object.getOwnPropertyNames(t),e=Object.keys(t),n={};return r.forEach(function(r){var o=e.indexOf(r);-1===o&&"constructor"!==r&&(n[r]=t[r])}),n}function u(t){var r=Object.keys(t),e={};return r.forEach(function(r){e[r]=t[r]}),e}function i(t){var r=function(){f["default"](this,new t)},e=a["default"]({},Object.getPrototypeOf(t).prototype,o(t.prototype)),n=p["default"].init(r).methods(e)["static"](u(t));return n.compose=d.compose,d.stripStamp(n)}Object.defineProperty(r,"__esModule",{value:!0}),r["default"]=i;var c=e(23),a=n(c),s=e(92),f=n(s),l=e(24),p=n(l),d=e(15);t.exports=r["default"]}])}); -------------------------------------------------------------------------------- /docs/advanced.md: -------------------------------------------------------------------------------- 1 | ## Advanced use cases 2 | 3 | ### Class decorator 4 | 5 | For all those nice guys/gals that like `class` and just want some mixability. It is assumed that the component directly extends React.Component, anything else should be inherited via stamp composition. 6 | 7 | ```js 8 | import React from 'react/addons'; 9 | import stamp from 'react-stampit/utils/decorator'; 10 | 11 | @stamp 12 | class Component extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | 16 | this.state = { 17 | foo: 'foo', 18 | }; 19 | } 20 | 21 | render() { 22 | return null; 23 | } 24 | } 25 | 26 | Component.defaultProps = { 27 | foo: 'foo', 28 | }; 29 | 30 | const mixin = { 31 | state: { 32 | bar: 'bar', 33 | }, 34 | 35 | defaultProps: { 36 | bar: 'bar', 37 | }, 38 | }; 39 | 40 | Component = Component.compose(mixin); 41 | ``` 42 | 43 | ```js 44 | assert.deepEqual( 45 | Component().state, { foo: 'foo', bar: 'bar' }, 46 | 'should inherit mixin state' 47 | ); 48 | >> ok 49 | assert.deepEqual( 50 | Component.defaultProps, { foo: 'foo', bar: 'bar' } 51 | 'should inherit `defaultProps` props' 52 | ); 53 | >> ok 54 | ``` 55 | 56 | __*Warning: Class decorators are a proposal for ES2016, they are not set in stone.*__ 57 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | ## API 2 | 3 | ### stampit(React [,descObj]) 4 | 5 | Return a factory function (called a stamp) that will produce new React components using the prototypes that are passed in or composed. 6 | 7 | * `@param {Object} React` The React library. 8 | * `@param {Object} descObj` A map of property names and values specialized for React. 9 | * `@return {Function} stamp` A factory to produce React components using the given properties. 10 | * `@return {Object} stamp.fixed` An object map containing the fixed prototypes. 11 | * `@return {Function} stamp.compose` Add mixin (stamp) to stamp. Chainable. 12 | 13 | ### stamp.compose([arg1] [,arg2] [,arg3...]) 14 | 15 | Take one or more stamps produced from `react-stampit` or `stampit` and 16 | combine them with `this` to produce and return a new stamp. 17 | 18 | * `@param {...Function} stamp` One or more stamps. 19 | * `@return {Function}` A new stamp composed from `this` and arguments. 20 | 21 | ## Utility methods 22 | 23 | ### compose([arg1], [arg2] [,arg3...]) 24 | 25 | Take two or more stamps produced from `react-stampit` or `stampit` and 26 | combine them to produce and return a new stamp. 27 | 28 | * `@param {...Function} stamp` Two or more stamps. 29 | * `@return {Function}` A new stamp composed from arguments. 30 | 31 | ### isStamp(obj) 32 | 33 | Take an object and return true if it's a stamp, false otherwise. 34 | 35 | ## API Differences 36 | 37 | react-stampit utitlizes a stamp description object made specifically for React components. Consider it a long lost relative of [stampit](https://github.com/stampit-org/stampit)'s stamp description object with nothing in common. 38 | 39 | react-stampit has also stripped all of stampit's static methods to enforce an API familiar to React users. Users are encouraged to utilize [React's lifecycle](https://facebook.github.io/react/docs/component-specs.html) and [component properties](#what-is-this) as replacements for these methods. 40 | -------------------------------------------------------------------------------- /docs/composition.md: -------------------------------------------------------------------------------- 1 | ## Composition logic 2 | 3 | Stamp composition might be compared to `React.createClass`'s mixin feature. A significant difference is that you compose a stamp without referencing the stamps being inherited from inside the stamp's declaration. 4 | 5 | 6 | ### State 7 | Composed stamps inherit other stamps' state. State is merged, throwing on duplicate keys. 8 | 9 | ```js 10 | const mixin = { 11 | state: { 12 | foo: 'foo', 13 | bar: 'bar', 14 | }, 15 | }; 16 | 17 | const component = stampit(React).compose(mixin); 18 | ``` 19 | 20 | ```js 21 | assert.deepEqual( 22 | component().state, { foo: 'foo', bar: 'bar }, 23 | 'should inherit state' 24 | ); 25 | >> ok 26 | ``` 27 | 28 | __*`React.createClass` throws an Invariant Violation when duplicate keys are found within mixins. `react-stampit` will throw a TypeError.*__ 29 | 30 | ### Statics 31 | Composed stamps inherit other stamps' statics. React statics are merged, throwing on duplicate keys for `propTypes` and `defaultProps`. Non-React statics override with last-in priority. 32 | 33 | ```js 34 | const mixin = { 35 | statics: { 36 | someStatic: { 37 | bar: 'bar', 38 | }, 39 | }, 40 | 41 | propTypes: { 42 | bar: React.PropTypes.string, 43 | }, 44 | }; 45 | 46 | const component = stampit(React, { 47 | statics: { 48 | someStatic: { 49 | foo: 'foo', 50 | }, 51 | }, 52 | 53 | propTypes: { 54 | foo: React.PropTypes.string, 55 | }, 56 | }).compose(mixin); 57 | ``` 58 | 59 | ```js 60 | assert.ok( 61 | component.propTypes.bar, 62 | 'should inherit bar propType' 63 | ); 64 | >> ok 65 | assert.ok( 66 | component.propTypes.foo, 67 | 'should inherit foo propType' 68 | ); 69 | >> ok 70 | assert.deepEqual( 71 | component.someStatic, { foo: 'foo' }, 72 | 'should override non-React statics' 73 | ); 74 | >> ok 75 | ``` 76 | 77 | __*`React.createClass` throws an Invariant Violation when duplicate keys are found in `getDefaultProps` and `getInitialState`. `react-stampit` will throw a TypeError.*__ 78 | 79 | ### Methods 80 | Composed stamps inherit other stamps' methods. A handful of React methods will be 'wrapped' executing with first-in priority. Any non-React methods or React methods with a unique constraint will throw on duplicates. 81 | 82 | __Wrapped__ 83 | 84 | * componentWillMount 85 | * componentDidMount 86 | * componentWillReceiveProps 87 | * componentWillUpdate 88 | * componentDidUpdate 89 | * componentWillUnmount 90 | * getChildContext 91 | 92 | __Unique__ 93 | 94 | * shouldComponentUpdate 95 | * render 96 | 97 | ```js 98 | const mixin = { 99 | componentDidMount() { 100 | this.state.mixin = true; 101 | }, 102 | }; 103 | 104 | const component = stampit(React, { 105 | state: { 106 | component: false, 107 | mixin: false, 108 | }, 109 | 110 | componentDidMount() { 111 | this.state.component = true; 112 | } 113 | }).compose(mixin); 114 | 115 | const instance = component(); 116 | instance.componentDidMount(); 117 | ``` 118 | 119 | ```js 120 | assert.deepEqual( 121 | instance.state, { component: true, mixin: true }, 122 | 'should inherit functionality of mixable methods' 123 | ); 124 | >> ok 125 | ``` 126 | __*`React.createClass` throws an Invariant Violation when duplicate `shouldComponentUpdate` or `render` methods exist, `react-stampit` will throw a TypeError.*__ 127 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-stampit", 3 | "version": "0.9.0", 4 | "description": "A specialized stampit factory for React.", 5 | "main": "lib", 6 | "files": [ 7 | "dist", 8 | "lib" 9 | ], 10 | "scripts": { 11 | "build": "npm run clean && npm run transpile && npm run build-browser", 12 | "build-browser": "webpack && MINIFY=1 webpack -p", 13 | "clean": "rimraf lib", 14 | "lint": "eslint src test", 15 | "prepublish": "npm run test && npm run build", 16 | "test": "npm run lint && babel-node --stage 1 test/test.js", 17 | "transpile": "babel --stage 1 src --out-dir lib" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/stampit-org/react-stampit.git" 22 | }, 23 | "keywords": [ 24 | "composability", 25 | "mixins", 26 | "react", 27 | "stamp", 28 | "stampit" 29 | ], 30 | "author": "Tim Routowicz ", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/stampit-org/react-stampit/issues" 34 | }, 35 | "homepage": "https://github.com/stampit-org/react-stampit", 36 | "dependencies": { 37 | "lodash": "^3.10.1", 38 | "stampit": "^2.1.0" 39 | }, 40 | "devDependencies": { 41 | "babel": "^5.8.21", 42 | "babel-eslint": "^4.0.5", 43 | "babel-loader": "^5.3.2", 44 | "eslint": "^1.1.0", 45 | "node-libs-browser": "^0.5.2", 46 | "react": "^0.13.3", 47 | "rewire": "^2.3.4", 48 | "rimraf": "^2.4.2", 49 | "tape": "^4.1.0", 50 | "webpack": "^1.11.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/addons.js: -------------------------------------------------------------------------------- 1 | import stampit from './'; 2 | import * as cache from './utils/cache'; 3 | import { compose, isStamp, stamp } from './utils'; 4 | 5 | stampit.addons = { 6 | cache, 7 | compose, 8 | isStamp, 9 | stamp, 10 | }; 11 | 12 | export default stampit; 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import assign from 'lodash/object/assign'; 2 | import has from 'lodash/object/has'; 3 | import isEmpty from 'lodash/lang/isEmpty'; 4 | import omit from 'lodash/object/omit'; 5 | import pick from 'lodash/object/pick'; 6 | import stampit from 'stampit'; 7 | 8 | import { compose, stripStamp } from './utils'; 9 | 10 | /** 11 | * Return a factory function (called a stamp) that will produce new 12 | * React components using the prototypes that are passed in or composed. 13 | * 14 | * @param {Object} React The React library. 15 | * @param {Object} [props] A map of property names and values specialized for React. 16 | * @return {Function} stamp A factory to produce React components using the given properties. 17 | * @return {Object} stamp.fixed An object map containing the fixed prototypes. 18 | */ 19 | export default function rStampit(React, props) { 20 | let stamp = React ? stampit.convertConstructor(React.Component) : stampit(); 21 | let refs, init, methods, statics; 22 | 23 | // Shortcut for converting React's class to a stamp. 24 | if (isEmpty(props)) { 25 | stamp.compose = compose; 26 | return stripStamp(stamp); 27 | } 28 | 29 | init = props.init || []; 30 | refs = props.state && { state: props.state } || {}; 31 | statics = assign({}, 32 | props.statics, 33 | pick(props, ['contextTypes', 'childContextTypes', 'propTypes', 'defaultProps']), 34 | { displayName: props.displayName || 'ReactStamp' } 35 | ); 36 | methods = omit(props, (val, key) => has(statics, key) || ['init', 'state', 'statics'].indexOf(key) >= 0); 37 | 38 | stamp = stamp 39 | .init(init) 40 | .refs(refs) 41 | .methods(methods) 42 | .static(statics); 43 | stamp.compose = compose; 44 | 45 | return stripStamp(stamp); 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/cache.js: -------------------------------------------------------------------------------- 1 | import uniqueId from 'lodash/utility/uniqueId'; 2 | 3 | let cache = {}; 4 | 5 | export function find(id) { 6 | return cache[id]; 7 | } 8 | 9 | export function save(val, id) { 10 | if (id) cache[id] = val; 11 | 12 | return val; 13 | } 14 | 15 | export { uniqueId }; 16 | -------------------------------------------------------------------------------- /src/utils/compose.js: -------------------------------------------------------------------------------- 1 | import assign from 'lodash/object/assign'; 2 | import forEach from 'lodash/collection/forEach'; 3 | import has from 'lodash/object/has'; 4 | import mapValues from 'lodash/object/mapValues'; 5 | import omit from 'lodash/object/omit'; 6 | import stampit from 'stampit'; 7 | 8 | import rStampit from '../'; 9 | import { isStamp, stripStamp } from './'; 10 | 11 | const dupeFilter = function (prev, next, key, targ) { 12 | if (targ[key]) { 13 | throw new TypeError('Cannot mixin key `' + key + '` because it is provided by multiple sources.'); 14 | } 15 | 16 | return next; 17 | }; 18 | 19 | /** 20 | * React specification for creating new components 21 | */ 22 | const reactSpec = { 23 | propTypes: 'many_merged_dupe', 24 | defaultProps: 'many_merged_dupe', 25 | contextTypes: 'many_merged', 26 | childContextTypes: 'many_merged', 27 | getChildContext: 'many_merged_dupe', 28 | render: 'once', 29 | componentWillMount: 'many', 30 | componentDidMount: 'many', 31 | componentWillReceiveProps: 'many', 32 | shouldComponentUpdate: 'once', 33 | componentWillUpdate: 'many', 34 | componentDidUpdate: 'many', 35 | componentWillUnmount: 'many', 36 | }; 37 | 38 | /** 39 | * Iterate through stamp methods, creating wrapper 40 | * functions for mixable React methods, starting 41 | * execution with first-in. 42 | * 43 | * @param {Object} targ Method destination 44 | * @param {Object} src New methods 45 | * @return {Object} An object of methods 46 | */ 47 | function wrapMethods(targ, src) { 48 | let methods; 49 | 50 | methods = mapValues(src, (val, key) => { 51 | if (typeof val === 'function') { 52 | switch (reactSpec[key]) { 53 | case 'many': 54 | return function () { 55 | /* eslint-disable no-unused-expressions */ 56 | targ[key] && targ[key].apply(this, arguments); 57 | val.apply(this, arguments); 58 | /* eslint-disable no-unused-expressions */ 59 | }; 60 | case 'many_merged_dupe': 61 | return function () { 62 | const res1 = targ[key] && targ[key].apply(this, arguments); 63 | const res2 = val.apply(this, arguments); 64 | 65 | return res1 ? assign(res1, res2, dupeFilter) : res2; 66 | }; 67 | case 'once': 68 | default: 69 | if (!targ[key]) { 70 | return val; 71 | } 72 | 73 | throw new TypeError('Cannot mixin `' + key + '` because it has a unique constraint.'); 74 | } 75 | } 76 | }); 77 | 78 | return assign({}, targ, methods); 79 | } 80 | 81 | /** 82 | * Process the static properties of a stamp and 83 | * combine the result with the passed in statics object. 84 | * 85 | * @param {Object} stamp A stamp 86 | * @param {Object} prev An object of past static properties 87 | * @return {Object} A processed object of static properties 88 | */ 89 | function extractStatics(targ, src) { 90 | let statics = assign({}, targ); 91 | 92 | forEach(src, (val, key) => { 93 | if (reactSpec[key] === 'many_merged_dupe') { 94 | statics[key] = assign({}, statics[key], val, dupeFilter); 95 | } else if (reactSpec[key] === 'many_merged') { 96 | statics[key] = assign({}, statics[key], val); 97 | } else { 98 | statics[key] = val; 99 | } 100 | }); 101 | 102 | return statics; 103 | } 104 | 105 | /** 106 | * Take two or more stamps produced from react-stampit or 107 | * stampit and combine them to produce and return a new stamp. 108 | * 109 | * @param {...Function} stamp Two or more stamps. 110 | * @return {Function} A new stamp composed from arguments. 111 | */ 112 | export default function compose(...stamps) { 113 | let result = stampit(), 114 | refs = { state: {} }, 115 | init = [], methods = {}, statics = {}; 116 | 117 | if (isStamp(this)) stamps.push(this); 118 | 119 | forEach(stamps, stamp => { 120 | stamp = !isStamp(stamp) ? rStampit(null, stamp) : stamp; // eslint-disable-line 121 | 122 | init = init.concat(stamp.fixed.init); 123 | methods = wrapMethods(methods, stamp.fixed.methods); 124 | statics = extractStatics(statics, omit(stamp, (val, key) => has(result, key))); 125 | 126 | if (stamp.fixed.refs && stamp.fixed.refs.state) { 127 | assign(refs.state, stamp.fixed.refs.state, dupeFilter); 128 | } 129 | }); 130 | 131 | result = result 132 | .init(init) 133 | .refs(refs) 134 | .methods(methods) 135 | .static(statics); 136 | result.compose = compose; 137 | 138 | return stripStamp(result); 139 | } 140 | -------------------------------------------------------------------------------- /src/utils/decorator.js: -------------------------------------------------------------------------------- 1 | import assign from 'lodash/object/assign'; 2 | import merge from 'lodash/object/merge'; 3 | import stampit from 'stampit'; 4 | 5 | import { compose, stripStamp } from './'; 6 | 7 | /** 8 | * Get object of non-enum properties 9 | */ 10 | function getNonEnum(target) { 11 | const props = Object.getOwnPropertyNames(target); 12 | const enumOnly = Object.keys(target); 13 | let obj = {}; 14 | 15 | props.forEach(function(key) { 16 | var indexInEnum = enumOnly.indexOf(key); 17 | if (indexInEnum === -1 && key !== 'constructor') { 18 | obj[key] = target[key]; 19 | } 20 | }); 21 | 22 | return obj; 23 | } 24 | 25 | /** 26 | * Get object of enum properties 27 | */ 28 | function getEnum(target) { 29 | const props = Object.keys(target); 30 | let obj = {}; 31 | 32 | props.forEach(function(key) { 33 | obj[key] = target[key]; 34 | }); 35 | 36 | return obj; 37 | } 38 | 39 | /** 40 | * ES2016 decorator for converting ES2015 class to stamp 41 | */ 42 | export default function stamp(Class) { 43 | const constructor = function() { 44 | merge(this, new Class()); 45 | }; 46 | const methods = assign({}, 47 | Object.getPrototypeOf(Class).prototype, 48 | getNonEnum(Class.prototype) 49 | ); 50 | 51 | let stamp = stampit 52 | .init(constructor) 53 | .methods(methods) 54 | .static(getEnum(Class)); 55 | stamp.compose = compose; 56 | 57 | return stripStamp(stamp); 58 | } 59 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import compose from './compose'; 2 | import stamp from './decorator'; 3 | 4 | export function isStamp(obj) { 5 | return ( 6 | typeof obj === 'function' && 7 | typeof obj.compose === 'function' && 8 | typeof obj.fixed === 'object' 9 | ); 10 | } 11 | 12 | export function stripStamp(stamp) { 13 | delete stamp.create; 14 | delete stamp.init; 15 | delete stamp.methods; 16 | delete stamp.state; 17 | delete stamp.refs; 18 | delete stamp.props; 19 | delete stamp.enclose; 20 | delete stamp.static; 21 | 22 | return stamp; 23 | } 24 | 25 | export { 26 | compose, 27 | stamp, 28 | }; 29 | -------------------------------------------------------------------------------- /test/advanced.js: -------------------------------------------------------------------------------- 1 | import keys from 'lodash/object/keys'; 2 | import React from 'react'; 3 | import test from 'tape'; 4 | 5 | import stampit from '../src'; 6 | import { isStamp, stamp } from '../src/utils'; 7 | import * as cache from '../src/utils/cache'; 8 | 9 | test('stamp decorator', (t) => { 10 | t.plan(4); 11 | 12 | @stamp 13 | class Component extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | 17 | this.state = { 18 | foo: 'foo', 19 | }; 20 | } 21 | 22 | render() { 23 | return null; 24 | } 25 | } 26 | 27 | Component.defaultProps = { 28 | foo: 'foo', 29 | }; 30 | 31 | const mixin = { 32 | state: { 33 | bar: 'bar', 34 | }, 35 | 36 | defaultProps: { 37 | bar: 'bar', 38 | }, 39 | }; 40 | 41 | t.ok(isStamp(Component), 'converts class to stamp'); 42 | /* eslint-disable new-cap */ 43 | t.ok(Component().render, 'maps methods'); 44 | t.deepEqual( 45 | keys(Component.compose(mixin)().state), ['bar', 'foo'], 46 | 'merges state' 47 | ); 48 | t.deepEqual( 49 | keys(Component.compose(mixin).defaultProps), ['bar', 'foo'], 50 | 'merges statics' 51 | ); 52 | /* eslint-enable new-cap */ 53 | }); 54 | 55 | test('stamp factory using `cacheStamp`', (t) => { 56 | t.plan(2); 57 | 58 | const id1 = cache.uniqueId(); 59 | const id2 = cache.uniqueId(); 60 | 61 | const stampFactory1 = React => { 62 | return cache.find(id1) || cache.save( 63 | stampit(React, { 64 | displayName: 'Component', 65 | }), id1 66 | ); 67 | }; 68 | 69 | const stampFactory2 = React => { 70 | return cache.find(id2) || cache.save( 71 | stampit(React) 72 | ); 73 | }; 74 | 75 | t.equal( 76 | stampFactory1(React), stampFactory1(React), 77 | 'is memoized if unique id is defined' 78 | ); 79 | 80 | t.notEqual( 81 | stampFactory2(React), stampFactory2(React), 82 | 'is not memoized if unique id is undefined' 83 | ); 84 | }); 85 | -------------------------------------------------------------------------------- /test/basics.js: -------------------------------------------------------------------------------- 1 | import React from 'react/addons'; 2 | import test from 'tape'; 3 | 4 | import stampit from '../src'; 5 | import { isStamp } from '../src/utils'; 6 | 7 | const TestUtils = React.addons.TestUtils; 8 | 9 | test('stampit()', (t) => { 10 | t.plan(1); 11 | 12 | t.ok( 13 | isStamp(stampit()), 14 | 'should return a stamp' 15 | ); 16 | }); 17 | 18 | test('stampit(React, props)', (t) => { 19 | t.plan(1); 20 | 21 | const stamp = stampit(React, {}); 22 | 23 | t.ok( 24 | isStamp(stamp), 25 | 'should return a stamp' 26 | ); 27 | }); 28 | 29 | test('stampit(React)', (t) => { 30 | t.plan(1); 31 | 32 | const stamp = stampit(React); 33 | 34 | t.ok( 35 | isStamp(stamp), 36 | 'should return a stamp' 37 | ); 38 | }); 39 | 40 | test('stampit(null, props)', (t) => { 41 | t.plan(1); 42 | 43 | const stamp = stampit(null, {}); 44 | 45 | t.ok( 46 | isStamp(stamp), 47 | 'should return a stamp' 48 | ); 49 | }); 50 | 51 | test('stampit(React, { render() })()', (t) => { 52 | t.plan(1); 53 | 54 | const stamp = stampit(React, { 55 | render() {}, 56 | }); 57 | 58 | t.ok( 59 | TestUtils.isCompositeComponent(stamp()), 60 | 'should return a React component' 61 | ); 62 | }); 63 | 64 | test('stampit(React, props).compose', (t) => { 65 | t.plan(1); 66 | 67 | t.equal( 68 | typeof stampit(React).compose, 'function', 69 | 'should be a function' 70 | ); 71 | }); 72 | 73 | test('stampit(React, props).create', (t) => { 74 | t.plan(1); 75 | 76 | t.equal( 77 | typeof stampit(React).create, 'undefined', 78 | 'should be undefined' 79 | ); 80 | }); 81 | 82 | test('stampit(React, props).init', (t) => { 83 | t.plan(1); 84 | 85 | t.equal( 86 | typeof stampit(React).init, 'undefined', 87 | 'should be undefined' 88 | ); 89 | }); 90 | 91 | test('stampit(React, props).methods', (t) => { 92 | t.plan(1); 93 | 94 | t.equal( 95 | typeof stampit(React).methods, 'undefined', 96 | 'should be undefined' 97 | ); 98 | }); 99 | 100 | test('stampit(React, props).state', (t) => { 101 | t.plan(1); 102 | 103 | t.equal( 104 | typeof stampit(React).state, 'undefined', 105 | 'should be undefined' 106 | ); 107 | }); 108 | 109 | test('stampit(React, props).refs', (t) => { 110 | t.plan(1); 111 | 112 | t.equal( 113 | typeof stampit(React).refs, 'undefined', 114 | 'should be undefined' 115 | ); 116 | }); 117 | 118 | test('stampit(React, props).props', (t) => { 119 | t.plan(1); 120 | 121 | t.equal( 122 | typeof stampit(React).props, 'undefined', 123 | 'should be undefined' 124 | ); 125 | }); 126 | 127 | test('stampit(React, props).enclose', (t) => { 128 | t.plan(1); 129 | 130 | t.equal( 131 | typeof stampit(React).enclose, 'undefined', 132 | 'should be undefined' 133 | ); 134 | }); 135 | 136 | test('stampit(React, props).static', (t) => { 137 | t.plan(1); 138 | 139 | t.equal( 140 | typeof stampit(React).static, 'undefined', 141 | 'should be undefined' 142 | ); 143 | }); 144 | -------------------------------------------------------------------------------- /test/compose.js: -------------------------------------------------------------------------------- 1 | import keys from 'lodash/object/keys'; 2 | import React from 'react'; 3 | import test from 'tape'; 4 | 5 | import stampit from '../src'; 6 | import { compose } from '../src/utils'; 7 | 8 | test('stampit(React, props).compose(stamp2)', (t) => { 9 | t.plan(1); 10 | 11 | const mixin = stampit(null, { 12 | method() { 13 | return 'mixin'; 14 | }, 15 | }); 16 | 17 | const stamp = stampit(React).compose(mixin); 18 | 19 | t.equal( 20 | stamp().method(), 'mixin', 21 | 'should return a stamp composed of `this` and passed stamp' 22 | ); 23 | }); 24 | 25 | test('stampit(React, props).compose(pojo)', (t) => { 26 | t.plan(1); 27 | 28 | const mixin = { 29 | method() { 30 | return 'mixin'; 31 | }, 32 | }; 33 | 34 | const stamp = stampit(React).compose(mixin); 35 | 36 | t.equal( 37 | stamp().method(), 'mixin', 38 | 'should return a stamp composed of `this` and passed pojo' 39 | ); 40 | }); 41 | 42 | test('stampit(React, props).compose(stamp2, stamp3)', (t) => { 43 | t.plan(2); 44 | 45 | const mixin1 = stampit(null, { 46 | method() { 47 | return this.state; 48 | }, 49 | }); 50 | 51 | const mixin2 = stampit(null, { 52 | statics: { 53 | util() { 54 | return 'static'; 55 | }, 56 | }, 57 | }); 58 | 59 | const stamp = stampit(React, { 60 | state: { 61 | foo: '', 62 | }, 63 | }).compose(mixin1, mixin2); 64 | 65 | t.deepEqual( 66 | keys(stamp().method()), ['foo'], 67 | 'should expose `this` to inherited methods' 68 | ); 69 | 70 | t.equal( 71 | stamp.util(), 'static', 72 | 'should inherit static methods' 73 | ); 74 | }); 75 | 76 | test('compose(stamp2, stamp1)', (t) => { 77 | t.plan(1); 78 | 79 | const mixin = stampit(null, { 80 | method() { 81 | return 'mixin'; 82 | }, 83 | }); 84 | 85 | const stamp = stampit(React); 86 | 87 | t.equal( 88 | compose(mixin, stamp)().method(), 'mixin', 89 | 'should return a stamp composed of passed stamps' 90 | ); 91 | }); 92 | 93 | test('stamps composed of stamps with state', (t) => { 94 | t.plan(2); 95 | 96 | const mixin = stampit(null, { 97 | state: { 98 | foo: ' ', 99 | }, 100 | }); 101 | 102 | const stamp = stampit(React, { 103 | state: { 104 | bar: ' ', 105 | }, 106 | }).compose(mixin); 107 | 108 | const failStamp = stampit(React, { 109 | state: { 110 | foo: ' ', 111 | }, 112 | }); 113 | 114 | t.deepEqual( 115 | stamp().state, { foo: ' ', bar: ' ' }, 116 | 'should inherit state' 117 | ); 118 | 119 | t.throws( 120 | () => failStamp.compose(mixin), TypeError, 121 | 'should throw on duplicate keys' 122 | ); 123 | }); 124 | 125 | test('stamps composed of stamps with React statics', (t) => { 126 | t.plan(8); 127 | 128 | const mixin = stampit(null, { 129 | contextTypes: { 130 | foo: React.PropTypes.string, 131 | }, 132 | childContextTypes: { 133 | foo: React.PropTypes.string, 134 | }, 135 | propTypes: { 136 | foo: React.PropTypes.string, 137 | }, 138 | defaultProps: { 139 | foo: 'foo', 140 | }, 141 | }); 142 | 143 | const stamp = stampit(React, { 144 | contextTypes: { 145 | bar: React.PropTypes.string, 146 | }, 147 | childContextTypes: { 148 | bar: React.PropTypes.string, 149 | }, 150 | propTypes: { 151 | bar: React.PropTypes.string, 152 | }, 153 | defaultProps: { 154 | bar: 'bar', 155 | }, 156 | }).compose(mixin); 157 | 158 | const failStamp1 = stampit(React, { 159 | propTypes: { 160 | foo: React.PropTypes.string, 161 | }, 162 | }); 163 | 164 | const failStamp2 = stampit(React, { 165 | defaultProps: { 166 | foo: 'foo', 167 | }, 168 | }); 169 | 170 | const okStamp1 = stampit(React, { 171 | contextTypes: { 172 | foo: React.PropTypes.string, 173 | }, 174 | }); 175 | 176 | const okStamp2 = stampit(React, { 177 | childContextTypes: { 178 | foo: React.PropTypes.string, 179 | }, 180 | }); 181 | 182 | t.deepEqual( 183 | keys(stamp.contextTypes), ['foo', 'bar'], 184 | 'should inherit `contextTypes` props' 185 | ); 186 | 187 | t.deepEqual( 188 | keys(stamp.childContextTypes), ['foo', 'bar'], 189 | 'should inherit `childContextTypes` props' 190 | ); 191 | 192 | t.deepEqual( 193 | keys(stamp.propTypes), ['foo', 'bar'], 194 | 'should inherit `propTypes` props' 195 | ); 196 | 197 | t.deepEqual( 198 | keys(stamp.defaultProps), ['foo', 'bar'], 199 | 'should inherit `defaultProps` props' 200 | ); 201 | 202 | t.throws( 203 | () => failStamp1.compose(mixin), TypeError, 204 | 'should throw on duplicate keys in `propTypes`' 205 | ); 206 | 207 | t.throws( 208 | () => failStamp2.compose(mixin), TypeError, 209 | 'should throw on duplicate keys in `defaultProps`' 210 | ); 211 | 212 | t.doesNotThrow( 213 | () => okStamp1.compose(mixin), 214 | 'should not throw on duplicate keys in `contextTypes`' 215 | ); 216 | 217 | t.doesNotThrow( 218 | () => okStamp2.compose(mixin), 219 | 'should not throw on duplicate keys in `childContextTypes`' 220 | ); 221 | }); 222 | 223 | test('stamps composed of stamps with non-React statics', (t) => { 224 | t.plan(2); 225 | 226 | const mixin = stampit(null, { 227 | statics: { 228 | obj: { 229 | foo: 'foo', 230 | bar: '', 231 | }, 232 | method() { 233 | return 'foo'; 234 | }, 235 | }, 236 | }); 237 | 238 | const stamp = stampit(React, { 239 | statics: { 240 | obj: { 241 | bar: 'bar', 242 | }, 243 | method() { 244 | return 'bar'; 245 | }, 246 | }, 247 | }).compose(mixin); 248 | 249 | t.deepEqual( 250 | stamp.obj, { bar: 'bar' }, 251 | 'should inherit static objects overriding with last-in priority' 252 | ); 253 | 254 | t.equal( 255 | stamp.method(), 'bar', 256 | 'should inherit static methods overriding with last-in priority' 257 | ); 258 | }); 259 | 260 | test('stamps composed of stamps with mixable methods', (t) => { 261 | t.plan(2); 262 | 263 | const mixin1 = stampit(null, { 264 | getChildContext() { 265 | return { 266 | foo: '', 267 | }; 268 | }, 269 | 270 | componentDidMount() { 271 | this.state.mixin1 = true; 272 | }, 273 | }); 274 | 275 | const mixin2 = stampit(null, { 276 | componentDidMount() { 277 | this.state.mixin2 = true; 278 | }, 279 | }); 280 | 281 | const stamp = stampit(React, { 282 | state: { 283 | stamp: false, 284 | mixin1: false, 285 | mixin2: false, 286 | }, 287 | 288 | getChildContext() { 289 | return { 290 | bar: '', 291 | }; 292 | }, 293 | 294 | componentDidMount() { 295 | this.state.stamp = true; 296 | }, 297 | }).compose(mixin1, mixin2); 298 | 299 | const instance = stamp(); 300 | instance.componentDidMount(); 301 | 302 | t.deepEqual( 303 | instance.state, { stamp: true, mixin1: true, mixin2: true }, 304 | 'should inherit functionality of mixable methods' 305 | ); 306 | 307 | t.deepEqual( 308 | keys(instance.getChildContext()), ['foo', 'bar'], 309 | 'should inherit functionality of getChildContext' 310 | ); 311 | }); 312 | 313 | test('stamps composed of stamps with non-mixable methods', (t) => { 314 | t.plan(1); 315 | 316 | const mixin = stampit(null, { 317 | render() {}, 318 | }); 319 | 320 | const stamp = stampit(React, { 321 | render() {}, 322 | }); 323 | 324 | t.throws( 325 | () => stamp.compose(mixin), TypeError, 326 | 'should throw on duplicate methods' 327 | ); 328 | }); 329 | 330 | -------------------------------------------------------------------------------- /test/methods.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import test from 'tape'; 3 | 4 | import stampit from '../src'; 5 | 6 | test('stampit(React, { method() {} })()', (t) => { 7 | t.plan(1); 8 | 9 | const stamp = stampit(React, { 10 | render() {}, 11 | }); 12 | 13 | t.equal( 14 | typeof Object.getPrototypeOf(stamp()).render, 'function', 15 | 'should return an instance with `method` as internal proto prop' 16 | ); 17 | }); 18 | -------------------------------------------------------------------------------- /test/state.js: -------------------------------------------------------------------------------- 1 | import has from 'lodash/object/has'; 2 | import React from 'react'; 3 | import test from 'tape'; 4 | 5 | import stampit from '../src'; 6 | 7 | test('stampit(React, { state: obj })()', (t) => { 8 | t.plan(1); 9 | 10 | const stamp = stampit(React, { 11 | state: { 12 | foo: '', 13 | }, 14 | }); 15 | 16 | t.ok( 17 | has(stamp().state, 'foo'), 18 | 'should return an instance with `state` prop' 19 | ); 20 | }); 21 | 22 | test('stampit(React, { init() { ... } })()', (t) => { 23 | t.plan(1); 24 | 25 | const stamp = stampit(React, { 26 | init() { 27 | this.state = { foo: '' }; 28 | }, 29 | }); 30 | 31 | t.ok( 32 | has(stamp().state, 'foo'), 33 | 'should return an instance with `state` prop' 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /test/statics.js: -------------------------------------------------------------------------------- 1 | import has from 'lodash/object/has'; 2 | import React from 'react'; 3 | import test from 'tape'; 4 | 5 | import stampit from '../src'; 6 | 7 | test('stampit(React, { statics: obj })', (t) => { 8 | t.plan(1); 9 | 10 | const stamp = stampit(React, { 11 | statics: { 12 | foo: '', 13 | }, 14 | }); 15 | 16 | t.ok( 17 | has(stamp, 'foo'), 18 | 'should return a stamp with `statics` props as props' 19 | ); 20 | }); 21 | 22 | test('stampit(React, { displayName: str })', (t) => { 23 | t.plan(1); 24 | 25 | const stamp = stampit(React, { 26 | displayName: 'stamp', 27 | }); 28 | 29 | t.ok( 30 | has(stamp, 'displayName'), 31 | 'should return a stamp with `displayName` prop' 32 | ); 33 | }); 34 | 35 | test('stampit(React, { contextTypes: obj })', (t) => { 36 | t.plan(1); 37 | 38 | const stamp = stampit(React, { 39 | contextTypes: {}, 40 | }); 41 | 42 | t.ok( 43 | has(stamp, 'contextTypes'), 44 | 'should return a stamp with `contextTypes` prop' 45 | ); 46 | }); 47 | 48 | test('stampit(React, { childContextTypes: obj })', (t) => { 49 | t.plan(1); 50 | 51 | const stamp = stampit(React, { 52 | childContextTypes: {}, 53 | }); 54 | 55 | t.ok( 56 | has(stamp, 'childContextTypes'), 57 | 'should return a stamp with `childContextTypes` prop' 58 | ); 59 | }); 60 | 61 | test('stampit(React, { propTypes: obj })', (t) => { 62 | t.plan(1); 63 | 64 | const stamp = stampit(React, { 65 | propTypes: {}, 66 | }); 67 | 68 | t.ok( 69 | has(stamp, 'propTypes'), 70 | 'should return a stamp with `propTypes` prop' 71 | ); 72 | }); 73 | 74 | test('stampit(React, { defaultProps: obj })', (t) => { 75 | t.plan(1); 76 | 77 | const stamp = stampit(React, { 78 | defaultProps: {}, 79 | }); 80 | 81 | t.ok( 82 | has(stamp, 'defaultProps'), 83 | 'should return a stamp with `defaultProps` prop' 84 | ); 85 | }); 86 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | require('./basics'); 2 | require('./state'); 3 | require('./statics'); 4 | require('./methods'); 5 | require('./compose'); 6 | require('./wrapMethods'); 7 | require('./advanced'); 8 | -------------------------------------------------------------------------------- /test/wrapMethods.js: -------------------------------------------------------------------------------- 1 | import rewire from 'rewire'; 2 | import test from 'tape'; 3 | 4 | let compose = rewire('../src/utils/compose'); 5 | let wrapMethods = compose.__get__('wrapMethods'); 6 | 7 | test('wrapMethods(targ, src)', (t) => { 8 | t.plan(7); 9 | 10 | /* eslint-disable brace-style */ 11 | const targ = { 12 | componentWillMount() { this.wrapped = false; }, 13 | componentDidMount() { this.wrapped = false; }, 14 | componentWillReceiveProps() { this.wrapped = false; }, 15 | componentWillUpdate() { this.wrapped = false; }, 16 | componentDidUpdate() { this.wrapped = false; }, 17 | componentWillUnmount() { this.wrapped = false; }, 18 | method() {}, 19 | }; 20 | 21 | const src = { 22 | componentWillMount() { this.wrapped = 'WillMount'; }, 23 | componentDidMount() { this.wrapped = 'DidMount'; }, 24 | componentWillReceiveProps() { this.wrapped = 'WillReceiveProps'; }, 25 | componentWillUpdate() { this.wrapped = 'WillUpdate'; }, 26 | componentDidUpdate() { this.wrapped = 'DidUpdate'; }, 27 | componentWillUnmount() { this.wrapped = 'WillUnmount'; }, 28 | }; 29 | 30 | const failObj = { 31 | method() {}, 32 | }; 33 | /* eslint-enable brace-style */ 34 | 35 | const obj = wrapMethods(targ, src); 36 | 37 | obj.componentWillMount(); 38 | t.equal( 39 | obj.wrapped, 'WillMount', 40 | 'should wrap `componentWillMount`' 41 | ); 42 | 43 | obj.componentDidMount(); 44 | t.equal( 45 | obj.wrapped, 'DidMount', 46 | 'should wrap `componentDidMount`' 47 | ); 48 | 49 | obj.componentWillReceiveProps(); 50 | t.equal( 51 | obj.wrapped, 'WillReceiveProps', 52 | 'should wrap `componentWillReceiveProps`' 53 | ); 54 | 55 | obj.componentWillUpdate(); 56 | t.equal( 57 | obj.wrapped, 'WillUpdate', 58 | 'should wrap `componentWillUpdate`' 59 | ); 60 | 61 | obj.componentDidUpdate(); 62 | t.equal( 63 | obj.wrapped, 'DidUpdate', 64 | 'should wrap `componentDidUpdate`' 65 | ); 66 | 67 | obj.componentWillUnmount(); 68 | t.ok( 69 | obj.wrapped, 'WillUnmount', 70 | 'should wrap `componentWillUnmount`' 71 | ); 72 | 73 | t.throws( 74 | () => wrapMethods(targ, failObj), TypeError, 75 | 'should throw on dupe non-React method' 76 | ); 77 | }); 78 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var minify = process.env.MINIFY || false; 3 | 4 | module.exports = { 5 | entry: { 6 | 'react-stampit': ['./src/index'], 7 | 'react-stampit-with-addons': ['./src/addons'], 8 | }, 9 | 10 | output: { 11 | path: path.resolve('./dist'), 12 | filename: minify ? '[name].min.js' : '[name].js', 13 | library: 'stampit', 14 | libraryTarget: 'umd', 15 | }, 16 | 17 | module: { 18 | loaders: [ 19 | { test: /\.js$/, loader: 'babel-loader' }, 20 | ], 21 | }, 22 | }; 23 | --------------------------------------------------------------------------------