├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── react-server.dev.js ├── react-server.js ├── react-server.min.js └── react.js ├── index.js ├── lib ├── React.js ├── ReactClass.js ├── ReactComponent.js ├── ReactComponentFactory.js ├── ReactCompositeComponent.js ├── ReactDomComponent.js ├── ReactTextComponent.js └── addons.js ├── package.json ├── register.js ├── tests ├── bench.js ├── fixtures │ ├── apps │ │ ├── chat │ │ │ ├── app.js │ │ │ ├── components │ │ │ │ ├── ChatApp.jsx │ │ │ │ ├── Html.jsx │ │ │ │ ├── MessageComposer.jsx │ │ │ │ ├── MessageListItem.jsx │ │ │ │ ├── MessageSection.jsx │ │ │ │ ├── ThreadListItem.jsx │ │ │ │ └── ThreadSection.jsx │ │ │ ├── configs │ │ │ │ └── routes.js │ │ │ ├── package.json │ │ │ ├── state.json │ │ │ └── stores │ │ │ │ ├── MessageStore.js │ │ │ │ ├── RouteStore.js │ │ │ │ ├── ThreadStore.js │ │ │ │ └── UnreadThreadStore.js │ │ ├── fluxible-router │ │ │ ├── app.js │ │ │ ├── components │ │ │ │ ├── About.js │ │ │ │ ├── Application.js │ │ │ │ ├── Home.js │ │ │ │ ├── Html.js │ │ │ │ ├── Nav.js │ │ │ │ ├── Page.js │ │ │ │ └── Timestamp.js │ │ │ ├── configs │ │ │ │ └── routes.js │ │ │ ├── package.json │ │ │ ├── state.json │ │ │ └── stores │ │ │ │ ├── ApplicationStore.js │ │ │ │ ├── PageStore.js │ │ │ │ ├── RouteStore.js │ │ │ │ └── TimeStore.js │ │ └── todo │ │ │ ├── app.js │ │ │ ├── components │ │ │ ├── Footer.jsx │ │ │ ├── Html.jsx │ │ │ ├── TodoApp.jsx │ │ │ └── TodoItem.jsx │ │ │ ├── package.json │ │ │ ├── state.json │ │ │ └── stores │ │ │ ├── PageStore.js │ │ │ └── TodoStore.js │ └── components │ │ ├── ContextChild.js │ │ ├── ContextParent.js │ │ ├── Hello.js │ │ └── HelloCreateClass.js ├── profile │ ├── react-server.js │ └── react.js └── unit │ ├── .eslintrc │ ├── apps.js │ ├── context.js │ ├── hello.js │ ├── native.js │ └── stateful.js ├── utils └── renderAppWithFreshReact.js └── webpack.config.js /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | parser: "babel-eslint" 3 | env: 4 | node: true 5 | rules: 6 | strict: 0 7 | indent: [2, 4] 8 | quotes: [0] 9 | no-unused-vars: 0 // see https://github.com/babel/babel-eslint/issues/21 10 | no-underscore-dangle: 0 11 | no-use-before-define: 0 12 | no-unused-expressions: 0 13 | yoda: 0 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | artifacts 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | artifacts/ 3 | **/npm-debug.log 4 | **/ynpm-debug.log 5 | .DS_Store 6 | *~ 7 | test/ 8 | tests/ 9 | docs/ 10 | examples/ 11 | screwdriver/ 12 | Makefile 13 | dist/react.js 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | matrix: 4 | allow_failures: 5 | - node_js: "0.12" 6 | node_js: 7 | - "0.10" 8 | - "0.12" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For React software 4 | 5 | Copyright (c) 2013-2015, Facebook, Inc. 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | * Neither the name Facebook nor the names of its contributors may be used to 19 | endorse or promote products derived from this software without specific 20 | prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-server 2 | 3 | This project seeks to re-implement React's `renderToString` and 4 | `renderToStaticMarkup` methods to be more efficient for server rendering. The 5 | resulting markup is intended to be equivalent to that of stock React so that 6 | the client (which will use stock React) will be able to re-use the server 7 | rendered markup. 8 | 9 | ## Install 10 | 11 | Not available on npm. You must install from git. 12 | 13 | ## Usage 14 | 15 | react-server is meant to be a drop in replacement for React specifically for the 16 | server. It should not be used on the client. 17 | 18 | You can enable react-server by doing the following: 19 | 20 | ```js 21 | require('react-server/register'); 22 | ``` 23 | 24 | Internally this is overriding node's require cache to return react-server 25 | whenever `require('react')` or `require('react/addons')` is called. 26 | 27 | ## Behavioral Differences 28 | 29 | In order to gain these efficiencies, some of the inner workings of React have 30 | been changed which may bleed in to how components are implemented. 31 | 32 | ### Autobinding 33 | 34 | `React.createClass` automatically binds all non-static methods to the component 35 | instance so that you do not need to automatically bind them. This is typically 36 | used when registering event handlers or passing methods between components. If 37 | your component relies on this, it's not as simple as just using bind because 38 | React on the client will warn about binding methods that are already autobound. 39 | Instead, you can create a closure: 40 | 41 | ``` 42 | React.createClass({ 43 | render: function () { 44 | var self = this; 45 | return foo 48 | } 49 | }); 50 | ``` 51 | 52 | ### Owner vs. Parent Context 53 | 54 | react-server uses parent context which is slightly different to React 0.13's 55 | owner context. React plans on switching to parent context in version 0.14, but 56 | react-server chose parent context due to its simpler implementation. In most 57 | cases, this is actually more reliable than the old owner context. 58 | 59 | ## Missing Utilities 60 | 61 | react-server may still be missing functionality from stock React. If you find 62 | any missing pieces or broken functionality, please open an issue to let me know. 63 | 64 | ## Testing 65 | 66 | You can run the test suite and benchmarks simply by running: 67 | 68 | ``` 69 | npm test 70 | ``` 71 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var assign = require('object-assign'); 2 | 3 | // Monkey patch setState to be synchronous and not deal with update queue 4 | var ReactComponent = require('react/lib/ReactComponent'); 5 | ReactComponent.prototype.setState = function (state) { 6 | this.state = assign({}, this.state, state); 7 | }; 8 | 9 | var ReactServer = require('./lib/React'); 10 | 11 | // Ensure that all addons use ./lib/React as React dependency 12 | require.cache[require.resolve('react/lib/React')] = require.cache[require.resolve('./lib/React')]; 13 | 14 | ReactServer.addons = require('./lib/addons'); 15 | 16 | module.exports = ReactServer; 17 | -------------------------------------------------------------------------------- /lib/React.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | */ 5 | 6 | var assign = require('object-assign'); 7 | 8 | import ReactServerClass from './ReactClass'; 9 | import ReactClass from 'react/lib/ReactClass'; 10 | 11 | 12 | // Prevent autobind on built-in components 13 | ReactClass.createClass = ReactServerClass.createClass; 14 | 15 | import ReactDomComponent from './ReactDomComponent'; 16 | import { 17 | instantiateReactComponent, 18 | injections as ReactComponentFactoryInjections 19 | } from './ReactComponentFactory.js'; 20 | import ReactCompositeComponent from './ReactCompositeComponent'; 21 | 22 | import ReactChildren from 'react/lib/ReactChildren'; 23 | import ReactComponent from 'react/lib/ReactComponent'; 24 | import ReactDefaultInjection from 'react/lib/ReactDefaultInjection'; 25 | import ReactDOM from 'react/lib/ReactDOM'; 26 | import ReactElement from 'react/lib/ReactElement'; 27 | import ReactInstanceHandles from 'react/lib/ReactInstanceHandles'; 28 | import ReactMarkupChecksum from 'react/lib/ReactMarkupChecksum'; 29 | import ReactNativeComponent from 'react/lib/ReactNativeComponent'; 30 | 31 | ReactDefaultInjection.inject(); 32 | ReactComponentFactoryInjections.ReactCompositeComponent = ReactCompositeComponent; 33 | ReactComponentFactoryInjections.ReactDomComponent = ReactDomComponent; 34 | 35 | var ReactServer = { 36 | Children: { 37 | map: ReactChildren.map, 38 | forEach: ReactChildren.forEach, 39 | count: ReactChildren.count, 40 | only: require('react/lib/onlyChild') 41 | }, 42 | Component: ReactComponent, 43 | createClass: ReactServerClass.createClass, 44 | createElement: ReactElement.createElement, 45 | cloneElement: ReactElement.cloneElement, 46 | createFactory: ReactElement.createFactory, 47 | DOM: ReactDOM, 48 | isValidElement: ReactElement.isValidElement, 49 | PropTypes: require('react/lib/ReactPropTypes'), 50 | renderToStaticMarkup: function (element, callback) { 51 | var component = instantiateReactComponent(element); 52 | var markup = component.mountComponent(null, { 53 | renderToStaticMarkup: true 54 | }, {}); 55 | callback && callback(null, markup); 56 | return markup; 57 | }, 58 | renderToString: function (element, callback) { 59 | var rootNodeId = ReactInstanceHandles.createReactRootID(); 60 | var component = instantiateReactComponent(element); 61 | var markup = component.mountComponent(rootNodeId, { 62 | renderToStaticMarkup: false 63 | }, {}); 64 | markup = ReactMarkupChecksum.addChecksumToMarkup(markup); 65 | callback && callback(null, markup); 66 | return markup; 67 | }, 68 | version: '0.13.3', 69 | 70 | __spread: assign, 71 | 72 | // Allows seeing if react-server is used 73 | __isReactServer: true 74 | }; 75 | 76 | module.exports = ReactServer; 77 | -------------------------------------------------------------------------------- /lib/ReactClass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule ReactClass 10 | */ 11 | 12 | 'use strict'; 13 | 14 | var ReactComponent = require("react/lib/ReactComponent"); 15 | var ReactElement = require("react/lib/ReactElement"); 16 | var ReactErrorUtils = require("react/lib/ReactErrorUtils"); 17 | var ReactInstanceMap = require("react/lib/ReactInstanceMap"); 18 | var ReactLifeCycle = require("react/lib/ReactLifeCycle"); 19 | var ReactPropTypeLocations = require("react/lib/ReactPropTypeLocations"); 20 | var ReactPropTypeLocationNames = require("react/lib/ReactPropTypeLocationNames"); 21 | var ReactUpdateQueue = require("react/lib/ReactUpdateQueue"); 22 | 23 | var assign = require("object-assign"); 24 | var invariant = require("react/lib/invariant"); 25 | var keyMirror = require("react/lib/keyMirror"); 26 | var keyOf = require("react/lib/keyOf"); 27 | var warning = require("react/lib/warning"); 28 | 29 | var MIXINS_KEY = keyOf({mixins: null}); 30 | 31 | /** 32 | * Policies that describe methods in `ReactClassInterface`. 33 | */ 34 | var SpecPolicy = keyMirror({ 35 | /** 36 | * These methods may be defined only once by the class specification or mixin. 37 | */ 38 | DEFINE_ONCE: null, 39 | /** 40 | * These methods may be defined by both the class specification and mixins. 41 | * Subsequent definitions will be chained. These methods must return void. 42 | */ 43 | DEFINE_MANY: null, 44 | /** 45 | * These methods are overriding the base class. 46 | */ 47 | OVERRIDE_BASE: null, 48 | /** 49 | * These methods are similar to DEFINE_MANY, except we assume they return 50 | * objects. We try to merge the keys of the return values of all the mixed in 51 | * functions. If there is a key conflict we throw. 52 | */ 53 | DEFINE_MANY_MERGED: null 54 | }); 55 | 56 | 57 | var injectedMixins = []; 58 | 59 | /** 60 | * Composite components are higher-level components that compose other composite 61 | * or native components. 62 | * 63 | * To create a new type of `ReactClass`, pass a specification of 64 | * your new class to `React.createClass`. The only requirement of your class 65 | * specification is that you implement a `render` method. 66 | * 67 | * var MyComponent = React.createClass({ 68 | * render: function() { 69 | * return
Hello World
; 70 | * } 71 | * }); 72 | * 73 | * The class specification supports a specific protocol of methods that have 74 | * special meaning (e.g. `render`). See `ReactClassInterface` for 75 | * more the comprehensive protocol. Any other properties and methods in the 76 | * class specification will available on the prototype. 77 | * 78 | * @interface ReactClassInterface 79 | * @internal 80 | */ 81 | var ReactClassInterface = { 82 | 83 | /** 84 | * An array of Mixin objects to include when defining your component. 85 | * 86 | * @type {array} 87 | * @optional 88 | */ 89 | mixins: SpecPolicy.DEFINE_MANY, 90 | 91 | /** 92 | * An object containing properties and methods that should be defined on 93 | * the component's constructor instead of its prototype (static methods). 94 | * 95 | * @type {object} 96 | * @optional 97 | */ 98 | statics: SpecPolicy.DEFINE_MANY, 99 | 100 | /** 101 | * Definition of prop types for this component. 102 | * 103 | * @type {object} 104 | * @optional 105 | */ 106 | propTypes: SpecPolicy.DEFINE_MANY, 107 | 108 | /** 109 | * Definition of context types for this component. 110 | * 111 | * @type {object} 112 | * @optional 113 | */ 114 | contextTypes: SpecPolicy.DEFINE_MANY, 115 | 116 | /** 117 | * Definition of context types this component sets for its children. 118 | * 119 | * @type {object} 120 | * @optional 121 | */ 122 | childContextTypes: SpecPolicy.DEFINE_MANY, 123 | 124 | // ==== Definition methods ==== 125 | 126 | /** 127 | * Invoked when the component is mounted. Values in the mapping will be set on 128 | * `this.props` if that prop is not specified (i.e. using an `in` check). 129 | * 130 | * This method is invoked before `getInitialState` and therefore cannot rely 131 | * on `this.state` or use `this.setState`. 132 | * 133 | * @return {object} 134 | * @optional 135 | */ 136 | getDefaultProps: SpecPolicy.DEFINE_MANY_MERGED, 137 | 138 | /** 139 | * Invoked once before the component is mounted. The return value will be used 140 | * as the initial value of `this.state`. 141 | * 142 | * getInitialState: function() { 143 | * return { 144 | * isOn: false, 145 | * fooBaz: new BazFoo() 146 | * } 147 | * } 148 | * 149 | * @return {object} 150 | * @optional 151 | */ 152 | getInitialState: SpecPolicy.DEFINE_MANY_MERGED, 153 | 154 | /** 155 | * @return {object} 156 | * @optional 157 | */ 158 | getChildContext: SpecPolicy.DEFINE_MANY_MERGED, 159 | 160 | /** 161 | * Uses props from `this.props` and state from `this.state` to render the 162 | * structure of the component. 163 | * 164 | * No guarantees are made about when or how often this method is invoked, so 165 | * it must not have side effects. 166 | * 167 | * render: function() { 168 | * var name = this.props.name; 169 | * return
Hello, {name}!
; 170 | * } 171 | * 172 | * @return {ReactComponent} 173 | * @nosideeffects 174 | * @required 175 | */ 176 | render: SpecPolicy.DEFINE_ONCE, 177 | 178 | 179 | 180 | // ==== Delegate methods ==== 181 | 182 | /** 183 | * Invoked when the component is initially created and about to be mounted. 184 | * This may have side effects, but any external subscriptions or data created 185 | * by this method must be cleaned up in `componentWillUnmount`. 186 | * 187 | * @optional 188 | */ 189 | componentWillMount: SpecPolicy.DEFINE_MANY, 190 | 191 | /** 192 | * Invoked when the component has been mounted and has a DOM representation. 193 | * However, there is no guarantee that the DOM node is in the document. 194 | * 195 | * Use this as an opportunity to operate on the DOM when the component has 196 | * been mounted (initialized and rendered) for the first time. 197 | * 198 | * @param {DOMElement} rootNode DOM element representing the component. 199 | * @optional 200 | */ 201 | componentDidMount: SpecPolicy.DEFINE_MANY, 202 | 203 | /** 204 | * Invoked before the component receives new props. 205 | * 206 | * Use this as an opportunity to react to a prop transition by updating the 207 | * state using `this.setState`. Current props are accessed via `this.props`. 208 | * 209 | * componentWillReceiveProps: function(nextProps, nextContext) { 210 | * this.setState({ 211 | * likesIncreasing: nextProps.likeCount > this.props.likeCount 212 | * }); 213 | * } 214 | * 215 | * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop 216 | * transition may cause a state change, but the opposite is not true. If you 217 | * need it, you are probably looking for `componentWillUpdate`. 218 | * 219 | * @param {object} nextProps 220 | * @optional 221 | */ 222 | componentWillReceiveProps: SpecPolicy.DEFINE_MANY, 223 | 224 | /** 225 | * Invoked while deciding if the component should be updated as a result of 226 | * receiving new props, state and/or context. 227 | * 228 | * Use this as an opportunity to `return false` when you're certain that the 229 | * transition to the new props/state/context will not require a component 230 | * update. 231 | * 232 | * shouldComponentUpdate: function(nextProps, nextState, nextContext) { 233 | * return !equal(nextProps, this.props) || 234 | * !equal(nextState, this.state) || 235 | * !equal(nextContext, this.context); 236 | * } 237 | * 238 | * @param {object} nextProps 239 | * @param {?object} nextState 240 | * @param {?object} nextContext 241 | * @return {boolean} True if the component should update. 242 | * @optional 243 | */ 244 | shouldComponentUpdate: SpecPolicy.DEFINE_ONCE, 245 | 246 | /** 247 | * Invoked when the component is about to update due to a transition from 248 | * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState` 249 | * and `nextContext`. 250 | * 251 | * Use this as an opportunity to perform preparation before an update occurs. 252 | * 253 | * NOTE: You **cannot** use `this.setState()` in this method. 254 | * 255 | * @param {object} nextProps 256 | * @param {?object} nextState 257 | * @param {?object} nextContext 258 | * @param {ReactReconcileTransaction} transaction 259 | * @optional 260 | */ 261 | componentWillUpdate: SpecPolicy.DEFINE_MANY, 262 | 263 | /** 264 | * Invoked when the component's DOM representation has been updated. 265 | * 266 | * Use this as an opportunity to operate on the DOM when the component has 267 | * been updated. 268 | * 269 | * @param {object} prevProps 270 | * @param {?object} prevState 271 | * @param {?object} prevContext 272 | * @param {DOMElement} rootNode DOM element representing the component. 273 | * @optional 274 | */ 275 | componentDidUpdate: SpecPolicy.DEFINE_MANY, 276 | 277 | /** 278 | * Invoked when the component is about to be removed from its parent and have 279 | * its DOM representation destroyed. 280 | * 281 | * Use this as an opportunity to deallocate any external resources. 282 | * 283 | * NOTE: There is no `componentDidUnmount` since your component will have been 284 | * destroyed by that point. 285 | * 286 | * @optional 287 | */ 288 | componentWillUnmount: SpecPolicy.DEFINE_MANY, 289 | 290 | 291 | 292 | // ==== Advanced methods ==== 293 | 294 | /** 295 | * Updates the component's currently mounted DOM representation. 296 | * 297 | * By default, this implements React's rendering and reconciliation algorithm. 298 | * Sophisticated clients may wish to override this. 299 | * 300 | * @param {ReactReconcileTransaction} transaction 301 | * @internal 302 | * @overridable 303 | */ 304 | updateComponent: SpecPolicy.OVERRIDE_BASE 305 | 306 | }; 307 | 308 | /** 309 | * Mapping from class specification keys to special processing functions. 310 | * 311 | * Although these are declared like instance properties in the specification 312 | * when defining classes using `React.createClass`, they are actually static 313 | * and are accessible on the constructor instead of the prototype. Despite 314 | * being static, they must be defined outside of the "statics" key under 315 | * which all other static methods are defined. 316 | */ 317 | var RESERVED_SPEC_KEYS = { 318 | displayName: function(Constructor, displayName) { 319 | Constructor.displayName = displayName; 320 | }, 321 | mixins: function(Constructor, mixins) { 322 | if (mixins) { 323 | for (var i = 0; i < mixins.length; i++) { 324 | mixSpecIntoComponent(Constructor, mixins[i]); 325 | } 326 | } 327 | }, 328 | childContextTypes: function(Constructor, childContextTypes) { 329 | if ("production" !== process.env.NODE_ENV) { 330 | validateTypeDef( 331 | Constructor, 332 | childContextTypes, 333 | ReactPropTypeLocations.childContext 334 | ); 335 | } 336 | Constructor.childContextTypes = assign( 337 | {}, 338 | Constructor.childContextTypes, 339 | childContextTypes 340 | ); 341 | }, 342 | contextTypes: function(Constructor, contextTypes) { 343 | if ("production" !== process.env.NODE_ENV) { 344 | validateTypeDef( 345 | Constructor, 346 | contextTypes, 347 | ReactPropTypeLocations.context 348 | ); 349 | } 350 | Constructor.contextTypes = assign( 351 | {}, 352 | Constructor.contextTypes, 353 | contextTypes 354 | ); 355 | }, 356 | /** 357 | * Special case getDefaultProps which should move into statics but requires 358 | * automatic merging. 359 | */ 360 | getDefaultProps: function(Constructor, getDefaultProps) { 361 | if (Constructor.getDefaultProps) { 362 | Constructor.getDefaultProps = createMergedResultFunction( 363 | Constructor.getDefaultProps, 364 | getDefaultProps 365 | ); 366 | } else { 367 | Constructor.getDefaultProps = getDefaultProps; 368 | } 369 | }, 370 | propTypes: function(Constructor, propTypes) { 371 | if ("production" !== process.env.NODE_ENV) { 372 | validateTypeDef( 373 | Constructor, 374 | propTypes, 375 | ReactPropTypeLocations.prop 376 | ); 377 | } 378 | Constructor.propTypes = assign( 379 | {}, 380 | Constructor.propTypes, 381 | propTypes 382 | ); 383 | }, 384 | statics: function(Constructor, statics) { 385 | mixStaticSpecIntoComponent(Constructor, statics); 386 | } 387 | }; 388 | 389 | function validateTypeDef(Constructor, typeDef, location) { 390 | for (var propName in typeDef) { 391 | if (typeDef.hasOwnProperty(propName)) { 392 | // use a warning instead of an invariant so components 393 | // don't show up in prod but not in __DEV__ 394 | ("production" !== process.env.NODE_ENV ? warning( 395 | typeof typeDef[propName] === 'function', 396 | '%s: %s type `%s` is invalid; it must be a function, usually from ' + 397 | 'React.PropTypes.', 398 | Constructor.displayName || 'ReactClass', 399 | ReactPropTypeLocationNames[location], 400 | propName 401 | ) : null); 402 | } 403 | } 404 | } 405 | 406 | function validateMethodOverride(proto, name) { 407 | var specPolicy = ReactClassInterface.hasOwnProperty(name) ? 408 | ReactClassInterface[name] : 409 | null; 410 | 411 | // Disallow overriding of base class methods unless explicitly allowed. 412 | if (ReactClassMixin.hasOwnProperty(name)) { 413 | ("production" !== process.env.NODE_ENV ? invariant( 414 | specPolicy === SpecPolicy.OVERRIDE_BASE, 415 | 'ReactClassInterface: You are attempting to override ' + 416 | '`%s` from your class specification. Ensure that your method names ' + 417 | 'do not overlap with React methods.', 418 | name 419 | ) : invariant(specPolicy === SpecPolicy.OVERRIDE_BASE)); 420 | } 421 | 422 | // Disallow defining methods more than once unless explicitly allowed. 423 | if (proto.hasOwnProperty(name)) { 424 | ("production" !== process.env.NODE_ENV ? invariant( 425 | specPolicy === SpecPolicy.DEFINE_MANY || 426 | specPolicy === SpecPolicy.DEFINE_MANY_MERGED, 427 | 'ReactClassInterface: You are attempting to define ' + 428 | '`%s` on your component more than once. This conflict may be due ' + 429 | 'to a mixin.', 430 | name 431 | ) : invariant(specPolicy === SpecPolicy.DEFINE_MANY || 432 | specPolicy === SpecPolicy.DEFINE_MANY_MERGED)); 433 | } 434 | } 435 | 436 | /** 437 | * Mixin helper which handles policy validation and reserved 438 | * specification keys when building React classses. 439 | */ 440 | function mixSpecIntoComponent(Constructor, spec) { 441 | if (!spec) { 442 | return; 443 | } 444 | 445 | ("production" !== process.env.NODE_ENV ? invariant( 446 | typeof spec !== 'function', 447 | 'ReactClass: You\'re attempting to ' + 448 | 'use a component class as a mixin. Instead, just use a regular object.' 449 | ) : invariant(typeof spec !== 'function')); 450 | ("production" !== process.env.NODE_ENV ? invariant( 451 | !ReactElement.isValidElement(spec), 452 | 'ReactClass: You\'re attempting to ' + 453 | 'use a component as a mixin. Instead, just use a regular object.' 454 | ) : invariant(!ReactElement.isValidElement(spec))); 455 | 456 | var proto = Constructor.prototype; 457 | 458 | // By handling mixins before any other properties, we ensure the same 459 | // chaining order is applied to methods with DEFINE_MANY policy, whether 460 | // mixins are listed before or after these methods in the spec. 461 | if (spec.hasOwnProperty(MIXINS_KEY)) { 462 | RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins); 463 | } 464 | 465 | for (var name in spec) { 466 | if (!spec.hasOwnProperty(name)) { 467 | continue; 468 | } 469 | 470 | if (name === MIXINS_KEY) { 471 | // We have already handled mixins in a special case above 472 | continue; 473 | } 474 | 475 | var property = spec[name]; 476 | validateMethodOverride(proto, name); 477 | 478 | if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) { 479 | RESERVED_SPEC_KEYS[name](Constructor, property); 480 | } else { 481 | // Setup methods on prototype: 482 | // The following member methods should not be automatically bound: 483 | // 1. Expected ReactClass methods (in the "interface"). 484 | // 2. Overridden methods (that were mixed in). 485 | var isReactClassMethod = 486 | ReactClassInterface.hasOwnProperty(name); 487 | var isAlreadyDefined = proto.hasOwnProperty(name); 488 | if (isAlreadyDefined) { 489 | var specPolicy = ReactClassInterface[name]; 490 | 491 | // These cases should already be caught by validateMethodOverride 492 | ("production" !== process.env.NODE_ENV ? invariant( 493 | isReactClassMethod && ( 494 | (specPolicy === SpecPolicy.DEFINE_MANY_MERGED || specPolicy === SpecPolicy.DEFINE_MANY) 495 | ), 496 | 'ReactClass: Unexpected spec policy %s for key %s ' + 497 | 'when mixing in component specs.', 498 | specPolicy, 499 | name 500 | ) : invariant(isReactClassMethod && ( 501 | (specPolicy === SpecPolicy.DEFINE_MANY_MERGED || specPolicy === SpecPolicy.DEFINE_MANY) 502 | ))); 503 | 504 | // For methods which are defined more than once, call the existing 505 | // methods before calling the new property, merging if appropriate. 506 | if (specPolicy === SpecPolicy.DEFINE_MANY_MERGED) { 507 | proto[name] = createMergedResultFunction(proto[name], property); 508 | } else if (specPolicy === SpecPolicy.DEFINE_MANY) { 509 | proto[name] = createChainedFunction(proto[name], property); 510 | } 511 | } else { 512 | proto[name] = property; 513 | if ("production" !== process.env.NODE_ENV) { 514 | // Add verbose displayName to the function, which helps when looking 515 | // at profiling tools. 516 | if (typeof property === 'function' && spec.displayName) { 517 | proto[name].displayName = spec.displayName + '_' + name; 518 | } 519 | } 520 | } 521 | } 522 | } 523 | } 524 | 525 | function mixStaticSpecIntoComponent(Constructor, statics) { 526 | if (!statics) { 527 | return; 528 | } 529 | for (var name in statics) { 530 | var property = statics[name]; 531 | if (!statics.hasOwnProperty(name)) { 532 | continue; 533 | } 534 | 535 | var isReserved = name in RESERVED_SPEC_KEYS; 536 | ("production" !== process.env.NODE_ENV ? invariant( 537 | !isReserved, 538 | 'ReactClass: You are attempting to define a reserved ' + 539 | 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' + 540 | 'as an instance property instead; it will still be accessible on the ' + 541 | 'constructor.', 542 | name 543 | ) : invariant(!isReserved)); 544 | 545 | var isInherited = name in Constructor; 546 | ("production" !== process.env.NODE_ENV ? invariant( 547 | !isInherited, 548 | 'ReactClass: You are attempting to define ' + 549 | '`%s` on your component more than once. This conflict may be ' + 550 | 'due to a mixin.', 551 | name 552 | ) : invariant(!isInherited)); 553 | Constructor[name] = property; 554 | } 555 | } 556 | 557 | /** 558 | * Merge two objects, but throw if both contain the same key. 559 | * 560 | * @param {object} one The first object, which is mutated. 561 | * @param {object} two The second object 562 | * @return {object} one after it has been mutated to contain everything in two. 563 | */ 564 | function mergeIntoWithNoDuplicateKeys(one, two) { 565 | ("production" !== process.env.NODE_ENV ? invariant( 566 | one && two && typeof one === 'object' && typeof two === 'object', 567 | 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.' 568 | ) : invariant(one && two && typeof one === 'object' && typeof two === 'object')); 569 | 570 | for (var key in two) { 571 | if (two.hasOwnProperty(key)) { 572 | ("production" !== process.env.NODE_ENV ? invariant( 573 | one[key] === undefined, 574 | 'mergeIntoWithNoDuplicateKeys(): ' + 575 | 'Tried to merge two objects with the same key: `%s`. This conflict ' + 576 | 'may be due to a mixin; in particular, this may be caused by two ' + 577 | 'getInitialState() or getDefaultProps() methods returning objects ' + 578 | 'with clashing keys.', 579 | key 580 | ) : invariant(one[key] === undefined)); 581 | one[key] = two[key]; 582 | } 583 | } 584 | return one; 585 | } 586 | 587 | /** 588 | * Creates a function that invokes two functions and merges their return values. 589 | * 590 | * @param {function} one Function to invoke first. 591 | * @param {function} two Function to invoke second. 592 | * @return {function} Function that invokes the two argument functions. 593 | * @private 594 | */ 595 | function createMergedResultFunction(one, two) { 596 | return function mergedResult() { 597 | var a = one.apply(this, arguments); 598 | var b = two.apply(this, arguments); 599 | if (a == null) { 600 | return b; 601 | } else if (b == null) { 602 | return a; 603 | } 604 | var c = {}; 605 | mergeIntoWithNoDuplicateKeys(c, a); 606 | mergeIntoWithNoDuplicateKeys(c, b); 607 | return c; 608 | }; 609 | } 610 | 611 | /** 612 | * Creates a function that invokes two functions and ignores their return vales. 613 | * 614 | * @param {function} one Function to invoke first. 615 | * @param {function} two Function to invoke second. 616 | * @return {function} Function that invokes the two argument functions. 617 | * @private 618 | */ 619 | function createChainedFunction(one, two) { 620 | return function chainedFunction() { 621 | one.apply(this, arguments); 622 | two.apply(this, arguments); 623 | }; 624 | } 625 | 626 | var typeDeprecationDescriptor = { 627 | enumerable: false, 628 | get: function() { 629 | var displayName = this.displayName || this.name || 'Component'; 630 | 631 | Object.defineProperty(this, 'type', { 632 | value: this 633 | }); 634 | return this; 635 | } 636 | }; 637 | 638 | /** 639 | * Add more to the ReactClass base class. These are all legacy features and 640 | * therefore not already part of the modern ReactComponent. 641 | */ 642 | var ReactClassMixin = { 643 | 644 | /** 645 | * TODO: This will be deprecated because state should always keep a consistent 646 | * type signature and the only use case for this, is to avoid that. 647 | */ 648 | replaceState: function(newState, callback) { 649 | ReactUpdateQueue.enqueueReplaceState(this, newState); 650 | if (callback) { 651 | ReactUpdateQueue.enqueueCallback(this, callback); 652 | } 653 | }, 654 | 655 | /** 656 | * Checks whether or not this composite component is mounted. 657 | * @return {boolean} True if mounted, false otherwise. 658 | * @protected 659 | * @final 660 | */ 661 | isMounted: function() { 662 | var internalInstance = ReactInstanceMap.get(this); 663 | return ( 664 | internalInstance && 665 | internalInstance !== ReactLifeCycle.currentlyMountingInstance 666 | ); 667 | }, 668 | 669 | /** 670 | * Sets a subset of the props. 671 | * 672 | * @param {object} partialProps Subset of the next props. 673 | * @param {?function} callback Called after props are updated. 674 | * @final 675 | * @public 676 | * @deprecated 677 | */ 678 | setProps: function(partialProps, callback) { 679 | ReactUpdateQueue.enqueueSetProps(this, partialProps); 680 | if (callback) { 681 | ReactUpdateQueue.enqueueCallback(this, callback); 682 | } 683 | }, 684 | 685 | /** 686 | * Replace all the props. 687 | * 688 | * @param {object} newProps Subset of the next props. 689 | * @param {?function} callback Called after props are updated. 690 | * @final 691 | * @public 692 | * @deprecated 693 | */ 694 | replaceProps: function(newProps, callback) { 695 | ReactUpdateQueue.enqueueReplaceProps(this, newProps); 696 | if (callback) { 697 | ReactUpdateQueue.enqueueCallback(this, callback); 698 | } 699 | } 700 | }; 701 | 702 | var ReactClassComponent = function() {}; 703 | assign( 704 | ReactClassComponent.prototype, 705 | ReactComponent.prototype, 706 | ReactClassMixin 707 | ); 708 | 709 | /** 710 | * Module for creating composite components. 711 | * 712 | * @class ReactClass 713 | */ 714 | var ReactClass = { 715 | 716 | /** 717 | * Creates a composite component class given a class specification. 718 | * 719 | * @param {object} spec Class specification (which must define `render`). 720 | * @return {function} Component constructor function. 721 | * @public 722 | */ 723 | createClass: function(spec) { 724 | var Constructor = function(props, context) { 725 | // This constructor is overridden by mocks. The argument is used 726 | // by mocks to assert on what gets mounted. 727 | 728 | this.props = props; 729 | this.context = context; 730 | this.state = null; 731 | 732 | // ReactClasses doesn't have constructors. Instead, they use the 733 | // getInitialState and componentWillMount methods for initialization. 734 | 735 | var initialState = this.getInitialState ? this.getInitialState() : null; 736 | if ("production" !== process.env.NODE_ENV) { 737 | // We allow auto-mocks to proceed as if they're returning null. 738 | if (typeof initialState === 'undefined' && 739 | this.getInitialState._isMockFunction) { 740 | // This is probably bad practice. Consider warning here and 741 | // deprecating this convenience. 742 | initialState = null; 743 | } 744 | } 745 | ("production" !== process.env.NODE_ENV ? invariant( 746 | typeof initialState === 'object' && !Array.isArray(initialState), 747 | '%s.getInitialState(): must return an object or null', 748 | Constructor.displayName || 'ReactCompositeComponent' 749 | ) : invariant(typeof initialState === 'object' && !Array.isArray(initialState))); 750 | this.state = initialState; 751 | }; 752 | Constructor.prototype = new ReactClassComponent(); 753 | Constructor.prototype.constructor = Constructor; 754 | 755 | mixSpecIntoComponent(Constructor, spec); 756 | 757 | // Initialize the defaultProps property after all mixins have been merged 758 | if (Constructor.getDefaultProps) { 759 | Constructor.defaultProps = Constructor.getDefaultProps(); 760 | } 761 | 762 | if ("production" !== process.env.NODE_ENV) { 763 | // This is a tag to indicate that the use of these method names is ok, 764 | // since it's used with createClass. If it's not, then it's likely a 765 | // mistake so we'll warn you to use the static property, property 766 | // initializer or constructor respectively. 767 | if (Constructor.getDefaultProps) { 768 | Constructor.getDefaultProps.isReactClassApproved = {}; 769 | } 770 | if (Constructor.prototype.getInitialState) { 771 | Constructor.prototype.getInitialState.isReactClassApproved = {}; 772 | } 773 | } 774 | 775 | ("production" !== process.env.NODE_ENV ? invariant( 776 | Constructor.prototype.render, 777 | 'createClass(...): Class specification must implement a `render` method.' 778 | ) : invariant(Constructor.prototype.render)); 779 | 780 | if ("production" !== process.env.NODE_ENV) { 781 | ("production" !== process.env.NODE_ENV ? warning( 782 | !Constructor.prototype.componentShouldUpdate, 783 | '%s has a method called ' + 784 | 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 785 | 'The name is phrased as a question because the function is ' + 786 | 'expected to return a value.', 787 | spec.displayName || 'A component' 788 | ) : null); 789 | } 790 | 791 | // Reduce time spent doing lookups by setting these on the prototype. 792 | for (var methodName in ReactClassInterface) { 793 | if (!Constructor.prototype[methodName]) { 794 | Constructor.prototype[methodName] = null; 795 | } 796 | } 797 | 798 | // Legacy hook 799 | Constructor.type = Constructor; 800 | if ("production" !== process.env.NODE_ENV) { 801 | try { 802 | Object.defineProperty(Constructor, 'type', typeDeprecationDescriptor); 803 | } catch (x) { 804 | // IE will fail on defineProperty (es5-shim/sham too) 805 | } 806 | } 807 | 808 | return Constructor; 809 | } 810 | 811 | }; 812 | 813 | module.exports = ReactClass; 814 | -------------------------------------------------------------------------------- /lib/ReactComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | */ 5 | 6 | class ReactComponent { 7 | constructor(element) { 8 | this.element = element; 9 | this.type = element.type; 10 | this.props = element.props; 11 | } 12 | } 13 | 14 | export default ReactComponent; 15 | -------------------------------------------------------------------------------- /lib/ReactComponentFactory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @providesModule instantiateReactComponent 10 | * @typechecks static-only 11 | */ 12 | 13 | 'use strict'; 14 | 15 | var ReactCompositeComponent = require("./ReactCompositeComponent"); 16 | var ReactDomComponent = require("./ReactDomComponent"); 17 | var ReactTextComponent = require("./ReactTextComponent"); 18 | var ReactEmptyComponent = require("react/lib/ReactEmptyComponent"); 19 | var ReactNativeComponent = require("react/lib/ReactNativeComponent"); 20 | 21 | var assign = require("object-assign"); 22 | var invariant = require("react/lib/invariant"); 23 | var warning = require("react/lib/warning"); 24 | 25 | export const injections = { 26 | ReactDomComponent: null, 27 | ReactCompositeComponent: null, 28 | ReactTextComponent: ReactTextComponent 29 | }; 30 | 31 | export function instantiateReactComponent(element, parentCompositeType) { 32 | var instance; 33 | 34 | if (element === null || element === false) { 35 | element = ReactEmptyComponent.emptyElement; 36 | } 37 | 38 | if (typeof element === 'object') { 39 | 40 | if ("production" !== process.env.NODE_ENV) { 41 | ("production" !== process.env.NODE_ENV ? warning( 42 | element && (typeof element.type === 'function' || 43 | typeof element.type === 'string'), 44 | 'Only functions or strings can be mounted as React components.' 45 | ) : null); 46 | } 47 | 48 | // Special case string values 49 | if (parentCompositeType === element.type && 50 | typeof element.type === 'string') { 51 | // Avoid recursion if the wrapper renders itself. 52 | instance = new (injections.ReactDomComponent)(element); 53 | // All native components are currently wrapped in a composite so we're 54 | // safe to assume that this is what we should instantiate. 55 | } else { 56 | instance = new (injections.ReactCompositeComponent)(element); 57 | } 58 | } else if (typeof element === 'string' || typeof element === 'number') { 59 | instance = new (injections.ReactTextComponent)(element); 60 | } else { 61 | ("production" !== process.env.NODE_ENV ? invariant( 62 | false, 63 | 'Encountered invalid React node of type %s', 64 | typeof element 65 | ) : invariant(false)); 66 | } 67 | 68 | // Internal instances should fully constructed at this point, so they should 69 | // not get any new fields added to them at this point. 70 | if ("production" !== process.env.NODE_ENV) { 71 | if (Object.preventExtensions) { 72 | Object.preventExtensions(instance); 73 | } 74 | } 75 | 76 | return instance; 77 | } 78 | -------------------------------------------------------------------------------- /lib/ReactCompositeComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | */ 5 | 6 | import assign from 'object-assign'; 7 | import ReactComponent from './ReactComponent'; 8 | import ReactNativeComponent from 'react/lib/ReactNativeComponent'; 9 | import {instantiateReactComponent} from './ReactComponentFactory'; 10 | import ReactPropTypeLocations from 'react/lib/ReactPropTypeLocations'; 11 | import ReactPropTypeLocationNames from 'react/lib/ReactPropTypeLocationNames'; 12 | import ReactElement from 'react/lib/ReactElement'; 13 | 14 | import invariant from 'react/lib/invariant'; 15 | import warning from 'react/lib/warning'; 16 | 17 | class ReactCompositeComponent extends ReactComponent { 18 | constructor(element) { 19 | super(element); 20 | } 21 | 22 | mountComponent(rootNodeID, transaction, context) { 23 | var Component = ReactNativeComponent.getComponentClassForElement( 24 | this.element 25 | ); 26 | var instance = new Component(this.props, context); 27 | if (instance.state === undefined) { 28 | instance.state = null; 29 | } 30 | this._validateComponentInstance(Component, instance); 31 | 32 | instance.componentWillMount && instance.componentWillMount(); 33 | 34 | var renderedChild = instance.render(); 35 | this._validateRenderedChild(renderedChild); 36 | 37 | var instantiatedChild = instantiateReactComponent(renderedChild, this.type); 38 | 39 | var childContext = instance.getChildContext && instance.getChildContext(); 40 | if (childContext) { 41 | this._validateChildContext(instance, childContext); 42 | } 43 | 44 | if (childContext) { 45 | childContext = assign({}, context, childContext); 46 | } 47 | 48 | return instantiatedChild.mountComponent(rootNodeID, transaction, childContext || context); 49 | } 50 | 51 | getName() { 52 | return this.type.displayName || this.type.name || null; 53 | } 54 | 55 | _validateComponentInstance(Component, instance) { 56 | if ("production" !== process.env.NODE_ENV) { 57 | // This will throw later in _renderValidatedComponent, but add an early 58 | // warning now to help debugging 59 | ("production" !== process.env.NODE_ENV ? warning( 60 | instance.render != null, 61 | '%s(...): No `render` method found on the returned component ' + 62 | 'instance: you may have forgotten to define `render` in your ' + 63 | 'component or you may have accidentally tried to render an element ' + 64 | 'whose type is a function that isn\'t a React component.', 65 | Component.displayName || Component.name || 'Component' 66 | ) : null); 67 | // Since plain JS classes are defined without any special initialization 68 | // logic, we can not catch common errors early. Therefore, we have to 69 | // catch them here, at initialization time, instead. 70 | ("production" !== process.env.NODE_ENV ? warning( 71 | !instance.getInitialState || 72 | instance.getInitialState.isReactClassApproved, 73 | 'getInitialState was defined on %s, a plain JavaScript class. ' + 74 | 'This is only supported for classes created using React.createClass. ' + 75 | 'Did you mean to define a state property instead?', 76 | this.getName() || 'a component' 77 | ) : null); 78 | ("production" !== process.env.NODE_ENV ? warning( 79 | !instance.getDefaultProps || 80 | instance.getDefaultProps.isReactClassApproved, 81 | 'getDefaultProps was defined on %s, a plain JavaScript class. ' + 82 | 'This is only supported for classes created using React.createClass. ' + 83 | 'Use a static property to define defaultProps instead.', 84 | this.getName() || 'a component' 85 | ) : null); 86 | ("production" !== process.env.NODE_ENV ? warning( 87 | !instance.propTypes, 88 | 'propTypes was defined as an instance property on %s. Use a static ' + 89 | 'property to define propTypes instead.', 90 | this.getName() || 'a component' 91 | ) : null); 92 | ("production" !== process.env.NODE_ENV ? warning( 93 | !instance.contextTypes, 94 | 'contextTypes was defined as an instance property on %s. Use a ' + 95 | 'static property to define contextTypes instead.', 96 | this.getName() || 'a component' 97 | ) : null); 98 | ("production" !== process.env.NODE_ENV ? warning( 99 | typeof instance.componentShouldUpdate !== 'function', 100 | '%s has a method called ' + 101 | 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 102 | 'The name is phrased as a question because the function is ' + 103 | 'expected to return a value.', 104 | (this.getName() || 'A component') 105 | ) : null); 106 | ("production" !== process.env.NODE_ENV ? invariant( 107 | typeof instance.state === 'object' && !Array.isArray(instance.state), 108 | '%s.state: must be set to an object or null', 109 | this.getName() || 'ReactCompositeComponent' 110 | ) : invariant(typeof instance.state === 'object' && !Array.isArray(instance.state))); 111 | } 112 | } 113 | 114 | _validateChildContext(instance, childContext) { 115 | ("production" !== process.env.NODE_ENV ? invariant( 116 | typeof instance.constructor.childContextTypes === 'object', 117 | '%s.getChildContext(): childContextTypes must be defined in order to ' + 118 | 'use getChildContext().', 119 | this.getName() || 'ReactCompositeComponent' 120 | ) : invariant(typeof instance.constructor.childContextTypes === 'object')); 121 | if ("production" !== process.env.NODE_ENV) { 122 | this._validatePropTypes( 123 | instance.constructor.childContextTypes, 124 | childContext, 125 | ReactPropTypeLocations.childContext 126 | ); 127 | } 128 | for (var name in childContext) { 129 | ("production" !== process.env.NODE_ENV ? invariant( 130 | name in instance.constructor.childContextTypes, 131 | '%s.getChildContext(): key "%s" is not defined in childContextTypes.', 132 | this.getName() || 'ReactCompositeComponent', 133 | name 134 | ) : invariant(name in instance.constructor.childContextTypes)); 135 | } 136 | return childContext; 137 | } 138 | 139 | /** 140 | * Assert that the props are valid 141 | * 142 | * @param {object} propTypes Map of prop name to a ReactPropType 143 | * @param {object} props 144 | * @param {string} location e.g. "prop", "context", "child context" 145 | * @private 146 | */ 147 | _validatePropTypes(propTypes, props, location) { 148 | // TODO: Stop validating prop types here and only use the element 149 | // validation. 150 | var componentName = this.getName(); 151 | for (var propName in propTypes) { 152 | if (propTypes.hasOwnProperty(propName)) { 153 | var error; 154 | try { 155 | // This is intentionally an invariant that gets caught. It's the same 156 | // behavior as without this statement except with a better message. 157 | ("production" !== process.env.NODE_ENV ? invariant( 158 | typeof propTypes[propName] === 'function', 159 | '%s: %s type `%s` is invalid; it must be a function, usually ' + 160 | 'from React.PropTypes.', 161 | componentName || 'React class', 162 | ReactPropTypeLocationNames[location], 163 | propName 164 | ) : invariant(typeof propTypes[propName] === 'function')); 165 | error = propTypes[propName](props, propName, componentName, location); 166 | } catch (ex) { 167 | error = ex; 168 | } 169 | if (error instanceof Error) { 170 | // We may want to extend this logic for similar errors in 171 | // React.render calls, so I'm abstracting it away into 172 | // a function to minimize refactoring in the future 173 | var name = this.getName(); 174 | var addendum = name ? ' Check the render method of `' + name + '`.' : ''; 175 | 176 | if (location === ReactPropTypeLocations.prop) { 177 | // Preface gives us something to blacklist in warning module 178 | ("production" !== process.env.NODE_ENV ? warning( 179 | false, 180 | 'Failed Composite propType: %s%s', 181 | error.message, 182 | addendum 183 | ) : null); 184 | } else { 185 | ("production" !== process.env.NODE_ENV ? warning( 186 | false, 187 | 'Failed Context Types: %s%s', 188 | error.message, 189 | addendum 190 | ) : null); 191 | } 192 | } 193 | } 194 | } 195 | } 196 | 197 | _validateRenderedChild(renderedChild) { 198 | ("production" !== process.env.NODE_ENV ? invariant( 199 | // TODO: An `isValidNode` function would probably be more appropriate 200 | renderedChild === null || renderedChild === false || 201 | ReactElement.isValidElement(renderedChild), 202 | '%s.render(): A valid ReactComponent must be returned. You may have ' + 203 | 'returned undefined, an array or some other invalid object.', 204 | this.getName() || 'ReactCompositeComponent' 205 | ) : invariant(// TODO: An `isValidNode` function would probably be more appropriate 206 | renderedChild === null || renderedChild === false || 207 | ReactElement.isValidElement(renderedChild))); 208 | } 209 | } 210 | 211 | export default ReactCompositeComponent; 212 | -------------------------------------------------------------------------------- /lib/ReactDomComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | */ 5 | 6 | import ReactComponent from './ReactComponent'; 7 | import assign from 'object-assign'; 8 | import traverseAllChildren from 'react/lib/traverseAllChildren'; 9 | import {instantiateReactComponent} from './ReactComponentFactory'; 10 | 11 | var invariant = require("react/lib/invariant"); 12 | var warning = require("react/lib/warning"); 13 | 14 | var validatedTagCache = {}; 15 | var VALID_TAG_REGEX = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/; // Simplified subset 16 | var hasOwnProperty = {}.hasOwnProperty; 17 | function validateDangerousTag(tag) { 18 | if (!hasOwnProperty.call(validatedTagCache, tag)) { 19 | ("production" !== process.env.NODE_ENV ? invariant(VALID_TAG_REGEX.test(tag), 'Invalid tag: %s', tag) : invariant(VALID_TAG_REGEX.test(tag))); 20 | validatedTagCache[tag] = true; 21 | } 22 | } 23 | 24 | class ReactDomComponent extends ReactComponent { 25 | constructor(element) { 26 | super(element); 27 | validateDangerousTag(this.type); 28 | } 29 | 30 | mountComponent(rootNodeId, transaction, context) { 31 | this._validateProps(this.props); 32 | return ( 33 | createOpenTagMarkup(this, rootNodeId, transaction, context) + 34 | createContentMarkup(this, rootNodeId, transaction, context) + 35 | createCloseTagMarkup(this, rootNodeId, transaction, context) 36 | ); 37 | } 38 | 39 | _validateProps(props) { 40 | if (!props) { 41 | return; 42 | } 43 | // Note the use of `==` which checks for null or undefined. 44 | if (props.dangerouslySetInnerHTML != null) { 45 | ("production" !== process.env.NODE_ENV ? invariant( 46 | props.children == null, 47 | 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.' 48 | ) : invariant(props.children == null)); 49 | ("production" !== process.env.NODE_ENV ? invariant( 50 | typeof props.dangerouslySetInnerHTML === 'object' && 51 | '__html' in props.dangerouslySetInnerHTML, 52 | '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' + 53 | 'Please visit https://fb.me/react-invariant-dangerously-set-inner-html ' + 54 | 'for more information.' 55 | ) : invariant(typeof props.dangerouslySetInnerHTML === 'object' && 56 | '__html' in props.dangerouslySetInnerHTML)); 57 | } 58 | if ("production" !== process.env.NODE_ENV) { 59 | ("production" !== process.env.NODE_ENV ? warning( 60 | props.innerHTML == null, 61 | 'Directly setting property `innerHTML` is not permitted. ' + 62 | 'For more information, lookup documentation on `dangerouslySetInnerHTML`.' 63 | ) : null); 64 | ("production" !== process.env.NODE_ENV ? warning( 65 | !props.contentEditable || props.children == null, 66 | 'A component is `contentEditable` and contains `children` managed by ' + 67 | 'React. It is now your responsibility to guarantee that none of ' + 68 | 'those nodes are unexpectedly modified or duplicated. This is ' + 69 | 'probably not intentional.' 70 | ) : null); 71 | } 72 | ("production" !== process.env.NODE_ENV ? invariant( 73 | props.style == null || typeof props.style === 'object', 74 | 'The `style` prop expects a mapping from style properties to values, ' + 75 | 'not a string. For example, style={{marginRight: spacing + \'em\'}} when ' + 76 | 'using JSX.' 77 | ) : invariant(props.style == null || typeof props.style === 'object')); 78 | } 79 | } 80 | 81 | function mountChildren(component, rootNodeID, transaction, context) { 82 | var mountImages = []; 83 | var element = component.element; 84 | var childrenToUse = component.props.children; 85 | traverseAllChildren(childrenToUse, function mountChild(traverseContext, child, name) { 86 | if (child == null) { 87 | return; 88 | } 89 | var childInstance = instantiateReactComponent(child, element.type); 90 | // Inlined for performance, see `ReactInstanceHandles.createReactID`. 91 | var rootID = rootNodeID + name; 92 | var mountImage = childInstance.mountComponent( 93 | rootID, 94 | transaction, 95 | context 96 | ); 97 | mountImages.push(mountImage); 98 | }, mountImages); 99 | 100 | return mountImages; 101 | } 102 | 103 | var omittedCloseTags = { 104 | 'area': true, 105 | 'base': true, 106 | 'br': true, 107 | 'col': true, 108 | 'embed': true, 109 | 'hr': true, 110 | 'img': true, 111 | 'input': true, 112 | 'keygen': true, 113 | 'link': true, 114 | 'meta': true, 115 | 'param': true, 116 | 'source': true, 117 | 'track': true, 118 | 'wbr': true 119 | // NOTE: menuitem's close tag should be omitted, but that causes problems. 120 | }; 121 | function createCloseTagMarkup(component, rootNodeId, transaction, context) { 122 | return omittedCloseTags[component.type] ? '' : ''; 123 | } 124 | 125 | /** 126 | * Creates markup for the open tag and all attributes. 127 | * 128 | * This method has side effects because events get registered. 129 | * 130 | * Iterating over object properties is faster than iterating over arrays. 131 | * @see http://jsperf.com/obj-vs-arr-iteration 132 | * 133 | * @private 134 | * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction 135 | * @return {string} Markup of opening tag. 136 | */ 137 | var CSSPropertyOperations = require('react/lib/CSSPropertyOperations'); 138 | var DOMPropertyOperations = require('react/lib/DOMPropertyOperations'); 139 | function createOpenTagMarkup(component, rootNodeId, transaction, context) { 140 | var props = component.props; 141 | var ret = '<' + component.type; 142 | 143 | for (var propKey in props) { 144 | if (!props.hasOwnProperty(propKey)) { 145 | continue; 146 | } 147 | var propValue = props[propKey]; 148 | if (propValue == null) { 149 | continue; 150 | } 151 | 152 | if (propKey === 'style') { 153 | if (propValue) { 154 | propValue = assign({}, props.style); 155 | } 156 | propValue = CSSPropertyOperations.createMarkupForStyles(propValue); 157 | } 158 | var markup = 159 | DOMPropertyOperations.createMarkupForProperty(propKey, propValue); 160 | if (markup) { 161 | ret += ' ' + markup; 162 | } 163 | } 164 | 165 | // For static pages, no need to put React ID and checksum. Saves lots of 166 | // bytes. 167 | if (transaction.renderToStaticMarkup) { 168 | return ret + '>'; 169 | } 170 | 171 | var markupForID = DOMPropertyOperations.createMarkupForID(rootNodeId); 172 | return ret + ' ' + markupForID + '>'; 173 | } 174 | 175 | /** 176 | * Creates markup for the content between the tags. 177 | * 178 | * @private 179 | * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction 180 | * @param {object} context 181 | * @return {string} Content markup. 182 | */ 183 | var escapeTextContentForBrowser = require('react/lib/escapeTextContentForBrowser'); 184 | var CONTENT_TYPES = {'string': true, 'number': true}; 185 | function createContentMarkup(component, rootNodeId, transaction, context) { 186 | var tag = component.type; 187 | var prefix = ''; 188 | if (tag === 'listing' || 189 | tag === 'pre' || 190 | tag === 'textarea') { 191 | // Add an initial newline because browsers ignore the first newline in 192 | // a ,
, or ');
 8 |     //        done();
 9 |     //    });
