├── .gitignore ├── README.md ├── appendix ├── 0-Terminology.md ├── 1-Rendering-A-Top-Level-Element.md ├── 2-Reconcile-Transaction.md └── 3-Host-Environment-Examples.md ├── example.js ├── package.json ├── src ├── fiber-types │ ├── .flowconfig │ ├── React.js.flow │ ├── ReactCompositeComponentTypes.js.flow │ ├── ReactCoroutine.js.flow │ ├── ReactElementType.js.flow │ ├── ReactFiber.js.flow │ ├── ReactFiberReconciler.js.flow │ ├── ReactFiberRoot.js.flow │ ├── ReactFiberUpdateQueue.js.flow │ ├── ReactInstanceType.js.flow │ ├── ReactPortal.js.flow │ ├── ReactPriorityLevel.js.flow │ ├── ReactTypeOfSideEffect.js.flow │ ├── ReactTypeOfWork.js.flow │ ├── ReactTypes.js.flow │ └── package.json ├── fiber │ ├── .flowconfig │ ├── HowDoesFiberWork.md │ ├── README.md │ ├── ReactTinyFiber.js │ ├── ReactTinyTypes.js │ ├── index.js │ ├── package.json │ └── yarn.lock └── stack │ ├── README.md │ ├── component.js │ ├── index.js │ ├── injection.js │ ├── mount.js │ ├── package.js │ ├── reconcileTransaction.js │ ├── utilities │ ├── invariants.js │ └── serialize.js │ └── yarn.lock ├── test.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tiny React Renderer 2 | 3 | Creating a React Renderer will give you the opportunity to apply the same React 4 | knowledge that you and your team already know and enjoy from the web and native 5 | to whatever host environment you need. 6 | 7 | Creating a renderer is a fairly straight-forward affair once you know what 8 | you’re looking for. 9 | 10 | Many languages have this concept of a `main`—the entry point to your 11 | application. If you look at any React application code you’ve written you’ll see 12 | that you “start” your app with a call like the following: 13 | 14 | ```jsx 15 | // web 16 | ReactDOM.render(React.createElement(MyApp), document.getElementById('app')); 17 | 18 | // native 19 | AppRegistry.registerComponent('MyApp', () => MyApp); 20 | ``` 21 | 22 | This is where your application enters into the React domain and comes alive. Your 23 | root React element is instantiated and attached to the host environment. 24 | 25 | If you follow either the ReactDOM or React Native codebases from where these 26 | methods are defined you will quickly find yourself at the `React{Host}Mount.js` 27 | file. Our renderer also begins there. 28 | 29 | With that let’s get started! Our tour continues in [./src/stack/mount.js](./src/stack/mount.js). 30 | 31 | ## Work in Progress 32 | 33 | Please note this guide is a work in progress. Much of this knowledge is derived 34 | from my experience in creating [React Hardware](https://github.com/iamdustan/react-hardware). 35 | 36 | ## Tests 37 | 38 | * `npm test` will run the tests with the stack-based renderer 39 | * `npm test -- --fiber` will run the tests with the upcoming fiber implementation 40 | 41 | ## Renderer Implementations 42 | 43 | * Stack 44 | * Fiber 45 | 46 | The React that we have worked with to date has been using what is called the 47 | stack renderer. This is because it is implemented on the traditional JavaScript 48 | stack and operates within those confines. 49 | 50 | Fiber, on the other-hand, can be thought of like a React VM. It is influenced by 51 | OCaml, algebraic effects, and other functional ideas. From an implementation 52 | level, Fiber has a first-class renderer API allowing creating custom renderers a 53 | much nicer experience. 54 | 55 | The documentation for stack is no in maintenance mode and will not be very 56 | accurate as time progresses. This can be found in `./src/stack`. The Fiber API 57 | changes will be followed throughout its development to a stable release in 58 | `./src/fiber` 59 | 60 | ## Thanks 61 | 62 | * [@thejameskyle](https://github.com/thejameskyle): for the inspiration of repo style 63 | * [@ryanflorence](https://github.com/ryanflorence) and [@mjackson](https://github.com/mjackson) for React Router and the problem that inspired this 64 | * [@gaearon](https://github.com/gaearon), [@matthewwithanm](https://github.com/matthewwithanm), 65 | [@vjeux](https://github.com/vjeux), [@zpao](https://github.com/zpao), 66 | [@Yomguithereal](https://github.com/Yomguithereal), [@axemclion](https://github.com/axemclion), 67 | and everyone else who has helped me poke around the React codebase. 68 | -------------------------------------------------------------------------------- /appendix/0-Terminology.md: -------------------------------------------------------------------------------- 1 | # Terminology 2 | 3 | Required reading: 4 | * [React Components, Elements, and Instances](https://facebook.github.io/react/blog/2015/12/18/react-components-elements-and-instances.html) 5 | * [React – Basic Theoretical Concepts](https://github.com/reactjs/react-basic) 6 | 7 | Coming soon. PRs also accepted :smile: 8 | 9 | tldr; 10 | 11 | * ReactElement: The return value of `` / 12 | `React.createElement('jsx', props)` calls 13 | * Component: There is the public React.Component interface that users interact 14 | with. There is a mirror internal component class which is responsible for the 15 | managing the internal state, lifecycle, warnings, invariants, props, et cetera. 16 | * Instance: ... 17 | 18 | -------------------------------------------------------------------------------- /appendix/1-Rendering-A-Top-Level-Element.md: -------------------------------------------------------------------------------- 1 | # Rendering a Top Level Element 2 | 3 | Coming soon. PRs also accepted :smile: 4 | 5 | -------------------------------------------------------------------------------- /appendix/2-Reconcile-Transaction.md: -------------------------------------------------------------------------------- 1 | # Reconcile Transaction 2 | 3 | Coming soon. PRs also accepted :smile: 4 | 5 | -------------------------------------------------------------------------------- /appendix/3-Host-Environment-Examples.md: -------------------------------------------------------------------------------- 1 | # Host Environment Example 2 | 3 | Coming soon. PRs also accepted :smile: 4 | 5 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { 3 | Router, 4 | Route, 5 | Link, 6 | browserHistory, 7 | renderToJSON 8 | } = require('react-router'); 9 | 10 | console.log( 11 | ReactDOM.render( 12 | renderToJSON( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | ) 22 | ); 23 | 24 | /** 25 | * { path: '/', 26 | * component: App, 27 | * childRoutes: [ 28 | * ] 29 | * ... 30 | * } 31 | */ 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-react-renderer", 3 | "version": "0.2.0", 4 | "description": "A tiny React renderer to demonstrate how to write a renderer.", 5 | "main": "./index.js", 6 | "scripts": { 7 | "flow": "pushd src/fiber; flow; popd;", 8 | "test-stack": "node ./test --stack", 9 | "test-fiber": "node ./test --fiber", 10 | "test": "node ./test && node ./test --fiber && npm run flow" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/iamdustan/tiny-react-renderer" 15 | }, 16 | "files": [ 17 | "src", 18 | "package.json", 19 | "README.md" 20 | ], 21 | "keywords": [ 22 | "react", 23 | "reactjs", 24 | "renderer" 25 | ], 26 | "author": "Dustan Kasten ", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/iamdustan/tiny-render-renderer/issues" 30 | }, 31 | "homepage": "https://github.com/iamdustan/tiny-render-renderer", 32 | "babel": { 33 | "plugins": [ 34 | "transform-flow-strip-types" 35 | ] 36 | }, 37 | "dependencies": { 38 | "babel-register": "^6.23.0", 39 | "fbjs": "^0.8.4", 40 | "react": "15.3.x" 41 | }, 42 | "devDependencies": { 43 | "babel-plugin-transform-flow-strip-types": "^6.22.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/fiber-types/.flowconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamdustan/tiny-react-renderer/2337d6558f0206e2b30c38e5df980bb70a046a92/src/fiber-types/.flowconfig -------------------------------------------------------------------------------- /src/fiber-types/React.js.flow: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | export type ReactComponent = typeof React$Component; 4 | export type ReactElement = typeof React$Element; 5 | 6 | -------------------------------------------------------------------------------- /src/fiber-types/ReactCompositeComponentTypes.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, 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 ReactCompositeComponentTypes 10 | * @flow 11 | */ 12 | 13 | export type CompositeComponentTypes = 0 | 1 | 2; 14 | 15 | module.exports = { 16 | ImpureClass: 0, 17 | PureClass: 1, 18 | StatelessFunctional: 2, 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /src/fiber-types/ReactCoroutine.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014-present, 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 ReactCoroutine 10 | * @flow 11 | */ 12 | 13 | 'use strict'; 14 | 15 | import type { ReactNodeList } from './ReactTypes'; 16 | 17 | type ReifiedYield = { continuation: Object, props: Object }; 18 | type CoroutineHandler = (props: T, yields: Array) => ReactNodeList; 19 | 20 | export type ReactCoroutine = { 21 | $$typeof: Symbol | number, 22 | key: null | string, 23 | children: any, 24 | // This should be a more specific CoroutineHandler 25 | handler: (props: any, yields: Array) => ReactNodeList, 26 | props: any, 27 | }; 28 | export type ReactYield = { 29 | $$typeof: Symbol | number, 30 | key: null | string, 31 | props: Object, 32 | continuation: mixed 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /src/fiber-types/ReactElementType.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-present, 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 | * @flow 10 | * @providesModule ReactElementType 11 | */ 12 | 13 | 'use strict'; 14 | 15 | export type Source = { 16 | fileName: string, 17 | lineNumber: number, 18 | }; 19 | 20 | export type ReactElement = { 21 | $$typeof: any, 22 | type: any, 23 | key: any, 24 | ref: any, 25 | props: any, 26 | _owner: any, // ReactInstance or ReactFiber 27 | 28 | // __DEV__ 29 | _store: { 30 | validated: boolean, 31 | }, 32 | _self: ReactElement, 33 | _shadowChildren: any, 34 | _source: Source, 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /src/fiber-types/ReactFiber.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, 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 ReactFiber 10 | * @flow 11 | */ 12 | import type { Source } from './ReactElementType'; 13 | import type { ReactInstance, DebugID } from './ReactInstanceType'; 14 | import type { TypeOfWork } from './ReactTypeOfWork'; 15 | import type { TypeOfSideEffect } from './ReactTypeOfSideEffect'; 16 | import type { PriorityLevel } from './ReactPriorityLevel'; 17 | import type { UpdateQueue } from './ReactFiberUpdateQueue'; 18 | 19 | 20 | // A Fiber is work on a Component that needs to be done or was done. There can 21 | // be more than one per component. 22 | export type Fiber = { 23 | // __DEV__ only 24 | _debugID ?: DebugID, 25 | _debugSource ?: Source | null, 26 | _debugOwner ?: Fiber | ReactInstance | null, // Stack compatible 27 | 28 | // These first fields are conceptually members of an Instance. This used to 29 | // be split into a separate type and intersected with the other Fiber fields, 30 | // but until Flow fixes its intersection bugs, we've merged them into a 31 | // single type. 32 | 33 | // An Instance is shared between all versions of a component. We can easily 34 | // break this out into a separate object to avoid copying so much to the 35 | // alternate versions of the tree. We put this on a single object for now to 36 | // minimize the number of objects created during the initial render. 37 | 38 | // Tag identifying the type of fiber. 39 | tag: TypeOfWork, 40 | 41 | // Unique identifier of this child. 42 | key: null | string, 43 | 44 | // The function/class/module associated with this fiber. 45 | type: any, 46 | 47 | // The local state associated with this fiber. 48 | stateNode: any, 49 | 50 | // Conceptual aliases 51 | // parent : Instance -> return The parent happens to be the same as the 52 | // return fiber since we've merged the fiber and instance. 53 | 54 | // Remaining fields belong to Fiber 55 | 56 | // The Fiber to return to after finishing processing this one. 57 | // This is effectively the parent, but there can be multiple parents (two) 58 | // so this is only the parent of the thing we're currently processing. 59 | // It is conceptually the same as the return address of a stack frame. 60 | return: Fiber | null, 61 | 62 | // Singly Linked List Tree Structure. 63 | child: Fiber | null, 64 | sibling: Fiber | null, 65 | index: number, 66 | 67 | // The ref last used to attach this node. 68 | // I'll avoid adding an owner field for prod and model that as functions. 69 | ref: null | (((handle : mixed) => void) & { _stringRef: ?string }), 70 | 71 | // Input is the data coming into process this fiber. Arguments. Props. 72 | pendingProps: any, // This type will be more specific once we overload the tag. 73 | // TODO: I think that there is a way to merge pendingProps and memoizedProps. 74 | memoizedProps: any, // The props used to create the output. 75 | 76 | // A queue of state updates and callbacks. 77 | updateQueue: UpdateQueue | null, 78 | 79 | // The state used to create the output 80 | memoizedState: any, 81 | 82 | // Effect 83 | effectTag: TypeOfSideEffect, 84 | 85 | // Singly linked list fast path to the next fiber with side-effects. 86 | nextEffect: Fiber | null, 87 | 88 | // The first and last fiber with side-effect within this subtree. This allows 89 | // us to reuse a slice of the linked list when we reuse the work done within 90 | // this fiber. 91 | firstEffect: Fiber | null, 92 | lastEffect: Fiber | null, 93 | 94 | // This will be used to quickly determine if a subtree has no pending changes. 95 | pendingWorkPriority: PriorityLevel, 96 | 97 | // This value represents the priority level that was last used to process this 98 | // component. This indicates whether it is better to continue from the 99 | // progressed work or if it is better to continue from the current state. 100 | progressedPriority: PriorityLevel, 101 | 102 | // If work bails out on a Fiber that already had some work started at a lower 103 | // priority, then we need to store the progressed work somewhere. This holds 104 | // the started child set until we need to get back to working on it. It may 105 | // or may not be the same as the "current" child. 106 | progressedChild: Fiber | null, 107 | 108 | // When we reconcile children onto progressedChild it is possible that we have 109 | // to delete some child fibers. We need to keep track of this side-effects so 110 | // that if we continue later on, we have to include those effects. Deletions 111 | // are added in the reverse order from sibling pointers. 112 | progressedFirstDeletion: Fiber | null, 113 | progressedLastDeletion: Fiber | null, 114 | 115 | // This is a pooled version of a Fiber. Every fiber that gets updated will 116 | // eventually have a pair. There are cases when we can clean up pairs to save 117 | // memory if we need to. 118 | alternate: Fiber | null, 119 | 120 | // Conceptual aliases 121 | // workInProgress : Fiber -> alternate The alternate used for reuse happens 122 | // to be the same as work in progress. 123 | 124 | }; 125 | -------------------------------------------------------------------------------- /src/fiber-types/ReactFiberReconciler.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, 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 ReactFiberReconciler 10 | * @flow 11 | */ 12 | 13 | 'use strict'; 14 | 15 | import type { ReactComponent } from './React'; 16 | 17 | import type { Fiber } from './ReactFiber'; 18 | import type { FiberRoot } from './ReactFiberRoot'; 19 | import type { PriorityLevel } from './ReactPriorityLevel'; 20 | import type { ReactNodeList } from './ReactTypes'; 21 | 22 | type Deadline = { 23 | timeRemaining : () => number 24 | }; 25 | 26 | type OpaqueHandle = Fiber; 27 | type OpaqueRoot = FiberRoot; 28 | 29 | export type HostConfig = { 30 | 31 | getRootHostContext(rootContainerInstance : C) : CX, 32 | getChildHostContext(parentHostContext : CX, type : T) : CX, 33 | getPublicInstance(instance : I | TI) : PI, 34 | 35 | createInstance( 36 | type : T, 37 | props : P, 38 | rootContainerInstance : C, 39 | hostContext : CX, 40 | internalInstanceHandle : OpaqueHandle 41 | ) : I, 42 | appendInitialChild(parentInstance : I, child : I | TI) : void, 43 | finalizeInitialChildren(parentInstance : I, type : T, props : P, rootContainerInstance : C) : boolean, 44 | 45 | prepareUpdate( 46 | instance : I, 47 | type : T, 48 | oldProps : P, 49 | newProps : P, 50 | rootContainerInstance : C, 51 | hostContext : CX 52 | ) : null | PL, 53 | commitUpdate( 54 | instance : I, 55 | updatePayload : PL, 56 | type : T, 57 | oldProps : P, 58 | newProps : P, 59 | internalInstanceHandle : OpaqueHandle 60 | ) : void, 61 | commitMount(instance : I, type : T, newProps : P, internalInstanceHandle : OpaqueHandle) : void, 62 | 63 | shouldSetTextContent(props : P) : boolean, 64 | resetTextContent(instance : I) : void, 65 | 66 | createTextInstance( 67 | text : string, 68 | rootContainerInstance : C, 69 | hostContext : CX, 70 | internalInstanceHandle : OpaqueHandle 71 | ) : TI, 72 | commitTextUpdate(textInstance : TI, oldText : string, newText : string) : void, 73 | 74 | appendChild(parentInstance : I | C, child : I | TI) : void, 75 | insertBefore(parentInstance : I | C, child : I | TI, beforeChild : I | TI) : void, 76 | removeChild(parentInstance : I | C, child : I | TI) : void, 77 | 78 | scheduleAnimationCallback(callback : () => void) : number | void, 79 | scheduleDeferredCallback(callback : (deadline : Deadline) => void) : number | void, 80 | 81 | prepareForCommit() : void, 82 | resetAfterCommit() : void, 83 | 84 | useSyncScheduling ?: boolean, 85 | }; 86 | 87 | export type Reconciler = { 88 | createContainer(containerInfo : C) : OpaqueRoot, 89 | updateContainer( 90 | element : ReactNodeList, 91 | container : OpaqueRoot, 92 | parentComponent : ?ReactComponent 93 | ) : void, 94 | performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void, 95 | batchedUpdates(fn : () => A) : A, 96 | unbatchedUpdates(fn : () => A) : A, 97 | syncUpdates(fn : () => A) : A, 98 | deferredUpdates(fn : () => A) : A, 99 | 100 | // Used to extract the return value from the initial render. Legacy API. 101 | getPublicRootInstance(container : OpaqueRoot) : (ReactComponent | TI | I | null), 102 | 103 | // Use for findDOMNode/findHostNode. Legacy API. 104 | findHostInstance(component : Fiber) : I | TI | null, 105 | }; 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/fiber-types/ReactFiberRoot.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, 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 ReactFiberRoot 10 | * @flow 11 | */ 12 | 13 | 'use strict'; 14 | 15 | import type { Fiber } from './ReactFiber'; 16 | import type { UpdateQueue } from './ReactFiberUpdateQueue'; 17 | 18 | export type FiberRoot = { 19 | // Any additional information from the host associated with this root. 20 | containerInfo: any, 21 | // The currently active root fiber. This is the mutable root of the tree. 22 | current: Fiber, 23 | // Determines if this root has already been added to the schedule for work. 24 | isScheduled: boolean, 25 | // The work schedule is a linked list. 26 | nextScheduledRoot: ?FiberRoot, 27 | // Linked list of callbacks to call after updates are committed. 28 | callbackList: ?UpdateQueue, 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /src/fiber-types/ReactFiberUpdateQueue.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, 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 ReactFiberUpdateQueue 10 | * @flow 11 | */ 12 | 13 | 'use strict'; 14 | 15 | type UpdateQueueNode = { 16 | partialState: any, 17 | callback: ?Function, 18 | callbackWasCalled: boolean, 19 | isReplace: boolean, 20 | next: ?UpdateQueueNode, 21 | }; 22 | 23 | export type UpdateQueue = UpdateQueueNode & { 24 | isForced: boolean, 25 | hasUpdate: boolean, 26 | hasCallback: boolean, 27 | tail: UpdateQueueNode 28 | }; 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/fiber-types/ReactInstanceType.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-present, 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 ReactInstanceType 10 | * @flow 11 | */ 12 | 13 | 'use strict'; 14 | 15 | import type {ReactElement} from './ReactElementType'; 16 | import type {CompositeComponentTypes} from './ReactCompositeComponentTypes'; 17 | 18 | export type DebugID = number; 19 | 20 | export type ReactInstance = { 21 | // Shared 22 | mountComponent: any, 23 | unmountComponent: any, 24 | receiveComponent: any, 25 | getName: () => string, 26 | getPublicInstance: any, 27 | _currentElement: ReactElement, 28 | 29 | // ReactCompositeComponent 30 | performInitialMountWithErrorHandling: any, 31 | performInitialMount: any, 32 | getHostNode: any, 33 | performUpdateIfNecessary: any, 34 | updateComponent: any, 35 | attachRef: (ref: string, component: ReactInstance) => void, 36 | detachRef: (ref: string) => void, 37 | _rootNodeID: number, 38 | _compositeType: CompositeComponentTypes, 39 | 40 | // ReactDOMComponent 41 | _tag: string, 42 | 43 | // instantiateReactComponent 44 | _mountIndex: number, 45 | _mountImage: any, 46 | // __DEV__ 47 | _debugID: DebugID, 48 | _warnedAboutRefsInRender: boolean, 49 | }; 50 | 51 | -------------------------------------------------------------------------------- /src/fiber-types/ReactPortal.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014-present, 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 ReactPortal 10 | * @flow 11 | */ 12 | 13 | 'use strict'; 14 | 15 | import type { ReactNodeList } from './ReactTypes'; 16 | 17 | export type ReactPortal = { 18 | $$typeof: Symbol | number, 19 | key: null | string, 20 | containerInfo: any, 21 | children : ReactNodeList, 22 | // TODO: figure out the API for cross-renderer implementation. 23 | implementation: any, 24 | }; 25 | -------------------------------------------------------------------------------- /src/fiber-types/ReactPriorityLevel.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, 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 ReactPriorityLevel 10 | * @flow 11 | */ 12 | 13 | 'use strict'; 14 | 15 | export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6; 16 | 17 | /* 18 | module.exports = { 19 | NoWork: 0, // No work is pending. 20 | SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects. 21 | TaskPriority: 2, // Completes at the end of the current tick. 22 | AnimationPriority: 3, // Needs to complete before the next frame. 23 | HighPriority: 4, // Interaction that needs to complete pretty soon to feel responsive. 24 | LowPriority: 5, // Data fetching, or result from updating stores. 25 | OffscreenPriority: 6, // Won't be visible but do the work in case it becomes visible. 26 | }; 27 | */ 28 | 29 | -------------------------------------------------------------------------------- /src/fiber-types/ReactTypeOfSideEffect.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, 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 ReactTypeOfSideEffect 10 | * @flow 11 | */ 12 | 13 | 'use strict'; 14 | 15 | export type TypeOfSideEffect = 0 | 1 | 2 | 3 | 4 | 8 | 16 | 32 | 64; 16 | 17 | module.exports = { 18 | NoEffect: 0, // 0b0000000 19 | Placement: 1, // 0b0000001 20 | Update: 2, // 0b0000010 21 | PlacementAndUpdate: 3, // 0b0000011 22 | Deletion: 4, // 0b0000100 23 | ContentReset: 8, // 0b0001000 24 | Callback: 16, // 0b0010000 25 | Err: 32, // 0b0100000 26 | Ref: 64, // 0b1000000 27 | }; 28 | -------------------------------------------------------------------------------- /src/fiber-types/ReactTypeOfWork.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-present, 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 ReactTypeOfWork 10 | * @flow 11 | */ 12 | 13 | 'use strict'; 14 | 15 | export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; 16 | 17 | module.exports = { 18 | IndeterminateComponent: 0, // Before we know whether it is functional or class 19 | FunctionalComponent: 1, 20 | ClassComponent: 2, 21 | HostRoot: 3, // Root of a host tree. Could be nested inside another node. 22 | HostPortal: 4, // A subtree. Could be an entry point to a different renderer. 23 | HostComponent: 5, 24 | HostText: 6, 25 | CoroutineComponent: 7, 26 | CoroutineHandlerPhase: 8, 27 | YieldComponent: 9, 28 | Fragment: 10, 29 | }; 30 | -------------------------------------------------------------------------------- /src/fiber-types/ReactTypes.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014-present, 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 ReactTypes 10 | * @flow 11 | */ 12 | 13 | 'use strict'; 14 | 15 | import type { ReactElement } from './React'; 16 | 17 | import type { ReactCoroutine, ReactYield } from './ReactCoroutine'; 18 | import type { ReactPortal } from './ReactPortal'; 19 | 20 | export type ReactNode = ReactElement | ReactCoroutine | ReactYield | ReactPortal | ReactText | ReactFragment; 21 | 22 | export type ReactFragment = ReactEmpty | Iterable; 23 | 24 | export type ReactNodeList = ReactEmpty | ReactNode; 25 | 26 | export type ReactText = string | number; 27 | 28 | export type ReactEmpty = null | void | boolean; 29 | 30 | -------------------------------------------------------------------------------- /src/fiber-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-fiber-types", 3 | "version": "16.0.0-alpha.3", 4 | "description": "eact is a JavaScript library for building user interfaces", 5 | "main": "ReactFiberReconciler.js", 6 | "repository": "http://github.com/iamdustan/tiny-react-renderer", 7 | "license": "BSD-3-Clause" 8 | } 9 | -------------------------------------------------------------------------------- /src/fiber/.flowconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamdustan/tiny-react-renderer/2337d6558f0206e2b30c38e5df980bb70a046a92/src/fiber/.flowconfig -------------------------------------------------------------------------------- /src/fiber/HowDoesFiberWork.md: -------------------------------------------------------------------------------- 1 | ## How Does Fiber Work? 2 | 3 | > Note: it is highly recommended that you read [https://github.com/acdlite/react-fiber-architecture](https://github.com/acdlite/react-fiber-architecture) 4 | > along with this. Andrew has a lot more foundational definitions and describes 5 | > the `fiber` data structure, whereas this describes how updates are scheduled 6 | > and commited from the renderers point of view. 7 | 8 | The fiber reconciler builds on the idea of Algebraic Effects. A custom renderer 9 | coordinates with the reconciler by informing it when certain effects should be 10 | scheduled. The type of effects are: 11 | 12 | * `NoEffect` 13 | * `Placement` 14 | * `Update` 15 | * `PlacementAndUpdate` 16 | * `Deletion` 17 | * `ContentReset` 18 | * `Callback` 19 | * `Err` 20 | * `Ref` 21 | 22 | This is likely best explained with an example using the DOM renderer. We’ll 23 | create a Composite Component that renders a text instance with the `"Hello"` 24 | inside of a `div` host instance. We’ll immediately render the `App` composite 25 | component updating the text instance contents to be the string `"World"`. 26 | 27 | ```jsx 28 | const App = (props) =>
{props.children}
; 29 | 30 | ReactDOM.render(Hello, document.body); 31 | ReactDOM.render(Goodbye, document.body); 32 | ``` 33 | 34 | In the initial rendering Fiber will schedule `PlacementAndUpdate` effects to 35 | create the simple `
-> [text#Hello]` tree. When reconciling the second 36 | render call, when beginning work Fiber will schedule a `ContentReset` effect to 37 | clear the text of the `div` and an `Update` effect to schedule setting the new 38 | text content. 39 | 40 | A renderer informs a fiber reconciler what text effects to schedule and how to 41 | complete them through the following handful of methods: 42 | 43 | * `shouldSetTextContent`: communicates to the reconciler if a `ContentReset` 44 | effect should be scheduled 45 | * `resetTextContent`: Fiber calls this method when commiting the `ContentReset` effect 46 | * `createTextInstance`: Fiber calls this method when it needs a new host text instance 47 | * `commitTextUpdate`: Fiber calls this method to commit the new text content `Update` effect 48 | 49 | -------------------------------------------------------------------------------- /src/fiber/README.md: -------------------------------------------------------------------------------- 1 | # Tiny React Renderer 2 | 3 | > Note that this is currently targeting the **React 16.0.0-alpha.3** release. 4 | 5 | Creating a fiber-based React renderer is quite direct, though there are a few 6 | awkward pieces around tooling that will be smoothed over in time. 7 | 8 | This guide can be read by jumping straight into the code to see the minimal work 9 | to implement a renderer, or you can read the [./HowDoesFiberWork.md](./HowDoesFiberWork.md) 10 | document for additional information on *how* Fiber works. 11 | 12 | With Fiber, all renderers begin (and maybe even end) in the React{Host}Fiber.js 13 | file. 14 | 15 | With that let’s get started in [./ReactTinyFiber.js](./ReactTinyFiber.js)! 16 | 17 | ## Work in Progress 18 | 19 | Please note this guide is a work in progress. Much of this knowledge is derived 20 | from my experience in creating [React Hardware](https://github.com/iamdustan/react-hardware). 21 | 22 | -------------------------------------------------------------------------------- /src/fiber/ReactTinyFiber.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * Welcome to the Tiny React Renderer on Fiber. 3 | * 4 | * The Reconciler API for the current targeted revision is available at: 5 | * https://github.com/facebook/react/blob/ca4325e3eff16b86879188eb996ebcc9a933336a/src/renderers/shared/fiber/ReactFiberReconciler.js#L48-L104 6 | * 7 | * A renderer is the public interface to a React reconciler. With Fiber you 8 | * create a reconciler by calling `ReactFiberReconciler` with a `HostConfig` 9 | * object. 10 | * 11 | * All types for creating a react reconciler are manually extracted into 12 | * `../react-types` for the current revision (16.0.0-alpha.3). 13 | * 14 | * @flow 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /** 20 | * The two internal types you need to be aware of. Everything else should be 21 | * kept private except host-specific things that you handle in your renderer. 22 | */ 23 | import type { HostConfig, Reconciler } from 'react-fiber-types'; 24 | import type { ReactNodeList } from 'react-fiber-types/ReactTypes'; 25 | 26 | // our renconciler types are defined in ./ReactTinyTypes.js for a convenient place to see 27 | // what types you’re expected to define when implementing a renderer 28 | import type { 29 | Props, 30 | Container, 31 | Instance, 32 | TextInstance, 33 | OpaqueHandle, 34 | HostContext, 35 | } from './ReactTinyTypes'; 36 | 37 | /** 38 | * This is the only entry point you need to create a Fiber renderer. Note that 39 | * it currently lives within the `react-dom` package and not `react. 40 | */ 41 | const ReactFiberReconciler : ( 42 | hostConfig: HostConfig<*, *, *, *, *, *, *, *> 43 | ) => Reconciler<*, *, *> = require('react-dom/lib/ReactFiberReconciler'); 44 | 45 | const LOG_STEPS = false; 46 | const log = (a, b, c) => { 47 | if (LOG_STEPS) { 48 | console.log(a, b, c); 49 | } 50 | }; 51 | 52 | const toJSON = (node) => { 53 | const props = node.props; 54 | if (typeof props.toJSON === 'function') { 55 | return props.toJSON(props); 56 | } 57 | 58 | let children = null; 59 | if (props.children) { 60 | if (Array.isArray(props.children)) { 61 | children = props.children.map(toJSON); 62 | } else if (props.children) { 63 | children = toJSON(props.children); 64 | } 65 | return Object.assign({}, props, {children}); 66 | } else { 67 | const clone = Object.assign({}, props); 68 | delete clone.children; 69 | return clone; 70 | } 71 | }; 72 | 73 | 74 | /** 75 | * The fun begins! 76 | * 77 | * We create a private reconciler instance. The methods defined here can be 78 | * thought of as the lifecycle of a renderer. React will manage all non-host 79 | * components, such as composites, stateless, and fragments. 80 | */ 81 | const TinyRenderer = ReactFiberReconciler({ 82 | 83 | // the tree creation and updating methods. If you’re familiar with the DOM API 84 | // this will look familiar 85 | 86 | createInstance( 87 | type : string, 88 | props : Props, 89 | rootContainerInstance : Container, 90 | hostContext : HostContext, 91 | internalInstanceHandle : Object 92 | ) { 93 | if (props.toJSON) { 94 | return props.toJSON(props); 95 | } else { 96 | return toJSON({props}); 97 | } 98 | }, 99 | 100 | // this is called instead of `appendChild` when the parentInstance is first 101 | // being created and mounted 102 | // added in https://github.com/facebook/react/pull/8400/ 103 | appendInitialChild( 104 | parentInstance : Instance, 105 | child : Instance | TextInstance 106 | ) : void { 107 | // 108 | log('appendInitialChild', child); 109 | }, 110 | 111 | 112 | appendChild( 113 | parentInstance : Instance | Container, 114 | child : Instance | TextInstance 115 | ) : void { 116 | log('appendChild', child); 117 | // const index = parentInstance.children.indexOf(child); 118 | // if (index !== -1) { 119 | // parentInstance.children.splice(index, 1); 120 | // } 121 | // parentInstance.children.push(child); 122 | }, 123 | 124 | removeChild( 125 | parentInstance : Instance | Container, 126 | child : Instance | TextInstance 127 | ) : void { 128 | log('removeChild', child); 129 | // parentInstance.removeChild(child); 130 | }, 131 | 132 | insertBefore( 133 | parentInstance : Instance | Container, 134 | child : Instance | TextInstance, 135 | beforeChild : Instance | TextInstance 136 | ) : void { 137 | log('insertBefore'); 138 | // parentInstance.insertBefore(child, beforeChild); 139 | }, 140 | 141 | // finalizeInitialChildren is the final HostConfig method called before 142 | // flushing the root component to the host environment 143 | 144 | finalizeInitialChildren( 145 | instance : Instance, 146 | type : string, 147 | props : Props, 148 | rootContainerInstance : Container 149 | ) : boolean { 150 | log('finalizeInitialChildren'); 151 | // setInitialProperties(instance, type, props, rootContainerInstance); 152 | return false; 153 | }, 154 | 155 | // prepare update is where you compute the diff for an instance. This is done 156 | // here to separate computation of the diff to the applying of the diff. Fiber 157 | // can reuse this work even if it pauses or aborts rendering a subset of the 158 | // tree. 159 | 160 | prepareUpdate( 161 | instance : Instance, 162 | type : string, 163 | oldProps : Props, 164 | newProps : Props, 165 | rootContainerInstance : Container, 166 | hostContext : HostContext 167 | ) : null | Array { 168 | log('TODO: prepareUpdate'); 169 | return null; 170 | // return diffProperties(instance, type, oldProps, newProps, rootContainerInstance, hostContext); 171 | }, 172 | 173 | commitUpdate( 174 | instance : Instance, 175 | updatePayload : Array, 176 | type : string, 177 | oldProps : Props, 178 | newProps : Props, 179 | internalInstanceHandle : Object, 180 | ) : void { 181 | // Apply the diff to the DOM node. 182 | // updateProperties(instance, updatePayload, type, oldProps, newProps); 183 | log('TODO: updateProperties'); 184 | }, 185 | 186 | // commitMount is called after initializeFinalChildren *if* 187 | // `initializeFinalChildren` returns true. 188 | 189 | commitMount( 190 | instance : Instance, 191 | type : string, 192 | newProps : Props, 193 | internalInstanceHandle : Object 194 | ) { 195 | log('commitMount'); 196 | // noop 197 | }, 198 | 199 | // HostContext is an internal object or reference for any bookkeeping your 200 | // renderer may need to do based on current location in the tree. In DOM this 201 | // is necessary for calling the correct `document.createElement` calls based 202 | // upon being in an `html`, `svg`, `mathml`, or other context of the tree. 203 | 204 | getRootHostContext(rootContainerInstance : Container) : HostContext { 205 | log('getRootHostContext'); 206 | return emptyObject; 207 | }, 208 | 209 | getChildHostContext(parentHostContext : HostContext, type: string) : HostContext { 210 | log('getChildHostContext'); 211 | return emptyObject; 212 | }, 213 | 214 | // getPublicInstance should be the identity function in 99% of all scenarios. 215 | // It was added to support the `getNodeMock` functionality for the 216 | // TestRenderers. 217 | 218 | getPublicInstance(instance : Instance | TextInstance) { 219 | log('getPublicInstance'); 220 | if (instance == null) { 221 | return null; 222 | } 223 | console.log(instance) 224 | return instance != null && instance.props.toJSON(instance); 225 | }, 226 | 227 | // the prepareForCommit and resetAfterCommit methods are necessary for any 228 | // global side-effects you need to trigger in the host environment. In 229 | // ReactDOM this does things like disable the ReactDOM events to ensure no 230 | // callbacks are fired during DOM manipulations 231 | 232 | prepareForCommit() : void { 233 | log('prepareForCommit'); 234 | // noop 235 | }, 236 | 237 | resetAfterCommit() : void { 238 | log('resetAfterCommit'); 239 | // noop 240 | }, 241 | 242 | // the following four methods are regarding TextInstances. In our example 243 | // renderer we don’t have specific text nodes like the DOM does so we’ll just 244 | // noop all of them. 245 | 246 | shouldSetTextContent(props : Props): boolean { 247 | log('shouldSetTextContent'); 248 | return false 249 | }, 250 | 251 | resetTextContent(instance : Instance) : void { 252 | log('resetTextContent'); 253 | // noop 254 | }, 255 | 256 | createTextInstance( 257 | text : string, 258 | rootContainerInstance : Container, 259 | hostContext : HostContext, 260 | internalInstanceHandle : OpaqueHandle 261 | ) : TextInstance { 262 | log('createTextInstance'); 263 | return null; 264 | }, 265 | 266 | commitTextUpdate( 267 | textInstance : TextInstance, 268 | oldText : string, 269 | newText : string 270 | ) : void { 271 | log('commitTextUpdate'); 272 | // noop 273 | throw new Error('commitTextUpdate should not be called'); 274 | }, 275 | 276 | scheduleAnimationCallback() { 277 | log('scheduleAnimationCallback'); 278 | }, 279 | 280 | scheduleDeferredCallback() { 281 | log('scheduleDeferredCallback'); 282 | }, 283 | 284 | useSyncScheduling: true, 285 | }); 286 | 287 | /** 288 | * Our public renderer. When someone requires your renderer, this is all they 289 | * should have access to. `render` and `unmountComponentAtNode` methods should 290 | * be considered required, though that isn’t strictly true. 291 | */ 292 | const defaultContainer = {}; 293 | const Tiny = { 294 | render( 295 | element : React$Element, 296 | callback : ?Function, 297 | container : any, 298 | ) { 299 | const containerKey = typeof container === 'undefined' ? defaultContainer : container; 300 | let root = roots.get(containerKey); 301 | if (!root) { 302 | root = TinyRenderer.createContainer(containerKey); 303 | roots.set(container, root); 304 | } 305 | 306 | TinyRenderer.updateContainer((element : any), root, null, callback); 307 | return TinyRenderer.getPublicRootInstance(root); 308 | }, 309 | unmountComponentAtNode(container : any) { 310 | const containerKey = typeof container === 'undefined' ? defaultContainer : container; 311 | const root = roots.get(containerKey); 312 | if (root) { 313 | TinyRenderer.updateContainer(null, root, null, () => { 314 | roots.delete(container); 315 | }); 316 | } 317 | }, 318 | // other API methods you may support, such as `renderPortal()` 319 | }; 320 | 321 | const roots = new Map(); 322 | const emptyObject = {}; 323 | 324 | module.exports = Tiny; 325 | 326 | -------------------------------------------------------------------------------- /src/fiber/ReactTinyTypes.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export type Props = { 4 | children : null | Instance | Array, 5 | toJSON ?: Function 6 | }; 7 | export type Container = {}; 8 | export type Instance = { 9 | type: string | Function, 10 | props: Props, 11 | }; 12 | export type TextInstance = null; 13 | export type OpaqueHandle = Object; 14 | export type HostContext = Object; 15 | 16 | -------------------------------------------------------------------------------- /src/fiber/index.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | module.exports = require('./ReactTinyFiber'); 4 | 5 | -------------------------------------------------------------------------------- /src/fiber/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-react-renderer-fiber", 3 | "private": true, 4 | "description": "A tiny React fiber renderer to demonstrate how to write a renderer.", 5 | "main": "./index.js", 6 | "scripts": { 7 | "test": "pushd ../../; npm run test-fiber" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/iamdustan/tiny-react-renderer" 12 | }, 13 | "files": [ 14 | "*", 15 | "package.json", 16 | "README.md" 17 | ], 18 | "keywords": [ 19 | "react", 20 | "reactjs", 21 | "renderer" 22 | ], 23 | "author": "Dustan Kasten ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/iamdustan/tiny-render-renderer/issues" 27 | }, 28 | "homepage": "https://github.com/iamdustan/tiny-render-renderer", 29 | "dependencies": { 30 | "fbjs": "^0.8.4", 31 | "react": "16.0.0-alpha.3", 32 | "react-dom": "16.0.0-alpha.3", 33 | "react-fiber-types": "file:../fiber-types" 34 | }, 35 | "devDependencies": {} 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/fiber/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asap@~2.0.3: 6 | version "2.0.5" 7 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" 8 | 9 | core-js@^1.0.0: 10 | version "1.2.7" 11 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" 12 | 13 | encoding@^0.1.11: 14 | version "0.1.12" 15 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 16 | dependencies: 17 | iconv-lite "~0.4.13" 18 | 19 | fbjs@^0.8.4, fbjs@^0.8.9: 20 | version "0.8.9" 21 | resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.9.tgz#180247fbd347dcc9004517b904f865400a0c8f14" 22 | dependencies: 23 | core-js "^1.0.0" 24 | isomorphic-fetch "^2.1.1" 25 | loose-envify "^1.0.0" 26 | object-assign "^4.1.0" 27 | promise "^7.1.1" 28 | setimmediate "^1.0.5" 29 | ua-parser-js "^0.7.9" 30 | 31 | iconv-lite@~0.4.13: 32 | version "0.4.15" 33 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" 34 | 35 | is-stream@^1.0.1: 36 | version "1.1.0" 37 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 38 | 39 | isomorphic-fetch@^2.1.1: 40 | version "2.2.1" 41 | resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 42 | dependencies: 43 | node-fetch "^1.0.1" 44 | whatwg-fetch ">=0.10.0" 45 | 46 | js-tokens@^3.0.0: 47 | version "3.0.1" 48 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" 49 | 50 | loose-envify@^1.0.0, loose-envify@^1.1.0: 51 | version "1.3.1" 52 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" 53 | dependencies: 54 | js-tokens "^3.0.0" 55 | 56 | node-fetch@^1.0.1: 57 | version "1.6.3" 58 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04" 59 | dependencies: 60 | encoding "^0.1.11" 61 | is-stream "^1.0.1" 62 | 63 | object-assign@^4.1.0: 64 | version "4.1.1" 65 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 66 | 67 | promise@^7.1.1: 68 | version "7.1.1" 69 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" 70 | dependencies: 71 | asap "~2.0.3" 72 | 73 | react-dom@16.0.0-alpha.3: 74 | version "16.0.0-alpha.3" 75 | resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.0.0-alpha.3.tgz#9cf304ea4bcdafe026f29ec2e71373316ec597ab" 76 | dependencies: 77 | fbjs "^0.8.9" 78 | loose-envify "^1.1.0" 79 | object-assign "^4.1.0" 80 | 81 | "react-fiber-types@file:../fiber-types": 82 | version "16.0.0-alpha.3" 83 | 84 | react@16.0.0-alpha.3: 85 | version "16.0.0-alpha.3" 86 | resolved "https://registry.yarnpkg.com/react/-/react-16.0.0-alpha.3.tgz#addfd7ae9d801fd20c6244142354ae0cb7b1fe00" 87 | dependencies: 88 | fbjs "^0.8.9" 89 | loose-envify "^1.1.0" 90 | object-assign "^4.1.0" 91 | 92 | setimmediate@^1.0.5: 93 | version "1.0.5" 94 | resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 95 | 96 | ua-parser-js@^0.7.9: 97 | version "0.7.12" 98 | resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" 99 | 100 | whatwg-fetch@>=0.10.0: 101 | version "2.0.2" 102 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.2.tgz#fe294d1d89e36c5be8b3195057f2e4bc74fc980e" 103 | -------------------------------------------------------------------------------- /src/stack/README.md: -------------------------------------------------------------------------------- 1 | # Tiny React Renderer 2 | 3 | Creating a stack-based renderer is a fairly straight-forward affair once you 4 | know what you’re looking for. 5 | 6 | Many languages have this concept of a `main`—the entry point to your 7 | application. If you look at any React application code you’ve written you’ll see 8 | that you “start” your app with a call like the following: 9 | 10 | ```jsx 11 | // web 12 | ReactDOM.render(React.createElement(MyApp), document.getElementById('app')); 13 | 14 | // native 15 | AppRegistry.registerComponent('MyApp', () => MyApp); 16 | ``` 17 | 18 | This is where your application enters into the React domain and comes alive. Your 19 | root React element is instantiated and attached to the host environment. 20 | 21 | If you follow either the ReactDOM or React Native codebases from where these 22 | methods are defined you will quickly find yourself at the `React{Host}Mount.js` 23 | file. Our renderer also begins there. 24 | 25 | With that let’s get started! Our tour continues in [./mount.js](./mount.js). 26 | 27 | ## Work in Progress 28 | 29 | Please note this guide is a work in progress. Much of this knowledge is derived 30 | from my experience in creating [React Hardware](https://github.com/iamdustan/react-hardware). 31 | 32 | ## Thanks 33 | 34 | * [@thejameskyle](https://github.com/thejameskyle): for the inspiration of repo style 35 | * [@ryanflorence](https://github.com/ryanflorence) and [@mjackson](https://github.com/mjackson) for React Router and the problem that inspired this 36 | * [@gaearon](https://github.com/gaearon), [@matthewwithanm](https://github.com/matthewwithanm), 37 | [@vjeux](https://github.com/vjeux), [@zpao](https://github.com/zpao), 38 | [@Yomguithereal](https://github.com/Yomguithereal), [@axemclion](https://github.com/axemclion), 39 | and everyone else who has helped me poke around the React codebase. 40 | -------------------------------------------------------------------------------- /src/stack/component.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * Welcome to the Tiny React Renderer. 3 | * 4 | * You should read this guide in the following order: 5 | * 6 | * 1. mount.js 7 | * 2. injection.js 8 | * 3. component.js 9 | * 4. Any of the appendices you find interesting and the many React renderer 10 | * source files. 11 | */ 12 | 13 | /** 14 | * React Components are the heart and soul of a React renderer. This is the 15 | * internal side of a consumer's ReactComponents. At a high level, you will 16 | * define one or more internal components to map to the public interface of your 17 | * renderer. Below this we have defined two ReactComponents. 18 | * 19 | * * MinimumViableComponent 20 | * * TinyRendererComponent 21 | * 22 | * MinimumViableComponent is an overview of the minimum interface required to be 23 | * a valid ReactComponent. 24 | * 25 | * TinyRendererComponent is the component that is constructed in the Tiny React 26 | * Renderer. 27 | */ 28 | 'use strict'; 29 | 30 | const ReactMultiChild = require('react/lib/ReactMultiChild'); 31 | const serialize = require('./utilities/serialize'); 32 | 33 | /** 34 | * This MinimumViableComponent is the minimal interface to be recognized as a 35 | * valid React Component with support for rendering children. This is assuming 36 | * that the default case for a component is to support children. 37 | * 38 | * This has a very symmetrical relationship to the userland side of creating a 39 | * React Component. In addition, there are private methods used by other parts 40 | * of React Core, such as `getPublicInstance` or 41 | * React <= 15.0 `getNativeNode` 42 | * React > 15.0 `getHostNode` 43 | */ 44 | const MinimumViableComponent = function(element) { 45 | // This internal API—while relatively unchanged for a while—is likely pretty 46 | // volatile. Many of these names began with ReactDOM and ReactART which may 47 | // not be the *best* abstraction for them. 48 | 49 | // `this.node` in ReactDOM points to the DOM node of a component. 50 | // `getNativeNode`/`getHostNode` should return this. 51 | this.node = null; 52 | // `this._mountImage` is the representation you use to render a ReactElement 53 | // hierarchy. This could be an HTML string, a DOM node, or an identifier or 54 | // simple representation that maps to the final result such as native UI 55 | // controls. 56 | this._mountImage = null; 57 | // `this._renderedChildren` is something in the form of null|Child|Array 58 | // `ReactMultiChild.Mixin` is primarily responsible for managing this property 59 | this._renderedChildren = null; 60 | // `this._currentElement` is the currently rendered ReactElement. This is 61 | // important because it allows you to compare the node and props on lifecycle 62 | // methods to update appropriately. 63 | this._currentElement = element; 64 | }; 65 | 66 | MinimumViableComponent.prototype = Object.assign( 67 | { 68 | // A nice symmetry to [TODO: React Lifecycle Methods] exists here. 69 | // These are the required methods to implement. You may additionally provide 70 | // custom implementations of other lifecycle methods or any arbitrary 71 | // methods that are private to your implementation. 72 | getPublicInstance() {}, 73 | mountComponent() {}, 74 | receiveComponent() {}, 75 | unmountComponent() {}, 76 | // Implement both of these for now. React <= 15.0 uses getNativeNode, but 77 | // that is confusing. Host environment is more accurate and will be used 78 | // going forward 79 | getNativeNode() {}, 80 | getHostNode() {} 81 | }, 82 | ReactMultiChild.Mixin 83 | ); 84 | 85 | /** 86 | * The fun begins! Well, it would in a not-so-tiny renderer, but for here it’s 87 | * pretty straight-forward. The idea for this particular implementation came 88 | * from conversations with Ryan Florence and Michael Jackson from the React 89 | * Router/React.js Training teams. 90 | * 91 | * React Router supports two route configuration approaches: 92 | * 93 | * 1. ReactComponents `{...}` 94 | * 2. Object Literal `[{path: '/', component: C}, {path: '/list', component: L}]` 95 | * 96 | * As I understand it, both approaches result in the object literal, it’s just a 97 | * matter of the path to get there. The question then is what would it look 98 | * like to write a renderer that accomplishes the ReactComponent authoring style 99 | * in a more elegant way? 100 | * 101 | * If you recall from our mount.js render method, the return value or callback 102 | * is called with the result of `component.getPublicInstance()`. We want this to 103 | * be the JSON representation of our route configuration. 104 | * 105 | * Below we will provide a default serialize.toJSON implementation and the bare 106 | * minimum required to mount and update these ReactElements. 107 | */ 108 | const TinyRendererComponent = function(element) { 109 | this.node = null; 110 | this._mountImage = null; 111 | this._renderedChildren = null; 112 | this._currentElement = element; 113 | }; 114 | 115 | const TinyRendererComponentMixin = { 116 | getPublicInstance() { 117 | // serialize.toJSON is the default serialization provided. 118 | // 119 | // It simply returns the node.props of a component with serialized children. 120 | // It also looks at whether a component has a custom `toJSON` method and will 121 | // use that instead, allowing consumers to provide their own serialization and 122 | // impacting the resulting public instance. 123 | 124 | return serialize.toJSON(this.node) 125 | }, 126 | 127 | mountComponent( 128 | transaction, 129 | nativeParent, 130 | nativeContainerInfo, 131 | context 132 | ) { 133 | // In a not-so-tiny renderer you would also want to validate the properties 134 | // (in dev mode) and apply them to the host environment. 135 | // I have often seen renderers have a `render` method defined on their 136 | // internal component implementation that is responsible for calling the 137 | // appropriate methods to update the UI. For example that could be DOM 138 | // methods, ReactNativeUIManager bridging calls, or node-blessed methods. 139 | this.node = this._currentElement; 140 | this.mountChildren(this.node.children, transaction, context); 141 | 142 | return this.node; 143 | }, 144 | 145 | receiveComponent(nextElement, transaction, context) { 146 | // Typically you would diff the props and apply those to the host 147 | // environment, though all we need to do is swap out our _currentElement. 148 | const prevElement = this._currentElement; 149 | this._currentElement = nextElement; 150 | 151 | // this.updateChildren comes from ReactMultiChild.Mixin 152 | this.updateChildren(nextElement.props.children, transaction, context); 153 | }, 154 | // there is no native node 155 | getHostNode() {}, 156 | // how do you unmount JSON? 157 | unmountComponent() {}, 158 | }; 159 | 160 | Object.assign( 161 | TinyRendererComponent.prototype, 162 | TinyRendererComponentMixin, 163 | ReactMultiChild.Mixin 164 | ); 165 | 166 | /** 167 | * CONGRATULATIONS! You now understand the basics of how a React Renderer works. 168 | * While there are still a lot of React internals we didn’t look at, you saw the 169 | * foundational pieces. Most every renderer listed on 170 | * http://iamdustan.com/react-renderers has the same entry path and hooks as 171 | * what you just learned. 172 | * 173 | * A lot of ReactDOM and ReactNative’s source is integrating the concepts we 174 | * just learned in this binding layer to a host environment. From this 175 | * foundation you have a solid understanding of the glue. ReactCore deals with 176 | * details of reconciliation and transactions, while renderers are plumbing. 177 | * 178 | * Fin. 179 | */ 180 | module.exports = TinyRendererComponent; 181 | 182 | -------------------------------------------------------------------------------- /src/stack/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const render = require('./mount'); 4 | 5 | module.exports = {render}; 6 | 7 | -------------------------------------------------------------------------------- /src/stack/injection.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * Welcome to the Tiny React Renderer. 3 | * 4 | * You should read this guide in the following order: 5 | * 6 | * 1. mount.js 7 | * 2. injection.js 8 | * 3. component.js 9 | * 4. Any of the appendices you find interesting and the many React renderer 10 | * source files. 11 | */ 12 | 13 | /** 14 | * A React renderer is responsible for injecting the implementations for one or 15 | * more internal concepts. 16 | * 17 | * The following list of 28 files is the result of searching for `inject` in the 18 | * React source code as of React 15.0.0. Since these are private APIs this is 19 | * not intended to be a dictionary of all possible injection points, but to 20 | * provide a reference of areas that may have the possibility to be customized. 21 | * 22 | * * BeforeInputEventPlugin.js 23 | * * DefaultEventPluginOrder.js 24 | * * dangerousStyleValue.js 25 | * * DOMProperty.js 26 | * * EventPluginHub.js 27 | * * EventPluginRegistry.js 28 | * * EventPluginUtils.js 29 | * * HTMLDOMPropertyConfig.js 30 | * * ReactBrowserEventEmitter.js 31 | * * ReactComponentBrowserEnvironment.js 32 | * * ReactComponent.js 33 | * * ReactComponentEnvironment.js 34 | * * ReactClass.js 35 | * * ReactDOM.js 36 | * * ReactDefaultInjection.js 37 | * * ReactDefaultPerf.js 38 | * * ReactDOMServer.js 39 | * * ReactDOMComponent.js 40 | * * ReactEmptyComponent.js 41 | * * ReactInjection.js 42 | * * ReactHostComponent.js 43 | * * ReactPerf.js 44 | * * ReactMount.js 45 | * * ReactServerRendering.js 46 | * * ReactTestUtils.js 47 | * * ReactUpdates.js 48 | * * ResponderEventPlugin.js 49 | * * Transaction.js 50 | */ 51 | 'use strict'; 52 | 53 | /** 54 | * ReactInjection is likely the most stable location to look for injection 55 | * points. As of 15.0.0 this contains the following 10 modules: 56 | * 57 | * * Component 58 | * * Class 59 | * * DOMProperty 60 | * * EmptyComponent 61 | * * EventPluginHub 62 | * * EventPluginUtils 63 | * * EventEmitter 64 | * * HostComponent 65 | * * Perf 66 | * * Updates 67 | * 68 | * Some of these can be injected verbatim for most scenarios, and we provide our 69 | * component implementation(s) to be called at the appropriate times. 70 | */ 71 | const ReactInjection = require('react/lib/ReactInjection'); 72 | const ReactDefaultBatchingStrategy = require('react/lib/ReactDefaultBatchingStrategy'); 73 | const TinyRendererReconcileTransaction = require('./reconcileTransaction'); 74 | const TinyRendererComponent = require('./component'); 75 | 76 | function inject() { 77 | // For the Tiny React Renderer only the generic component class will be 78 | // injected. The HostComponent injection has the following three methods: 79 | // 80 | // * injectGenericComponentClass 81 | // * injectTextComponentClass 82 | // 83 | // `GenericComponentClass` is analogous to $JSXInstrinsics in flow 84 | // terminology. This is a lowercase JSXElement such as
85 | // 86 | // `TextComponentClass` is what class should be used when text is being 87 | // rendered. This is what would be called for the single child in 88 | //
Hello, world.
89 | (ReactInjection.NativeComponent || ReactInjection.HostComponent).injectGenericComponentClass( 90 | TinyRendererComponent 91 | ); 92 | 93 | ReactInjection.Updates.injectReconcileTransaction( 94 | TinyRendererReconcileTransaction 95 | ); 96 | 97 | ReactInjection.Updates.injectBatchingStrategy( 98 | ReactDefaultBatchingStrategy 99 | ); 100 | } 101 | 102 | module.exports = {inject}; 103 | 104 | -------------------------------------------------------------------------------- /src/stack/mount.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * Welcome to the Tiny React Renderer. 3 | * 4 | * You should read this guide in the following order: 5 | * 6 | * 1. mount.js 7 | * 2. injection.js 8 | * 3. component.js 9 | * 4. Any of the appendices you find interesting and the many React renderer 10 | * source files. 11 | */ 12 | 13 | /** 14 | * This file has been authored in a linear manner and is not reflective of many 15 | * of the steps you would take while writing a production renderer. 16 | * 17 | * We’re going to look at the four required modules that a React renderer has. 18 | * Beyond this you will have the work that binds a renderer implementation to a 19 | * host environment. For this Tiny React Renderer we’re implementing a glorified 20 | * `toJSON` renderer and will not have that. See 21 | * [Appendix 3: Host Environment Examples](https://github.com/iamdustan/tiny-react-renderer/tree/master/appendix/3-Host-Environment-Examples.md) 22 | * to learn more. 23 | * 24 | * The four core modules in a React renderer are: 25 | * 26 | * * DefaultInjection 27 | * * Mount 28 | * * Component 29 | * * ReconcileTransaction 30 | * 31 | * `DefaultInjection` must inject its implementations before any other work can 32 | * be done. It is part of a renderer's initialization process. 33 | * 34 | * `Mount` can be thought of a renderer's `main` method. All userland code enters 35 | * the React world through this door. 36 | * 37 | * `ReconcileTransaction` is a class which maintains the logic for how 38 | * reconciliations take place. Many renderers can use the simple transaction as 39 | * provided in here, although a lot of complexity can be contained within here. 40 | * See [Appendix 2: Reconcile Transaction](https://github.com/iamdustan/tiny-react-renderer/tree/master/appendix/2-Reconcile-Transaction.md) 41 | * for a deeper look into the default DOM ReactReconcileTransaction details. 42 | * 43 | * `Component` is the internal class to a consumer's component. This is where 44 | * property diffing is computed and applied and where the realization of a 45 | * renderer takes place. 46 | */ 47 | 48 | /** 49 | * As with most modern JavaScript projects in 2016, we begin by importing our 50 | * dependencies. The first dependency here will typically be seen inline, but 51 | * has been moved out for clarity while reading the primary `mount` method 52 | * below. 53 | * 54 | * After this we require a few React internal modules that allow us to 55 | * instantiate ReactComponents and integrate with the reconciliation algorithm. 56 | * 57 | * The final require introduces us to a common pattern in React core which is 58 | * the ability to `inject` an implementation into different parts of the core 59 | * application. For example, ReactDOM injects a generic component class that 60 | * handles rendering
, , and , and any other DOM node. 61 | * 62 | * The `./injection.js` file documents this in greater detail. 63 | */ 64 | 'use strict'; 65 | 66 | const invariants = require('./utilities/invariants'); 67 | const instantiateReactComponent = require('react/lib/instantiateReactComponent'); 68 | const ReactInstanceHandles = require('react/lib/ReactInstanceHandles'); 69 | const ReactUpdates = require('react/lib/ReactUpdates'); 70 | const DefaultInjection = require('./injection'); 71 | 72 | /** 73 | * Step 0. Inject the unique aspects of your renderer into React core 74 | * immediately. These will be things like your event system, generic component 75 | * handler, reconciliation strategy, or batching strategy. 76 | */ 77 | DefaultInjection.inject(); 78 | 79 | /** 80 | * Step 1. Userlands entry into the React world. Regardless of the renderer you 81 | * use—whether ReactDOM, ReactNative, ReactHardware, ReactBlessed, or one of the 82 | * many others—all `ReactDOM.render|RN.registerComponent|ReactBlessed.render` 83 | * type of methods will enter the React world through this method. 84 | * 85 | * As an example: 86 | * ``` 87 | * ReactDOM.render( 88 | *
Hello, World
, 89 | * document.getElementById('app'), 90 | * (inst) => console.log(inst) 91 | * ); 92 | * ``` 93 | * 94 | * will reach this method in the `ReactMount` module. Your signature should have 95 | * the `ReactElement` as the first argument, an optional callback as the last 96 | * argument, and any environment-specific arguments in between. 97 | * 98 | * Optionally, you may return the public instance of the rendered component as 99 | * the return value. 100 | **/ 101 | const render = ( 102 | nextElement, // ReactElement description. 103 | callback // optional callback for when mount is complete 104 | ) => { 105 | 106 | // The first thing you'll want to do in here is confirm the caller passed in a 107 | // valid ReactElement. The implementation of this is the same across renderers 108 | // with the exception of the error message through when the invariant fails. 109 | invariants.isValidElement(nextElement); 110 | 111 | // For this tiny renderer, we do not have a target element to render into, 112 | // though many renderers have this concern. In this scenario you should 113 | // consider applying a `warning` or `invariant` to that argument to ensure the 114 | // consumer has an educational experience in development mode. A key objective 115 | // of writing a renderer is to make interacting with the host system simpler. 116 | // A given renderer should seek to help its users to avoid simple mistakes. 117 | // such as passing in a non-existent DOM node. 118 | // 119 | // @example: 120 | // warning.isValidTargetElement(targetElement); 121 | 122 | // Appendix 1: Rerendering A Top Level Element 123 | // https://github.com/iamdustan/tiny-react-renderer/tree/master/appendix/1-Rendering-A-Top-Level-Element.md 124 | // 125 | // If there is a target element or the opportunity to reuse a previous render 126 | // call, you would look up the previous element and reconcile from there. 127 | 128 | // Woohoo! The consumer has now made it to the point where we’re interacting 129 | // with React internals! Since any application can have multiple roots, we 130 | // want to get an identifier from the `ReactInstanceHandles` component. 131 | // 132 | // Next we instantiate a new ReactComponent from the ReactElement passed in. 133 | // See [React glossary](https://facebook.github.io/react/docs/glossary.html) 134 | // for more context on the relationship between ReactComponent and ReactElement. 135 | const rootId = ReactInstanceHandles.createReactRootID(0); 136 | const component = instantiateReactComponent(nextElement); 137 | 138 | // The initial render is currently synchronous, but any updates that happen 139 | // during rendering, in componentWillMount or componentDidMount, will be 140 | // batched according to the current batching strategy. 141 | // 142 | // Note: there is ongoing research for creating an incremental reconciler 143 | // which may impact this aspect of renderer creation. 144 | // 145 | // Assuming you’ve read the [React Reconciliation Algorithm](https://facebook.github.io/react/docs/reconciliation.html) article on 146 | // the React docs, this may be familiar. The “public” API for accomplishing 147 | // this is done with a batching strategy and transaction. React provides 148 | // default implementations for both of these and does not require any effort 149 | // from us other than calling them. 150 | ReactUpdates.batchedUpdates(() => { 151 | // Two points to React for using object pooling internally and being good 152 | // stewards of garbage collection and memory pressure. 153 | const transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); 154 | transaction.perform(() => { 155 | // The `component` here is an instance of your 156 | // `ReactCustomRendererComponent` class. To be 100% honest, I’m not 157 | // certain if the method signature is enforced by React core or if it is 158 | // renderer specific. This is following the ReactDOM renderer. The 159 | // important piece is that we pass our transaction and rootId through, in 160 | // addition to any other contextual information needed. 161 | component.mountComponent( 162 | transaction, 163 | rootId, 164 | // TODO: what is _idCounter used for and when should it be nonzero? 165 | {_idCounter: 0}, 166 | {} 167 | ); 168 | if (callback) { 169 | callback(component.getPublicInstance()); 170 | } 171 | }); 172 | ReactUpdates.ReactReconcileTransaction.release(transaction); 173 | 174 | }); 175 | 176 | return component.getPublicInstance(); 177 | }; 178 | 179 | // Congratulations! You’ve done it! You have a React renderer! Though so far 180 | // we haven’t done anything interesting. For that we need to implement our 181 | // ReactComponent class. For that you’ll head over to `./component.js`. See you 182 | // there! 183 | module.exports = render; 184 | 185 | -------------------------------------------------------------------------------- /src/stack/package.js: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-react-renderer-stack", 3 | "private": true, 4 | "description": "A tiny React stack renderer to demonstrate how to write a renderer.", 5 | "main": "./index.js", 6 | "scripts": { 7 | "test": "pushd ../../; node ./test --fiber" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/iamdustan/tiny-react-renderer" 12 | }, 13 | "files": [ 14 | "*", 15 | "package.json", 16 | "README.md" 17 | ], 18 | "keywords": [ 19 | "react", 20 | "reactjs", 21 | "renderer" 22 | ], 23 | "author": "Dustan Kasten ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/iamdustan/tiny-render-renderer/issues" 27 | }, 28 | "homepage": "https://github.com/iamdustan/tiny-render-renderer", 29 | "dependencies": { 30 | "fbjs": "^0.8.4", 31 | "react": "15.3.x" 32 | }, 33 | "devDependencies": {} 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/stack/reconcileTransaction.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * Welcome to the Tiny React Renderer. 3 | * 4 | * You should read this guide in the following order: 5 | * 6 | * 1. mount.js 7 | * 2. injection.js 8 | * 3. component.js 9 | * 4. Any of the appendices you find interesting and the many React renderer 10 | * source files. 11 | */ 12 | 13 | /*** 14 | * The ReconcileTransaction class provided here is the simplest scenario 15 | * possible. The source code here—including all callbacks beyond this point—is 16 | * the same as the ReactReconcileTransaction.js module in the React 15 source 17 | * code, but with the DOM event and selection handlers removed. 18 | * 19 | * Really interesting edge cases are managed from here, but nothing of 20 | * interest happens in a Tiny Renderer. 21 | */ 22 | 23 | 'use strict'; 24 | 25 | const CallbackQueue = require('react/lib/CallbackQueue'); 26 | const PooledClass = require('react/lib/PooledClass'); 27 | const Transaction = require('react/lib/Transaction'); 28 | 29 | /** 30 | * Provides a `CallbackQueue` queue for collecting `onDOMReady` or analogous 31 | * callbacks during the performing of the transaction. 32 | */ 33 | const ON_RENDERER_READY_QUEUEING = { 34 | /** 35 | * Initializes the internal firmata `connected` queue. 36 | */ 37 | initialize: function() { 38 | this.reactMountReady.reset(); 39 | }, 40 | 41 | /** 42 | * After Hardware is connected, invoke all registered `ready` callbacks. 43 | */ 44 | close: function() { 45 | this.reactMountReady.notifyAll(); 46 | }, 47 | }; 48 | 49 | /** 50 | * Executed within the scope of the `Transaction` instance. Consider these as 51 | * being member methods, but with an implied ordering while being isolated from 52 | * each other. 53 | */ 54 | const TRANSACTION_WRAPPERS = [ON_RENDERER_READY_QUEUEING]; 55 | 56 | function TinyRendererReconcileTransaction() { 57 | this.reinitializeTransaction(); 58 | this.reactMountReady = CallbackQueue.getPooled(null); 59 | } 60 | 61 | const Mixin = { 62 | /** 63 | * @see Transaction 64 | * @abstract 65 | * @final 66 | * @return {array} List of operation wrap procedures. 67 | */ 68 | getTransactionWrappers: function() { 69 | return TRANSACTION_WRAPPERS; 70 | }, 71 | 72 | /** 73 | * @return {object} The queue to collect `ready` callbacks with. 74 | */ 75 | getReactMountReady: function() { 76 | return this.reactMountReady; 77 | }, 78 | 79 | /** 80 | * `PooledClass` looks for this, and will invoke this before allowing this 81 | * instance to be reused. 82 | */ 83 | destructor: function() { 84 | CallbackQueue.release(this.reactMountReady); 85 | this.reactMountReady = null; 86 | }, 87 | }; 88 | 89 | Object.assign( 90 | TinyRendererReconcileTransaction.prototype, 91 | Transaction.Mixin, 92 | TinyRendererReconcileTransaction, 93 | Mixin 94 | ); 95 | 96 | PooledClass.addPoolingTo(TinyRendererReconcileTransaction); 97 | 98 | module.exports = TinyRendererReconcileTransaction; 99 | 100 | -------------------------------------------------------------------------------- /src/stack/utilities/invariants.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * Welcome to the Tiny React Renderer. 3 | * 4 | * You should read this guide in the following order: 5 | * 6 | * 1. mount.js 7 | * 2. injection.js 8 | * 3. component.js 9 | * 4. Any of the appendices you find interesting and the many React renderer 10 | * source files. 11 | */ 12 | 'use strict'; 13 | 14 | const invariant = require('fbjs/lib/invariant'); 15 | const ReactElement = require('react/lib/ReactElement'); 16 | 17 | const isValidElement = (nextElement) => invariant( 18 | ReactElement.isValidElement(nextElement), 19 | 'ReactHardware.render(): Invalid component element.%s', 20 | ( 21 | typeof nextElement === 'function' ? 22 | ' Instead of passing a component class, make sure to instantiate ' + 23 | 'it by passing it to React.createElement.' : 24 | // Check if it quacks like an element 25 | nextElement != null && nextElement.props !== undefined ? 26 | ' This may be caused by unintentionally loading two independent ' + 27 | 'copies of React.' : 28 | '' 29 | ) 30 | ); 31 | 32 | module.exports = { 33 | isValidElement: isValidElement, 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /src/stack/utilities/serialize.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * Welcome to the Tiny React Renderer. 3 | * 4 | * You should read this guide in the following order: 5 | * 6 | * 1. mount.js 7 | * 2. injection.js 8 | * 3. component.js 9 | * 4. Any of the appendices you find interesting and the many React renderer 10 | * source files. 11 | */ 12 | 'use strict'; 13 | 14 | const toJSON = (node) => { 15 | const props = node.props; 16 | if (typeof props.toJSON === 'function') { 17 | return props.toJSON(props); 18 | } 19 | 20 | let children = null; 21 | if (props.children) { 22 | if (Array.isArray(props.children)) { 23 | children = props.children.map(toJSON); 24 | } else if (props.children) { 25 | children = toJSON(props.children); 26 | } 27 | return Object.assign({}, props, {children}); 28 | } else { 29 | const clone = Object.assign({}, props); 30 | delete clone.children; 31 | return clone; 32 | } 33 | }; 34 | 35 | module.exports = {toJSON}; 36 | 37 | -------------------------------------------------------------------------------- /src/stack/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('babel-register')({}); 4 | 5 | const assert = require('assert'); 6 | const React = require('react'); 7 | const args = process.argv.slice(2); 8 | 9 | const TEST_FILE = args[0] === '-f' || args[0] === '--fiber' 10 | ? 'fiber' 11 | : 'stack'; 12 | 13 | console.log('Running %s tests', TEST_FILE); 14 | const TinyRenderer = require('./src/' + TEST_FILE); 15 | const render = TinyRenderer.render; 16 | const toJSON = (props) => { 17 | if (props.children) { 18 | let childRoutes; 19 | if (Array.isArray(props.children)) { 20 | childRoutes = props.children.map(child => ( 21 | typeof child.props.toJSON === 'function' 22 | ? child.props.toJSON(child.props) 23 | : toJSON(child.props) 24 | )); 25 | } else { 26 | childRoutes = {}; 27 | } 28 | return {path: props.path, childRoutes}; 29 | } 30 | 31 | return {path: props.path}; 32 | }; 33 | 34 | // mock stateless components 35 | const Base = () => React.createElement('div'); 36 | const Page1 = () => React.createElement('div'); 37 | const Page2 = () => React.createElement('div'); 38 | 39 | // helper for {children} 40 | const Route = (path, component, children) => 41 | React.createElement('Route', {path: path, component: component, key: path}, children); 42 | 43 | const Rte = (path, component, children) => 44 | React.createElement('Route', {path: path, component: component, key: path, toJSON: toJSON}, children); 45 | 46 | const ok = []; 47 | const fail = []; 48 | const skipped = []; 49 | const colors = { 50 | green: '\x1b[32m', 51 | red: '\x1b[31m', 52 | reset: '\x1b[37m', 53 | }; 54 | 55 | const it = (desc, fn) => { 56 | try { 57 | fn.call(null); 58 | console.log('%s✓ %s%s', colors.green, colors.reset, desc); 59 | ok.push({desc}); 60 | } catch (err) { 61 | fail.push({desc, err}); 62 | console.log('%s𝘅 %s%s',colors.red, colors.reset, desc); 63 | console.error('%s. Expected\n %j\n to equal\n %j\n', err.name, err.actual, err.expected) 64 | } 65 | }; 66 | 67 | it.skip = (desc, fn) => skipped.push({desc}); 68 | 69 | it('should render with the default toJSON behavior', () => { 70 | const element = render( 71 | Route('/', Base, [ 72 | Route('/page/1', Page1), 73 | Route('/page/2', Page2) 74 | ]) 75 | ); 76 | 77 | assert.deepEqual( 78 | element, 79 | { 80 | path: '/', 81 | component: Base, 82 | children: [ 83 | { 84 | path: '/page/1', 85 | component: Page1 86 | }, 87 | { 88 | path: '/page/2', 89 | component: Page2 90 | } 91 | ] 92 | } 93 | ); 94 | }); 95 | 96 | it('should render with a custom toJSON method', () => { 97 | const element = render( 98 | Rte('/', Base, [ 99 | Rte('/page/1', Page1, [Rte('lol')]), 100 | Rte('/page/2', Page2) 101 | ]) 102 | ); 103 | 104 | assert.deepEqual( 105 | element, 106 | { 107 | path: '/', 108 | childRoutes: [ 109 | {path: '/page/1', childRoutes: [{path: 'lol'}]}, 110 | {path: '/page/2'} 111 | ] 112 | } 113 | ); 114 | }); 115 | 116 | if (fail.length > 0) { 117 | console.log('%s tests passed', ok.length); 118 | if (skipped.length) console.log('%s tests skipped', skipped.length); 119 | console.log('%s tests failed', fail.length); 120 | process.exit(1); 121 | } else { 122 | console.log('%s tests passed', ok.length); 123 | if (skipped.length) console.log('%s tests skipped', skipped.length); 124 | } 125 | 126 | console.log(''); 127 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ansi-regex@^2.0.0: 6 | version "2.1.1" 7 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 8 | 9 | ansi-styles@^2.2.1: 10 | version "2.2.1" 11 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 12 | 13 | asap@~2.0.3: 14 | version "2.0.5" 15 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" 16 | 17 | babel-code-frame@^6.22.0: 18 | version "6.22.0" 19 | resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" 20 | dependencies: 21 | chalk "^1.1.0" 22 | esutils "^2.0.2" 23 | js-tokens "^3.0.0" 24 | 25 | babel-core@^6.23.0: 26 | version "6.23.1" 27 | resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.23.1.tgz#c143cb621bb2f621710c220c5d579d15b8a442df" 28 | dependencies: 29 | babel-code-frame "^6.22.0" 30 | babel-generator "^6.23.0" 31 | babel-helpers "^6.23.0" 32 | babel-messages "^6.23.0" 33 | babel-register "^6.23.0" 34 | babel-runtime "^6.22.0" 35 | babel-template "^6.23.0" 36 | babel-traverse "^6.23.1" 37 | babel-types "^6.23.0" 38 | babylon "^6.11.0" 39 | convert-source-map "^1.1.0" 40 | debug "^2.1.1" 41 | json5 "^0.5.0" 42 | lodash "^4.2.0" 43 | minimatch "^3.0.2" 44 | path-is-absolute "^1.0.0" 45 | private "^0.1.6" 46 | slash "^1.0.0" 47 | source-map "^0.5.0" 48 | 49 | babel-generator@^6.23.0: 50 | version "6.23.0" 51 | resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.23.0.tgz#6b8edab956ef3116f79d8c84c5a3c05f32a74bc5" 52 | dependencies: 53 | babel-messages "^6.23.0" 54 | babel-runtime "^6.22.0" 55 | babel-types "^6.23.0" 56 | detect-indent "^4.0.0" 57 | jsesc "^1.3.0" 58 | lodash "^4.2.0" 59 | source-map "^0.5.0" 60 | trim-right "^1.0.1" 61 | 62 | babel-helpers@^6.23.0: 63 | version "6.23.0" 64 | resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.23.0.tgz#4f8f2e092d0b6a8808a4bde79c27f1e2ecf0d992" 65 | dependencies: 66 | babel-runtime "^6.22.0" 67 | babel-template "^6.23.0" 68 | 69 | babel-messages@^6.23.0: 70 | version "6.23.0" 71 | resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" 72 | dependencies: 73 | babel-runtime "^6.22.0" 74 | 75 | babel-plugin-syntax-flow@^6.18.0: 76 | version "6.18.0" 77 | resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" 78 | 79 | babel-plugin-transform-flow-strip-types@^6.22.0: 80 | version "6.22.0" 81 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" 82 | dependencies: 83 | babel-plugin-syntax-flow "^6.18.0" 84 | babel-runtime "^6.22.0" 85 | 86 | babel-register@^6.23.0: 87 | version "6.23.0" 88 | resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.23.0.tgz#c9aa3d4cca94b51da34826c4a0f9e08145d74ff3" 89 | dependencies: 90 | babel-core "^6.23.0" 91 | babel-runtime "^6.22.0" 92 | core-js "^2.4.0" 93 | home-or-tmp "^2.0.0" 94 | lodash "^4.2.0" 95 | mkdirp "^0.5.1" 96 | source-map-support "^0.4.2" 97 | 98 | babel-runtime@^6.22.0: 99 | version "6.23.0" 100 | resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" 101 | dependencies: 102 | core-js "^2.4.0" 103 | regenerator-runtime "^0.10.0" 104 | 105 | babel-template@^6.23.0: 106 | version "6.23.0" 107 | resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.23.0.tgz#04d4f270adbb3aa704a8143ae26faa529238e638" 108 | dependencies: 109 | babel-runtime "^6.22.0" 110 | babel-traverse "^6.23.0" 111 | babel-types "^6.23.0" 112 | babylon "^6.11.0" 113 | lodash "^4.2.0" 114 | 115 | babel-traverse@^6.23.0, babel-traverse@^6.23.1: 116 | version "6.23.1" 117 | resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.23.1.tgz#d3cb59010ecd06a97d81310065f966b699e14f48" 118 | dependencies: 119 | babel-code-frame "^6.22.0" 120 | babel-messages "^6.23.0" 121 | babel-runtime "^6.22.0" 122 | babel-types "^6.23.0" 123 | babylon "^6.15.0" 124 | debug "^2.2.0" 125 | globals "^9.0.0" 126 | invariant "^2.2.0" 127 | lodash "^4.2.0" 128 | 129 | babel-types@^6.23.0: 130 | version "6.23.0" 131 | resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.23.0.tgz#bb17179d7538bad38cd0c9e115d340f77e7e9acf" 132 | dependencies: 133 | babel-runtime "^6.22.0" 134 | esutils "^2.0.2" 135 | lodash "^4.2.0" 136 | to-fast-properties "^1.0.1" 137 | 138 | babylon@^6.11.0, babylon@^6.15.0: 139 | version "6.16.1" 140 | resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3" 141 | 142 | balanced-match@^0.4.1: 143 | version "0.4.2" 144 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 145 | 146 | brace-expansion@^1.0.0: 147 | version "1.1.6" 148 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" 149 | dependencies: 150 | balanced-match "^0.4.1" 151 | concat-map "0.0.1" 152 | 153 | chalk@^1.1.0: 154 | version "1.1.3" 155 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 156 | dependencies: 157 | ansi-styles "^2.2.1" 158 | escape-string-regexp "^1.0.2" 159 | has-ansi "^2.0.0" 160 | strip-ansi "^3.0.0" 161 | supports-color "^2.0.0" 162 | 163 | concat-map@0.0.1: 164 | version "0.0.1" 165 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 166 | 167 | convert-source-map@^1.1.0: 168 | version "1.4.0" 169 | resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.4.0.tgz#e3dad195bf61bfe13a7a3c73e9876ec14a0268f3" 170 | 171 | core-js@^1.0.0: 172 | version "1.2.7" 173 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" 174 | 175 | core-js@^2.4.0: 176 | version "2.4.1" 177 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" 178 | 179 | debug@^2.1.1, debug@^2.2.0: 180 | version "2.6.1" 181 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351" 182 | dependencies: 183 | ms "0.7.2" 184 | 185 | detect-indent@^4.0.0: 186 | version "4.0.0" 187 | resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" 188 | dependencies: 189 | repeating "^2.0.0" 190 | 191 | encoding@^0.1.11: 192 | version "0.1.12" 193 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 194 | dependencies: 195 | iconv-lite "~0.4.13" 196 | 197 | escape-string-regexp@^1.0.2: 198 | version "1.0.5" 199 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 200 | 201 | esutils@^2.0.2: 202 | version "2.0.2" 203 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 204 | 205 | fbjs@^0.8.4: 206 | version "0.8.5" 207 | resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.5.tgz#f69ba8a876096cb1b9bffe4d7c1e71c19d39d008" 208 | dependencies: 209 | core-js "^1.0.0" 210 | immutable "^3.7.6" 211 | isomorphic-fetch "^2.1.1" 212 | loose-envify "^1.0.0" 213 | object-assign "^4.1.0" 214 | promise "^7.1.1" 215 | ua-parser-js "^0.7.9" 216 | 217 | globals@^9.0.0: 218 | version "9.16.0" 219 | resolved "https://registry.yarnpkg.com/globals/-/globals-9.16.0.tgz#63e903658171ec2d9f51b1d31de5e2b8dc01fb80" 220 | 221 | has-ansi@^2.0.0: 222 | version "2.0.0" 223 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 224 | dependencies: 225 | ansi-regex "^2.0.0" 226 | 227 | home-or-tmp@^2.0.0: 228 | version "2.0.0" 229 | resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" 230 | dependencies: 231 | os-homedir "^1.0.0" 232 | os-tmpdir "^1.0.1" 233 | 234 | iconv-lite@~0.4.13: 235 | version "0.4.13" 236 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" 237 | 238 | immutable@^3.7.6: 239 | version "3.8.1" 240 | resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" 241 | 242 | invariant@^2.2.0: 243 | version "2.2.2" 244 | resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" 245 | dependencies: 246 | loose-envify "^1.0.0" 247 | 248 | is-finite@^1.0.0: 249 | version "1.0.2" 250 | resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" 251 | dependencies: 252 | number-is-nan "^1.0.0" 253 | 254 | is-stream@^1.0.1: 255 | version "1.1.0" 256 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 257 | 258 | isomorphic-fetch@^2.1.1: 259 | version "2.2.1" 260 | resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 261 | dependencies: 262 | node-fetch "^1.0.1" 263 | whatwg-fetch ">=0.10.0" 264 | 265 | js-tokens@^1.0.1: 266 | version "1.0.3" 267 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-1.0.3.tgz#14e56eb68c8f1a92c43d59f5014ec29dc20f2ae1" 268 | 269 | js-tokens@^3.0.0: 270 | version "3.0.1" 271 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" 272 | 273 | jsesc@^1.3.0: 274 | version "1.3.0" 275 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" 276 | 277 | json5@^0.5.0: 278 | version "0.5.1" 279 | resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" 280 | 281 | lodash@^4.2.0: 282 | version "4.17.4" 283 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 284 | 285 | loose-envify@^1.0.0, loose-envify@^1.1.0: 286 | version "1.2.0" 287 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.2.0.tgz#69a65aad3de542cf4ee0f4fe74e8e33c709ccb0f" 288 | dependencies: 289 | js-tokens "^1.0.1" 290 | 291 | minimatch@^3.0.2: 292 | version "3.0.3" 293 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 294 | dependencies: 295 | brace-expansion "^1.0.0" 296 | 297 | minimist@0.0.8: 298 | version "0.0.8" 299 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 300 | 301 | mkdirp@^0.5.1: 302 | version "0.5.1" 303 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 304 | dependencies: 305 | minimist "0.0.8" 306 | 307 | ms@0.7.2: 308 | version "0.7.2" 309 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" 310 | 311 | node-fetch@^1.0.1: 312 | version "1.6.3" 313 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04" 314 | dependencies: 315 | encoding "^0.1.11" 316 | is-stream "^1.0.1" 317 | 318 | number-is-nan@^1.0.0: 319 | version "1.0.1" 320 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 321 | 322 | object-assign@^4.1.0: 323 | version "4.1.0" 324 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" 325 | 326 | os-homedir@^1.0.0: 327 | version "1.0.2" 328 | resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" 329 | 330 | os-tmpdir@^1.0.1: 331 | version "1.0.2" 332 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 333 | 334 | path-is-absolute@^1.0.0: 335 | version "1.0.1" 336 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 337 | 338 | private@^0.1.6: 339 | version "0.1.7" 340 | resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" 341 | 342 | promise@^7.1.1: 343 | version "7.1.1" 344 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" 345 | dependencies: 346 | asap "~2.0.3" 347 | 348 | react@15.3.x: 349 | version "15.3.2" 350 | resolved "https://registry.yarnpkg.com/react/-/react-15.3.2.tgz#a7bccd2fee8af126b0317e222c28d1d54528d09e" 351 | dependencies: 352 | fbjs "^0.8.4" 353 | loose-envify "^1.1.0" 354 | object-assign "^4.1.0" 355 | 356 | regenerator-runtime@^0.10.0: 357 | version "0.10.3" 358 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.3.tgz#8c4367a904b51ea62a908ac310bf99ff90a82a3e" 359 | 360 | repeating@^2.0.0: 361 | version "2.0.1" 362 | resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" 363 | dependencies: 364 | is-finite "^1.0.0" 365 | 366 | slash@^1.0.0: 367 | version "1.0.0" 368 | resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" 369 | 370 | source-map-support@^0.4.2: 371 | version "0.4.11" 372 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.11.tgz#647f939978b38535909530885303daf23279f322" 373 | dependencies: 374 | source-map "^0.5.3" 375 | 376 | source-map@^0.5.0, source-map@^0.5.3: 377 | version "0.5.6" 378 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" 379 | 380 | strip-ansi@^3.0.0: 381 | version "3.0.1" 382 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 383 | dependencies: 384 | ansi-regex "^2.0.0" 385 | 386 | supports-color@^2.0.0: 387 | version "2.0.0" 388 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 389 | 390 | to-fast-properties@^1.0.1: 391 | version "1.0.2" 392 | resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.2.tgz#f3f5c0c3ba7299a7ef99427e44633257ade43320" 393 | 394 | trim-right@^1.0.1: 395 | version "1.0.1" 396 | resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" 397 | 398 | ua-parser-js@^0.7.9: 399 | version "0.7.10" 400 | resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.10.tgz#917559ddcce07cbc09ece7d80495e4c268f4ef9f" 401 | 402 | whatwg-fetch@>=0.10.0: 403 | version "1.0.0" 404 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-1.0.0.tgz#01c2ac4df40e236aaa18480e3be74bd5c8eb798e" 405 | --------------------------------------------------------------------------------