10 |     //});
11 |     //it('should render form', function (done) {
12 |     //    React.renderToStaticMarkup(');
14 |     //        done();
15 |     //    });
16 |     //});
17 |     //it('should render iframe', function (done) {
18 |     //    React.renderToStaticMarkup(');
20 |     //        done();
21 |     //    });
22 |     //});
23 |     //it('should render img', function (done) {
24 |     //    React.renderToStaticMarkup(');
26 |     //        done();
27 |     //    });
28 |     //});
29 |     //it('should render input', function (done) {
30 |     //    React.renderToStaticMarkup(');
32 |     //        done();
33 |     //    });
34 |     //});
35 |     //it('should render option', function (done) {
36 |     //    React.renderToStaticMarkup(');
38 |     //        done();
39 |     //    });
40 |     //});
41 |     //it('should render select', function (done) {
42 |     //    React.renderToStaticMarkup(');
44 |     //        done();
45 |     //    });
46 |     //});
47 |     it('should render textarea', function (done) {
48 |         React.renderToStaticMarkup(
49 |             ');
52 |                 done();
53 |             }
54 |         );
55 |     });
56 |     //it('should render text', function (done) {
57 |     //    React.renderToStaticMarkup(');
59 |     //        done();
60 |     //    });
61 |     //});
62 | });
63 | 


--------------------------------------------------------------------------------
/tests/unit/stateful.js:
--------------------------------------------------------------------------------
 1 | var assert = require('assert');
 2 | var React = require('../../index');
 3 | var StatefulComponentNoInitialState = React.createClass({
 4 |     componentWillMount: function () {
 5 |         this.setState({
 6 |             foo: 'baz'
 7 |         });
 8 |     },
 9 |     render: function () {
10 |         return 
{this.state.foo}
; 11 | } 12 | }); 13 | var StatefulComponent = React.createClass({ 14 | getInitialState: function () { 15 | return { 16 | foo: 'bar' 17 | }; 18 | }, 19 | componentWillMount: function () { 20 | this.setState({ 21 | foo: 'baz' 22 | }); 23 | }, 24 | render: function () { 25 | return
{this.state.foo}
; 26 | } 27 | }); 28 | 29 | describe('StateFul', function () { 30 | it('should render with updated state', function (done) { 31 | React.renderToStaticMarkup(, function (err, markup) { 32 | assert.equal(markup, '
baz
'); 33 | done(); 34 | }); 35 | }); 36 | it('should render without initial state', function (done) { 37 | React.renderToStaticMarkup(, function (err, markup) { 38 | assert.equal(markup, '
baz
'); 39 | done(); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /utils/renderAppWithFreshReact.js: -------------------------------------------------------------------------------- 1 | var mockery = require('mockery'); 2 | var PromiseLib = require('es6-promise').Promise; 3 | 4 | module.exports = function renderAppWithFreshReact(reactPath, appPath, statePath) { 5 | return new PromiseLib(function (resolve, reject) { 6 | mockery.enable({ 7 | useCleanCache: true, 8 | warnOnUnregistered: false, 9 | warnOnReplace: true 10 | }); 11 | var state = require(statePath); 12 | var React = require(reactPath); 13 | mockery.registerMock('react', React); 14 | mockery.registerMock('react/addons', React); 15 | var app = require(appPath); 16 | app.rehydrate(state).then(function (context) { 17 | var markup = React.renderToStaticMarkup(context.createElement()); 18 | mockery.deregisterAll(); 19 | mockery.resetCache(); 20 | mockery.disable(); 21 | resolve({ 22 | context: context, 23 | React: React, 24 | markup: markup 25 | }); 26 | }).catch(reject); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | module.exports = { 4 | entry: './index.js', 5 | output: { 6 | library: "React", 7 | libraryTarget: "umd", 8 | path: __dirname + '/dist' 9 | }, 10 | module: { 11 | loaders: [ 12 | { test: /\.(js)$/, exclude: /node_modules/, loader: require.resolve('babel-loader') } 13 | ] 14 | }, 15 | plugins: [ 16 | new webpack.DefinePlugin({ 17 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') 18 | }) 19 | ] 20 | }; 21 | --------------------------------------------------------------------------------