",
12 | "license": "BSD-3-Clause",
13 | "dependencies": {
14 | "react": "^0.14.3",
15 | "invariant": "^2.2.0"
16 | },
17 | "devDependencies": {
18 | "babel-core": "^6.2.1",
19 | "babel-loader": "^6.2.0",
20 | "babel-preset-es2015": "^6.1.18",
21 | "babel-preset-react": "^6.1.18",
22 | "babel-preset-stage-0": "^6.1.18",
23 | "browser-perf": "^1.4.3",
24 | "react-dom": "^0.14.3",
25 | "webpack": "^1.12.8",
26 | "webpack-dev-server": "^1.14.1"
27 | },
28 | "directories": {
29 | "test": "test"
30 | },
31 | "repository": {
32 | "type": "git",
33 | "url": "git+https://github.com/web-perf/react-worker-dom.git"
34 | },
35 | "bugs": {
36 | "url": "https://github.com/web-perf/react-worker-dom/issues"
37 | },
38 | "homepage": "http://web-perf.github.io/react-worker-dom"
39 | }
40 |
--------------------------------------------------------------------------------
/src/common/channel.js:
--------------------------------------------------------------------------------
1 | export default class Channel {
2 | constructor(channel) {
3 | this.channel = channel;
4 | }
5 | send(type, args) {
6 | this.channel.postMessage(JSON.stringify({
7 | type, args
8 | }));
9 | }
10 | onMessage(handler) {
11 | this.channel.addEventListener('message', (e) => {
12 | handler(JSON.parse(e.data));
13 | });
14 | }
15 | static serializeEvent(e) {
16 | var newTarget = {
17 | value: e.target.value,
18 | checked: e.target.checked,
19 | selected: e.target.selected
20 | }
21 | delete e.view;
22 | e.target = newTarget;
23 | return JSON.stringify(e);
24 | }
25 | static deserializeEvent(msg) {
26 | var e = JSON.parse(msg);
27 | e.preventDefault = e.stopPropgation = function() {}
28 | return e;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/common/constants.js:
--------------------------------------------------------------------------------
1 | // Message types passed between worker and UI Thread
2 | export const EVENT = 'event';
3 | export const RENDER_TIME = 'render_time';
4 | export const RENDER = 'render';
5 |
6 | // Operations to be performed while rendering
7 | export const RENDER_QUEUE = 'renderQueue';
8 | export const CONSTRUCTOR = 'constructor';
9 | export const ADD_CHILD = 'appendChild';
10 | export const ADD_CHILD_INDEX = 'addChildIndex';
11 | export const REMOVE_CHILD = 'removeChild';
12 | export const REMOVE_CHILD_INDEX = 'removeChildIndex';
13 | export const REPLACE_AT = 'replaceAt';
14 | export const SET_CONTENT = 'setContent';
15 | export const SET_ATTRIBUTES = 'setAttributes';
16 | export const ADD_EVENT_HANDLERS = 'addEventListeners';
17 | export const REMOVE_EVENT_HANDLERS = 'removeEventHandlers';
18 |
19 | // Other constants
20 | export const MAX_QUEUE_SIZE = 500;
--------------------------------------------------------------------------------
/src/page/NodeIDOperations.js:
--------------------------------------------------------------------------------
1 | function invariant(condition, msg, ...args) {
2 | if (!condition){
3 | console.error(msg, ...args);
4 | }
5 | }
6 |
7 | class NodeList {
8 | constructor(tag = '') {
9 | this.tag = tag;
10 | this.nodeList = {};
11 | }
12 | exists(id) {
13 | return typeof this.nodeList[id] !== 'undefined';
14 | }
15 | add(node) {
16 | invariant(!this.exists(node.guid), 'Node already exists', node.guid, node, this.nodeList[node.guid]);
17 | this.nodeList[node.guid] = node;
18 | }
19 | get(id) {
20 | invariant(this.exists(id), 'Id does not exist to get', id);
21 | return this.nodeList[id];
22 | }
23 | getByReactId(reactId) {
24 | for (let id in this.nodeList) {
25 | if (this.nodeList[id].reactId === reactId) {
26 | return this.nodeList[id];
27 | }
28 | }
29 | }
30 | remove(id) {
31 | invariant(this.exists(id), 'Id does not exist to remove', id);
32 | delete this.nodeList[id];
33 | }
34 | }
35 |
36 | export default new NodeList();
37 |
--------------------------------------------------------------------------------
/src/page/WorkerDomNodeImpl.js:
--------------------------------------------------------------------------------
1 | import ReactBrowserEventEmitter from 'react/lib/ReactBrowserEventEmitter';
2 | import EventConstants from 'react/lib/EventConstants';
3 |
4 | export default class WorkerDomNodeImpl {
5 | constructor(guid, reactId, el, options) {
6 | this.el = el;
7 | this.guid = guid;
8 | this.options = options;
9 | this.reactId = reactId;
10 | if (el === '#text') {
11 | this.ref = document.createTextNode(options.value);
12 | this.type = 'TEXT_NODE';
13 | } else {
14 | this.ref = document.createElement(el);
15 | this.ref.setAttribute('data-reactid', this.reactId);
16 | this.ref.setAttribute('data-reactwwid', this.guid);
17 | this.setAttributes(this.options);
18 | }
19 | }
20 | addChild(node, afterNode) {
21 | this.ref.appendChild(node.ref);
22 | }
23 | addChildAtIndex(node, index) {
24 | var nextNode = this.ref.childNodes[index];
25 | if (nextNode){
26 | this.ref.insertBefore(node.ref, nextNode);
27 | } else {
28 | this.ref.appendChild(node.ref);
29 | }
30 | }
31 | removeChild(node) {
32 | this.ref.removeChild(node.ref);
33 | }
34 | removeChildAtIndex(index) {
35 | var nodeToRemove = this.ref.childNodes[index];
36 | let guid = null;
37 | if (nodeToRemove.nodeType !== Node.TEXT_NODE){
38 | guid = nodeToRemove.getAttribute('data-reactwwid');
39 | }
40 | this.ref.removeChild(nodeToRemove);
41 | return guid;
42 | }
43 | replace(oldNode) {
44 | oldNode.ref.parentNode.replaceChild(this.ref, oldNode.ref);
45 | }
46 | setContent(content) {
47 | if (this.type === 'TEXT_NODE') {
48 | this.ref.nodeValue = content;
49 | } else {
50 | this.ref.innerHTML = escape(content);
51 | }
52 | }
53 | setAttributes(options) {
54 | for (let key in options) {
55 | setAttribute(this.ref, key, options[key]);
56 | }
57 | }
58 | addEventHandlers(container, onEvent, ...handlers) {
59 | handlers.forEach((handler) => {
60 | switch (this.el) {
61 | case 'form':
62 | this._listeners = [
63 | ReactBrowserEventEmitter.trapBubbledEvent(EventConstants.topLevelTypes.topReset, 'reset', this.ref),
64 | ReactBrowserEventEmitter.trapBubbledEvent(EventConstants.topLevelTypes.topSubmit, 'submit', this.ref)
65 | ];
66 | // TODO - Add more cases of events that do not bubble
67 | // Look at trapBubbledEventsLocal in REactDomComponent in react-dom
68 | }
69 | ReactBrowserEventEmitter.listenTo(handler, container);
70 | ReactBrowserEventEmitter.putListener(this.reactId, handler, (syntheticEvent, reactId, e) => {
71 | onEvent(handler, syntheticEvent, reactId, e);
72 | });
73 | });
74 | }
75 |
76 | removeEventHandlers() {
77 | ReactBrowserEventEmitter.deleteAllListeners(this.reactId);
78 | }
79 | }
80 |
81 | function setAttribute(node, key, value) {
82 | switch (key) {
83 | case 'style':
84 | for (var prop in value) {
85 | node.style[prop] = value[prop];
86 | }
87 | break;
88 | case 'checked':
89 | if (value) {
90 | node.checked = true;
91 | } else {
92 | node.checked = false;
93 | }
94 | break;
95 | case 'className':
96 | node.className = value;
97 | default:
98 | node.setAttribute(key, value);
99 |
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/page/index.js:
--------------------------------------------------------------------------------
1 | import WorkerDomNodeImpl from './WorkerDomNodeImpl';
2 | import Channel from './../common/channel';
3 | import { EVENT, RENDER_TIME, ADD_EVENT_HANDLERS, ADD_CHILD_INDEX, REMOVE_CHILD_INDEX, REMOVE_CHILD, REPLACE_AT, REMOVE_EVENT_HANDLERS, RENDER_QUEUE, CONSTRUCTOR, ADD_CHILD, RENDER, SET_ATTRIBUTES, SET_CONTENT } from './../common/constants';
4 | import ReactMount from 'react/lib/ReactMount';
5 |
6 | import inject from './inject';
7 | import NodeIDOps from './NodeIDOperations';
8 |
9 | class ReactWorkerDom {
10 | constructor(worker, container) {
11 | inject();
12 |
13 | this.container = container;
14 |
15 | this.channel = new Channel(worker);
16 | this.channel.onMessage(this.handleMessage.bind(this));
17 | this.channel.send(RENDER_TIME, {
18 | time: 1,
19 | count: 0
20 | });
21 | }
22 |
23 | handleMessage(data) {
24 | switch (data.type) {
25 | case RENDER_QUEUE:
26 | var start = performance.now();
27 | data.args.forEach(msg => this.handleRenderQueueMessage(msg));
28 | this.channel.send(RENDER_TIME, {
29 | time: performance.now() - start,
30 | count: data.args.length
31 | });
32 | break;
33 | default:
34 | console.log('Cannot handle message %s', data.type, data.args);
35 | }
36 | }
37 |
38 | handleRenderQueueMessage(data) {
39 | var node;
40 | if (data.method !== CONSTRUCTOR) {
41 | node = NodeIDOps.get(data.guid);
42 | //console.log('%s(%s:%s).%s', node.el, node.guid, node.reactId, data.method, ...data.args);
43 | }
44 | switch (data.method) {
45 | case CONSTRUCTOR:
46 | node = new WorkerDomNodeImpl(data.guid, data.reactId, ...data.args);
47 | NodeIDOps.add(node);
48 | break;
49 | case RENDER: // Should only be called once per worker
50 | this.container.appendChild(node.ref);
51 | ReactMount.registerContainer(node.ref);
52 | break;
53 | case ADD_CHILD:
54 | node.addChild(NodeIDOps.get(data.args[0]));
55 | break;
56 | case ADD_CHILD_INDEX:
57 | node.addChildAtIndex(NodeIDOps.get(data.args[0]), data.args[1]);
58 | break;
59 | case REMOVE_CHILD:
60 | node.removeChild(NodeIDOps.get(data.args[0]));
61 | break;
62 | case REMOVE_CHILD_INDEX:
63 | var removedNodeGuid = node.removeChildAtIndex(data.args);
64 | removedNodeGuid && NodeIDOps.remove(removedNodeGuid);
65 | break;
66 | case REPLACE_AT:
67 | let oldNode = NodeIDOps.getByReactId(data.args[0]);
68 | node.replace(oldNode);
69 | NodeIDOps.remove(oldNode.guid);
70 | break;
71 | case SET_ATTRIBUTES:
72 | node.setAttributes(...data.args);
73 | break;
74 | case SET_CONTENT:
75 | node.setContent(...data.args);
76 | break;
77 | case ADD_EVENT_HANDLERS:
78 | node.addEventHandlers(this.container, this.onEvent.bind(this), ...data.args);
79 | break;
80 | case REMOVE_EVENT_HANDLERS:
81 | node.removeEventHandlers();
82 | break;
83 | default:
84 | console.log('Cannot run %s on Node with id %s', data.method, data.id);
85 | }
86 | }
87 |
88 | onEvent(handler, syntheticEvent, reactId, e) {
89 | this.channel.send(EVENT, {
90 | reactId,
91 | eventType: handler,
92 | event: Channel.serializeEvent(syntheticEvent)
93 | });
94 | syntheticEvent.preventDefault();
95 | // FIXME - Prevent default first, but if this event does not prevent default
96 | // In the thread, raise this event again
97 | }
98 | }
99 |
100 | // Doing this so that it can be use both as
101 | // import {render} from 'react-worker-dom'; render();
102 | // import ReactDom from 'react-worker-dom'; ReactDom.render();
103 | module.exports = {
104 | render: function(worker, container) {
105 | return new ReactWorkerDom(worker, container);
106 | }
107 | };
108 |
--------------------------------------------------------------------------------
/src/page/inject.js:
--------------------------------------------------------------------------------
1 | import ReactInjection from 'react/lib/ReactInjection';
2 | import SimpleEventPlugin from 'react/lib/SimpleEventPlugin';
3 | import EnterLeaveEventPlugin from 'react/lib/EnterLeaveEventPlugin';
4 | import ChangeEventPlugin from 'react/lib/ChangeEventPlugin';
5 | import SelectEventPlugin from 'react/lib/SelectEventPlugin';
6 | import BeforeInputEventPlugin from 'react/lib/BeforeInputEventPlugin';
7 | import DefaultEventPluginOrder from 'react/lib/DefaultEventPluginOrder';
8 | import ReactEventListener from 'react/lib/ReactEventListener';
9 | import ReactReconcileTransaction from 'react/lib/ReactReconcileTransaction';
10 | import ReactDefaultBatchingStrategy from 'react/lib/ReactDefaultBatchingStrategy';
11 | import ReactInstanceHandles from 'react/lib/ReactInstanceHandles';
12 | import ReactMount from 'react/lib/ReactMount';
13 |
14 | var alreadyInjected = false;
15 |
16 |
17 | export default function inject() {
18 | if (alreadyInjected) {
19 | // TODO: This is currently true because these injections are shared between
20 | // the client and the server package. They should be built independently
21 | // and not share any injection state. Then this problem will be solved.
22 | return;
23 | }
24 |
25 | ReactInjection.EventEmitter.injectReactEventListener(
26 | ReactEventListener
27 | );
28 |
29 | ReactInjection.EventPluginHub.injectEventPluginOrder(DefaultEventPluginOrder);
30 | ReactInjection.EventPluginHub.injectInstanceHandle(ReactInstanceHandles);
31 | ReactInjection.EventPluginHub.injectMount(ReactMount);
32 |
33 |
34 | ReactInjection.EventPluginHub.injectEventPluginsByName({
35 | SimpleEventPlugin: SimpleEventPlugin,
36 | EnterLeaveEventPlugin: EnterLeaveEventPlugin,
37 | ChangeEventPlugin: ChangeEventPlugin,
38 | SelectEventPlugin: SelectEventPlugin,
39 | BeforeInputEventPlugin: BeforeInputEventPlugin,
40 | });
41 |
42 | ReactInjection.Updates.injectReconcileTransaction(
43 | ReactReconcileTransaction
44 | );
45 | ReactInjection.Updates.injectBatchingStrategy(
46 | ReactDefaultBatchingStrategy
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | context: path.join(__dirname),
6 | entry: {
7 | 'ReactWW-worker': './worker/index.js',
8 | 'ReactWorker': './page/index.js',
9 | },
10 | output: {
11 | filename: '[name]' + '.js',
12 | path: path.join(__dirname, './../dist'),
13 | },
14 | devtool: 'source-map',
15 | module: {
16 | loaders: [{
17 | test: /\.jsx?$/,
18 | loader: 'babel-loader',
19 | query: {
20 | presets: ['es2015', 'react', 'stage-0'],
21 | cacheDirectory: true
22 | },
23 | }]
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/worker/ReactWWChildOperations.js:
--------------------------------------------------------------------------------
1 | import ReactMultiChildUpdateTypes from 'react/lib/ReactMultiChildUpdateTypes';
2 | import ReactWWIDOperations from './ReactWWIDOperations';
3 |
4 | const {
5 | INSERT_MARKUP, MOVE_EXISTING, SET_MARKUP, TEXT_CONTENT, REMOVE_NODE
6 | } =
7 | ReactMultiChildUpdateTypes;
8 |
9 | export const actions = {
10 | [INSERT_MARKUP](update, components) {
11 | const parent = update.parentNode;
12 | const child = components[update.markupIndex];
13 |
14 | if (typeof child === 'string' || typeof child === 'number') {
15 | parent.setContent(child);
16 | } else {
17 | if (update.toIndex){
18 | parent.addChildAtIndex(child.getPublicInstance(), update.toIndex);
19 | } else {
20 | parent.addChild(child.getPublicInstance());
21 | }
22 | }
23 | }, [MOVE_EXISTING]() {
24 | console.log(MOVE_EXISTING);
25 | }, [SET_MARKUP]() {
26 | console.log(SET_MARKUP);
27 | }, [TEXT_CONTENT]() {
28 | console.log(TEXT_CONTENT);
29 | }, [REMOVE_NODE](update, components) {
30 | // FIXME - Since this is async, if more than one node from the same parent
31 | // Node is to be removed, this causes an error
32 | update.parentNode.removeChildFromIndex(update.fromIndex);
33 | }
34 | };
35 |
36 | export function processChildrenUpdates(updates, components) {
37 | for (let i = 0, l = updates.length; i < l; ++i) {
38 | updates[i].parentNode = ReactWWIDOperations.get(updates[i].parentID);
39 | let update = updates[i];
40 | actions[update.type](update, components);
41 | }
42 | }
43 |
44 | export function replaceNodeWithMarkupByID(reactId, markup) {
45 | // reactId here is the reactId of the old node
46 | // By the time we are here, the oldNode is already unmounted and hence gone from ReactWWOps
47 | // ASSUMPTION: The nextNode has the same reactId as the old node
48 |
49 | const nextNode = markup.getPublicInstance();
50 | nextNode.replaceAt(reactId);
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/worker/ReactWWComponent.js:
--------------------------------------------------------------------------------
1 | import ReactMultiChild from 'react/lib/ReactMultiChild';
2 |
3 | import WorkerDomNodeStub from './WorkerDomNodeStub';
4 | import ReactWWIDOperations from './ReactWWIDOperations';
5 |
6 | import ReactBrowserEventEmitter from 'react/lib/ReactBrowserEventEmitter';
7 |
8 |
9 | /**
10 | * Function to separate event Handlers and regular props
11 | * @param {Object} props Props passed to a React Component
12 | * @return {eventHandlers: {}, options: {}} An object containing eventHandlers and options
13 | */
14 | function extractEventHandlers(props) {
15 | let result = {
16 | eventHandlers: {},
17 | options: {}
18 | };
19 | for (let key in props) {
20 | if (ReactBrowserEventEmitter.registrationNameModules.hasOwnProperty(key)) {
21 | result.eventHandlers[key] = props[key];
22 | } else {
23 | result.options[key] = props[key];
24 | }
25 | }
26 | return result;
27 | }
28 |
29 | /**
30 | * Renders the given react element with webworkers.
31 | *
32 | * @constructor ReactWWComponent
33 | * @extends ReactMultiChild
34 | */
35 | export default class ReactWWComponent {
36 | constructor(tag) {
37 | this._tag = tag.toLowerCase();
38 | this._renderedChildren = null;
39 | this._previousStyle = null;
40 | this._previousStyleCopy = null;
41 | this._rootNodeID = null;
42 | this._wrapperState = null;
43 | this._topLevelWrapper = null;
44 | this._nodeWithLegacyProperties = null;
45 | }
46 |
47 | construct(element) {
48 | this._currentElement = element;
49 | }
50 |
51 | /**
52 | * Mounting the root component.
53 | *
54 | * @internal
55 | * @param {string} rootID - The root ID for this node.
56 | * @param {ReactReconcileTransaction} transaction
57 | * @param {object} context
58 | */
59 | mountComponent(rootID, transaction, context) {
60 | this._rootNodeID = rootID;
61 |
62 | const node = this.mountNode(ReactWWIDOperations.getParent(rootID), this._currentElement, transaction);
63 |
64 | ReactWWIDOperations.add(rootID, node);
65 |
66 | // Mounting children
67 | let childrenToUse = this._currentElement.props.children;
68 | childrenToUse = childrenToUse === null ? [] : [].concat(childrenToUse);
69 |
70 | if (childrenToUse.length) {
71 | this.mountChildren(childrenToUse, transaction, context);
72 | }
73 |
74 | // Rendering the rootNode
75 | ReactWWIDOperations.rootNode.render();
76 | return this;
77 | }
78 |
79 | /**
80 | * Mounting the node itself.
81 | *
82 | * @param {Node} parent - The parent node.
83 | * @param {ReactElement} element - The element to mount.
84 | * @return {Node} - The mounted node.
85 | */
86 | mountNode(parent, element, transaction) {
87 | const {
88 | props, type
89 | } = element, {
90 | children, ...restProps
91 | } = props;
92 |
93 | let {
94 | eventHandlers, options
95 | } = extractEventHandlers(restProps);
96 | const node = new WorkerDomNodeStub(this._rootNodeID, type, options);
97 | parent.addChild(node);
98 |
99 | transaction.getReactMountReady().enqueue(function(){
100 | this.node.addEventHandlers(this.eventHandlers);
101 | }, {
102 | node, eventHandlers
103 | });
104 |
105 | return node;
106 | }
107 |
108 | /**
109 | * Receive a component update.
110 | *
111 | * @param {ReactElement} nextElement
112 | * @param {ReactReconcileTransaction} transaction
113 | * @param {object} context
114 | * @internal
115 | * @overridable
116 | */
117 | receiveComponent(nextElement, transaction, context) {
118 | const {
119 | props: {
120 | children, ...restProps
121 | }
122 | } = nextElement, {
123 | eventHandlers, options
124 | } = extractEventHandlers(restProps);
125 |
126 | let node = ReactWWIDOperations.get(this._rootNodeID);
127 |
128 | node.setAttributes(options);
129 | //node.addEventHandlers(eventHandlers);
130 |
131 | this.updateChildren(children, transaction, context);
132 | //ReactWWIDOperations.rootNode.render(); <- No real need to update the parent also
133 | return this;
134 | }
135 |
136 | /**
137 | * Dropping the component.
138 | */
139 | unmountComponent() {
140 | this.unmountChildren();
141 |
142 | const node = ReactWWIDOperations.get(this._rootNodeID);
143 | node.removeEventHandlers();
144 |
145 | // Unmounting should not remove Child
146 | // THey will be removed when they are replaced in markup
147 | // Or when REMOVE_NODE in Child openrations is called
148 | //var parent = ReactWWIDOperations.getParent(node.reactId);
149 | //parent.removeChild(node);
150 |
151 | ReactWWIDOperations.drop(this._rootNodeID);
152 |
153 | this._rootNodeID = null;
154 |
155 | ReactWWIDOperations.rootNode.render();
156 | }
157 |
158 | /**
159 | * Getting a public instance of the component for refs.
160 | *
161 | * @return {Node} - The instance's node.
162 | */
163 | getPublicInstance() {
164 | return ReactWWIDOperations.get(this._rootNodeID);
165 | }
166 | }
167 |
168 | /**
169 | * Extending the component with the MultiChild mixin.
170 | */
171 | Object.assign(
172 | ReactWWComponent.prototype,
173 | ReactMultiChild.Mixin
174 | );
175 |
--------------------------------------------------------------------------------
/src/worker/ReactWWIDOperations.js:
--------------------------------------------------------------------------------
1 | import WorkerDomNodeStub from './WorkerDomNodeStub';
2 |
3 | const nodes = {};
4 |
5 | /**
6 | * Backend for ID operations.
7 | */
8 | class ReactWWIDOperations {
9 | setRoot(root) {
10 | this.rootNode = root;
11 | }
12 |
13 | add(ID, node) {
14 | nodes[ID] = node;
15 | return this;
16 | }
17 | get(ID) {
18 | return nodes[ID];
19 | }
20 | drop(ID) {
21 | delete nodes[ID];
22 | return this;
23 | }
24 |
25 | getParent(ID) {
26 | // If the node is root, we return the rootNode itself
27 | if (ID.match(/\./g).length === 1)
28 | return this.rootNode;
29 |
30 | const parentID = ID.split('.').slice(0, -1).join('.');
31 | return this.get(parentID);
32 | }
33 | }
34 |
35 | export default new ReactWWIDOperations();
36 |
--------------------------------------------------------------------------------
/src/worker/ReactWWInjection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Injecting the renderer's needed dependencies into React's internals.
3 | */
4 | import ReactInjection from 'react/lib/ReactInjection';
5 | import ReactComponentEnvironment from 'react/lib/ReactComponentEnvironment';
6 | import ReactDOMFeatureFlags from 'react/lib/ReactDOMFeatureFlags'
7 |
8 | import ReactWWReconcileTransaction from './ReactWWReconcileTransaction';
9 | import ReactWWComponent from './ReactWWComponent';
10 | import ReactWWTextComponent from './ReactWWTextComponent';
11 |
12 |
13 | import {
14 | processChildrenUpdates, replaceNodeWithMarkupByID
15 | }
16 | from './ReactWWChildOperations';
17 |
18 | export default function inject() {
19 |
20 | ReactInjection.NativeComponent.injectGenericComponentClass(
21 | ReactWWComponent
22 | );
23 |
24 | ReactInjection.Updates.injectReconcileTransaction(
25 | ReactWWReconcileTransaction
26 | );
27 |
28 | ReactInjection.NativeComponent.injectTextComponentClass(
29 | ReactWWTextComponent
30 | );
31 |
32 | ReactInjection.EmptyComponent.injectEmptyComponent('element');
33 |
34 | ReactComponentEnvironment.processChildrenUpdates = processChildrenUpdates;
35 | ReactComponentEnvironment.replaceNodeWithMarkupByID = replaceNodeWithMarkupByID
36 | }
37 |
--------------------------------------------------------------------------------
/src/worker/ReactWWReconcileTransaction.js:
--------------------------------------------------------------------------------
1 | /**
2 | * React Specific React Transaction
3 | * =========================================
4 | *
5 | * React custom reconcile transaction injected by the renderer to enable
6 | * updates.
7 | *
8 | * NOTE: This looks more like a shim than the proper thing actually.
9 | */
10 | import CallbackQueue from 'react/lib/CallbackQueue';
11 | import PooledClass from 'react/lib/PooledClass';
12 | import Transaction from 'react/lib/Transaction';
13 |
14 | const ON_READY_QUEUEING = {
15 | initialize: function () {
16 | this.reactMountReady.reset();
17 | },
18 | close: function () {
19 | this.reactMountReady.notifyAll();
20 | }
21 | };
22 |
23 | function ReactWWReconcileTransaction() {
24 | this.useCreateElement = true;
25 | this.reinitializeTransaction();
26 | this.reactMountReady = CallbackQueue.getPooled(null);
27 | }
28 |
29 | const Mixin = {
30 | getTransactionWrappers: function() {
31 | return [ON_READY_QUEUEING];
32 | },
33 | getReactMountReady: function() {
34 | return this.reactMountReady;
35 | },
36 | destructor: function() {
37 | CallbackQueue.release(this.reactMountReady);
38 | this.reactMountReady = null;
39 | }
40 | };
41 |
42 | Object.assign(
43 | ReactWWReconcileTransaction.prototype,
44 | Transaction.Mixin,
45 | Mixin
46 | );
47 |
48 | PooledClass.addPoolingTo(ReactWWReconcileTransaction);
49 |
50 | export default ReactWWReconcileTransaction;
51 |
--------------------------------------------------------------------------------
/src/worker/ReactWWTextComponent.js:
--------------------------------------------------------------------------------
1 | import ReactWWIDOperations from './ReactWWIDOperations';
2 | import Node from './WorkerDomNodeStub';
3 |
4 | export default class ReactWWTextComponent {
5 | constructor(props) {}
6 |
7 | construct(text) {
8 | this._currentElement = text;
9 | this._rootNodeID = null;
10 | }
11 |
12 | mountComponent(rootID, transaction, context) {
13 | this._rootNodeID = rootID;
14 | const parent = ReactWWIDOperations.getParent(this._rootNodeID);
15 | const node = new Node(this._rootNodeID, '#text', {
16 | value: this._currentElement
17 | });
18 | parent.addChild(node);
19 | ReactWWIDOperations.add(this._rootNodeID, node);
20 | return node;
21 | }
22 |
23 | receiveComponent(nextText, transaction) {
24 | if (this._currentElement !== nextText) {
25 | this._currentElement = nextText;
26 | const node = ReactWWIDOperations.get(this._rootNodeID);
27 | node.setContent(this._currentElement);
28 | }
29 | return this;
30 | }
31 |
32 | unmountComponent() {
33 | // Nothing really to do, since this just sets the content
34 | }
35 |
36 | getPublicInstance() {
37 | return this._currentElement;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/worker/WorkerBridge.js:
--------------------------------------------------------------------------------
1 | import Channel from './../common/channel';
2 | import {EVENT, RENDER_QUEUE, RENDER_TIME, MAX_QUEUE_SIZE} from './../common/constants';
3 | import ReactWWIDOperations from './ReactWWIDOperations';
4 |
5 | class WorkerBridge {
6 | constructor() {
7 | this.queue = [];
8 | this.channel = new Channel(self);
9 | this.channel.onMessage(this.handleMessage.bind(this));
10 | this.pollQueue();
11 | this.TIMEOUT = 5;
12 | }
13 |
14 | pollQueue(){
15 | self.setTimeout(() => {
16 | this.flushQueue();
17 | this.pollQueue();
18 | }, this.TIMEOUT);
19 | }
20 |
21 | handleMessage(data){
22 | switch (data.type) {
23 | case EVENT:
24 | this.handleEvent(data.args);
25 | break;
26 | case RENDER_TIME:
27 | this.rate = data.args.count / data.args.time;
28 | break;
29 | default:
30 | console.log('Unknown operation %s', data);
31 | }
32 | }
33 |
34 | postMessage(msg) {
35 | this.queue.push(msg);
36 | }
37 |
38 | flushQueue(){
39 | if (this.queue.length === 0){
40 | return;
41 | }
42 | this.channel.send(RENDER_QUEUE, this.queue);
43 | this.queue = [];
44 | }
45 |
46 | handleEvent(data) {
47 | var node = ReactWWIDOperations.get(data.reactId);
48 | node.on(data.eventType, Channel.deserializeEvent(data.event));
49 | }
50 | }
51 |
52 | export default new WorkerBridge();
53 |
--------------------------------------------------------------------------------
/src/worker/WorkerDomNodeStub.js:
--------------------------------------------------------------------------------
1 | import Bridge from './WorkerBridge';
2 | import {CONSTRUCTOR, ADD_CHILD, ADD_CHILD_INDEX, REMOVE_CHILD, REMOVE_CHILD_INDEX, REPLACE_AT ,SET_CONTENT, REMOVE_EVENT_HANDLERS, SET_ATTRIBUTES, ADD_EVENT_HANDLERS, RENDER} from './../common/constants';
3 |
4 | var guid = 0;
5 |
6 | export default class WorkerDomNodeStub {
7 | constructor(reactId, el, options) {
8 | this.el = el;
9 | this.options = options;
10 | this.eventHandlers = {};
11 | this.reactId = reactId;
12 | this.guid = guid++;
13 | this.impl(CONSTRUCTOR, [this.el, this.options]);
14 | }
15 | addChild(node) {
16 | this.impl(ADD_CHILD, [node.guid]);
17 | }
18 | addChildAtIndex(node, index) {
19 | this.impl(ADD_CHILD_INDEX, [node.guid, index]);
20 | }
21 | removeChild(node) {
22 | this.impl(REMOVE_CHILD, [node.guid]);
23 | }
24 | removeChildFromIndex(index){
25 | this.impl(REMOVE_CHILD_INDEX, index);
26 | }
27 | replaceAt(reactId){
28 | this.impl(REPLACE_AT, [reactId]);
29 | }
30 | setContent(content) {
31 | this.impl(SET_CONTENT, [content]);
32 | }
33 | setAttributes(options) {
34 | this.impl(SET_ATTRIBUTES, [options]);
35 | }
36 | addEventHandlers(handlers) {
37 | let canSend = false;
38 | for (let key in handlers) {
39 | canSend = true;
40 | this.eventHandlers[key] = handlers[key];
41 | }
42 | if (canSend) {
43 | this.impl(ADD_EVENT_HANDLERS, Object.keys(handlers));
44 | }
45 | }
46 | removeEventHandlers(handlers){
47 | this.impl(REMOVE_EVENT_HANDLERS);
48 | }
49 | on(eventName, e) {
50 | var fn = this.eventHandlers[eventName];
51 | if (typeof fn === 'function') {
52 | fn.call(this, e);
53 | }
54 | }
55 | render() {
56 | this.impl(RENDER);
57 | }
58 | impl(method, args = []) { // Sends a messages to the Implementation
59 | Bridge.postMessage({
60 | method,
61 | args,
62 | reactId: this.reactId,
63 | guid: this.guid
64 | });
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/worker/index.js:
--------------------------------------------------------------------------------
1 | import ReactInstanceHandles from 'react/lib/ReactInstanceHandles';
2 | import ReactElement from 'react/lib/ReactElement';
3 | import ReactUpdates from 'react/lib/ReactUpdates';
4 | import instantiateReactComponent from 'react/lib/instantiateReactComponent';
5 | import invariant from 'invariant';
6 |
7 | import inject from './ReactWWInjection';
8 | import ReactWWIDOperations from './ReactWWIDOperations';
9 | import WorkerDomNodeStub from './WorkerDomNodeStub';
10 |
11 | /**
12 | * Injecting dependencies.
13 | */
14 | inject();
15 |
16 | /**
17 | * Renders the given react element using a web worker.
18 | *
19 | * @param {ReactElement} element - Node to update.
20 | * @return {ReactComponent} - The rendered component instance.
21 | */
22 | function render(element) {
23 | // Is the given element valid?
24 | invariant(
25 | ReactElement.isValidElement(element),
26 | 'render(): You must pass a valid ReactElement.'
27 | );
28 |
29 | const id = ReactInstanceHandles.createReactRootID(); // Creating a root id & creating the screen
30 | const component = instantiateReactComponent(element); // Mounting the app
31 | const transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
32 |
33 | ReactWWIDOperations.setRoot(new WorkerDomNodeStub('0', 'div', {}));
34 |
35 | // The initial render is synchronous but any updates that happen during
36 | // rendering, in componentWillMount or componentDidMount, will be batched
37 | // according to the current batching strategy.
38 | ReactUpdates.batchedUpdates(() => {
39 | transaction.perform(() => {
40 | component.mountComponent(id, transaction, {});
41 | });
42 | ReactUpdates.ReactReconcileTransaction.release(transaction);
43 | });
44 |
45 | return component._instance;
46 | }
47 |
48 | module.exports = {
49 | render: render
50 | };
51 |
--------------------------------------------------------------------------------
/test/dbmonster/components/app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import getData from './data';
3 |
4 | class Query extends React.Component{
5 | lpad(padding, toLength, str) {
6 | return padding.repeat((toLength - str.length) / padding.length).concat(str);
7 | };
8 |
9 | formatElapsed(value) {
10 | var str = parseFloat(value).toFixed(2);
11 | if (value > 60) {
12 | minutes = Math.floor(value / 60);
13 | comps = (value % 60).toFixed(2).split('.');
14 | seconds = this.lpad('0', 2, comps[0]);
15 | ms = comps[1];
16 | str = minutes + ":" + seconds + "." + ms;
17 | }
18 | return str;
19 | };
20 |
21 | render() {
22 | var className = "elapsed short";
23 | if (this.props.elapsed >= 10.0) {
24 | className = "elapsed warn_long";
25 | } else if (this.props.elapsed >= 1.0) {
26 | className = "elapsed warn";
27 | }
28 |
29 | return (
30 |
31 | {this.props.elapsed ? this.formatElapsed(this.props.elapsed) : '-'}
32 |
33 |
{this.props.query}
34 |
35 |
36 |
37 | );
38 | };
39 | }
40 |
41 |
42 | class Database extends React.Component{
43 |
44 | sample(queries, time) {
45 | var topFiveQueries = queries.slice(0, 5);
46 | while (topFiveQueries.length < 5) {
47 | topFiveQueries.push({ query: "" });
48 | }
49 |
50 | var _queries = [];
51 | topFiveQueries.forEach(function(query, index) {
52 | _queries.push(
53 |
58 | );
59 | });
60 |
61 | var countClassName = "label";
62 | if (queries.length >= 20) {
63 | countClassName += " label-important";
64 | }
65 | else if (queries.length >= 10) {
66 | countClassName += " label-warning";
67 | }
68 | else {
69 | countClassName += " label-success";
70 | }
71 |
72 | return [
73 |
74 |
75 | {queries.length}
76 |
77 | ,
78 | _queries
79 | ];
80 | };
81 |
82 | render() {
83 | var lastSample = this.props.samples[this.props.samples.length - 1];
84 |
85 | return (
86 |
87 |
88 | {this.props.dbname}
89 |
90 | {this.sample(lastSample.queries, lastSample.time)}
91 |
92 | );
93 | };
94 | };
95 |
96 | class DBMon extends React.Component {
97 | constructor(props) {
98 | super(props);
99 | this.state = {
100 | databases: {}
101 | };
102 | };
103 |
104 | loadSamples() {
105 | var newData = getData(this.props.rows);
106 | Object.keys(newData.databases).forEach(function(dbname) {
107 | var sampleInfo = newData.databases[dbname];
108 | if (!this.state.databases[dbname]) {
109 | this.state.databases[dbname] = {
110 | name: dbname,
111 | samples: []
112 | }
113 | }
114 |
115 | var samples = this.state.databases[dbname].samples;
116 | samples.push({
117 | time: newData.start_at,
118 | queries: sampleInfo.queries
119 | });
120 | if (samples.length > 5) {
121 | samples.splice(0, samples.length - 5);
122 | }
123 | }.bind(this));
124 |
125 | this.setState(this.state);
126 | //setTimeout(function(){this.setState(this.state)}.bind(this), 100);
127 | setTimeout(this.loadSamples.bind(this), this.props.timeout);
128 | };
129 |
130 | componentDidMount() {
131 | this.loadSamples();
132 | };
133 |
134 | render() {
135 | var databases = [];
136 | Object.keys(this.state.databases).forEach(function(dbname) {
137 | databases.push(
138 |
141 | );
142 | }.bind(this));
143 |
144 | return (
145 |
146 |
147 |
148 | {databases}
149 |
150 |
151 |
152 | );
153 | };
154 | }
155 |
156 | export default DBMon;
--------------------------------------------------------------------------------
/test/dbmonster/components/data.js:
--------------------------------------------------------------------------------
1 | export default function(rows) {
2 | // generate some dummy data
3 | var data = {
4 | start_at: new Date().getTime() / 1000,
5 | databases: {}
6 | };
7 |
8 | for (var i = 1; i <= rows; i++) {
9 | data.databases["cluster" + i] = {
10 | queries: []
11 | };
12 |
13 | data.databases["cluster" + i + "slave"] = {
14 | queries: []
15 | };
16 | }
17 |
18 | Object.keys(data.databases).forEach(function(dbname) {
19 | var info = data.databases[dbname];
20 |
21 | var r = Math.floor((Math.random() * 10) + 1);
22 | for (var i = 0; i < r; i++) {
23 | var q = {
24 | canvas_action: null,
25 | canvas_context_id: null,
26 | canvas_controller: null,
27 | canvas_hostname: null,
28 | canvas_job_tag: null,
29 | canvas_pid: null,
30 | elapsed: Math.random() * 15,
31 | query: "SELECT blah FROM something",
32 | waiting: Math.random() < 0.5
33 | };
34 |
35 | if (Math.random() < 0.2) {
36 | q.query = " in transaction";
37 | }
38 |
39 | if (Math.random() < 0.1) {
40 | q.query = "vacuum";
41 | }
42 |
43 | info.queries.push(q);
44 | }
45 |
46 | info.queries = info.queries.sort(function(a, b) {
47 | return b.elapsed - a.elapsed;
48 | });
49 | });
50 |
51 | return data;
52 | }
53 |
--------------------------------------------------------------------------------
/test/dbmonster/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
25 |
26 |
27 |
28 |
29 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/test/dbmonster/main-normal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {render} from 'react-dom';
3 |
4 | import App from './components/app.jsx';
5 |
6 | for (var i = 0; i < ENV.count; i++) {
7 | render(, document.getElementById('topLevelContainer-' + i));
8 | }
--------------------------------------------------------------------------------
/test/dbmonster/main-worker.js:
--------------------------------------------------------------------------------
1 | // import React from 'react'; <-- Don't need this. Component is defined in worker-impl.js
2 | import {render} from 'react-worker-dom';
3 |
4 | // import DBMon from './components/app.jsx'; <-- Don't need this. Defined in worker-impl.js
5 |
6 | for (var i = 0; i < ENV.count; i++) {
7 | render(new Worker('/react-worker-dom/dist/dbmonster/worker-impl.js#rows=' + ENV.rows + '&timeout=' + ENV.timeout), document.getElementById('topLevelContainer-' + i));
8 | }
9 |
--------------------------------------------------------------------------------
/test/dbmonster/worker-impl.jsx:
--------------------------------------------------------------------------------
1 | /* This file is added from a Web Worker - look at page-worker.js for the main file in the page */
2 |
3 | // Helper functions to parse additional params passed to this worker
4 | // Will not be needed unless your app needs to pass params to the web worker
5 | var ENV = parseArgs(self.location.hash.substring(1));
6 | function parseArgs(uri) {
7 | var q = {};
8 | uri.replace(new RegExp("([^?=&]+)(=([^&]*))?", "g"), function($0, $1, $2, $3) {
9 | q[$1] = $3;
10 | });
11 | return {
12 | timeout: parseInt('0' + q.timeout, 10),
13 | rows: parseInt('0' + q.rows, 10),
14 | }
15 | }
16 |
17 | // -------------------------------------------------------------
18 | // Start of actual code that an application will need
19 |
20 | import React from 'react';
21 | import ReactWorkerDOM from 'react-worker-dom-worker';
22 |
23 | import App from './components/app.jsx';
24 |
25 | ReactWorkerDOM.render();
26 |
27 |
28 |
--------------------------------------------------------------------------------
/test/perf.js:
--------------------------------------------------------------------------------
1 | /*
2 | Performance Measurement suite for the DBMonster app
3 |
4 | - Ensure that a HTTP server is running at the root of this project
5 | - Ensure that the project is built and dist folder has all the generated files
6 | */
7 |
8 | var fs = require('fs');
9 | var browserPerf = require('browser-perf');
10 |
11 | var ROWS = [1, 2, 3, 5, 7, 10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100, 120, 150, 170, 200].reverse();
12 | var FILE = '_dbmonster-perf.json';
13 |
14 | var IS_WORKER = true;
15 | var BROWSER = 'chrome'
16 |
17 | if (process.argv[2] !== 'worker') {
18 | IS_WORKER = false;
19 | }
20 | if (process.argv[3] !== 'chrome') {
21 | BROWSER = 'android';
22 | }
23 |
24 | (function run(i) {
25 | if (i < ROWS.length) {
26 | var row = ROWS[i];
27 | var url = ['http://localhost:8080/test/dbmonster/index.html#worker=', IS_WORKER, '&rows=', row].join('');
28 | browserPerf(null, function(err, res) {
29 | if (err) {
30 | console.log('ERROR', err);
31 | } else {
32 | saveResults(res[0], BROWSER + (IS_WORKER ? 'worker' : 'normal'), row);
33 | run(i + 1);
34 | }
35 | }, {
36 | selenium: 'http://localhost:9515',
37 | preScript: function(b) {
38 | return b.get(url).then(function() {
39 | return b.sleep(3000);
40 | });
41 | },
42 | actions: function(b) {
43 | return b.sleep(5000);
44 | },
45 | browsers: [{
46 | browserName: BROWSER
47 | }],
48 | metrics: ['TimelineMetrics']
49 | });
50 | }
51 | }(0));
52 |
53 |
54 |
55 | function saveResults(result, isWorker, rows) {
56 | var res = {};
57 |
58 | try {
59 | res = JSON.parse(fs.readFileSync(FILE));
60 | } catch (e) {}
61 | if (typeof res[isWorker] === 'undefined') {
62 | res[isWorker] = {};
63 | }
64 | if (typeof res[isWorker][rows] === 'undefined') {
65 | res[isWorker][rows] = [];
66 | }
67 | res[isWorker][rows].push(result['framesPerSec (devtools)']);
68 | fs.writeFileSync(FILE, JSON.stringify(res, null, 4));
69 | }
70 |
--------------------------------------------------------------------------------
/test/todo/components/app.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var TodoItem = require('./todoItem.jsx');
3 | var TodoForm = require('./todoForm.jsx');
4 | var Clock = require('./clock.jsx');
5 |
6 | module.exports = React.createClass({
7 | getInitialState: function(){
8 | return {
9 | items: []
10 | }
11 | },
12 |
13 | addItem: function(item) {
14 | this.setState({
15 | items: this.state.items.concat({
16 | text: item,
17 | done: false
18 | })
19 | });
20 | },
21 |
22 | deleteItem: function(i) {
23 | var a = this.state.items;
24 | this.setState({
25 | items: a.slice(0,i).concat(a.slice(i+1, a.length))
26 | });
27 | },
28 |
29 | toggleItem: function(i, checked) {
30 | var a = this.state.items;
31 | var items = a.slice(0,i)
32 | .concat({
33 | text: this.state.items[i].text,
34 | done: checked
35 | })
36 | .concat(a.slice(i+1, a.length));
37 |
38 | this.setState({
39 | items: items
40 | });
41 | },
42 |
43 | moveItem: function(i, direction){
44 | var items = this.state.items.slice(0);
45 | var swap = items[i + direction];
46 | if (swap){
47 | items[i + direction] = items[i];
48 | items[i] = swap;
49 | this.setState({items: items});
50 | }
51 | },
52 |
53 | renderList: function(items){
54 | if (items.length === 0){
55 | return Add some todo items
56 | } else {
57 | return (
58 |
59 | {items.map((item, i) => {
60 | return (
61 |
66 | );
67 | })}
68 |
69 | )
70 | }
71 | },
72 |
73 | render: function() {
74 | return (
75 |
76 |
TODO
77 | {this.renderList(this.state.items)}
78 |
79 |
80 |
81 |
82 | );
83 | }
84 | });
85 |
--------------------------------------------------------------------------------
/test/todo/components/clock.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | module.exports = React.createClass({
4 | getInitialState(){
5 | return {
6 | time: new Date(),
7 | closed: false
8 | }
9 | },
10 | componentDidMount(){
11 | this.timerHandle = setInterval(()=>{
12 | this.setState({
13 | time: new Date()
14 | });
15 | }, 1000);
16 | },
17 | componentWillUnmount(){
18 | if (this.timerHandle){
19 | clearInterval(this.timerHandle);
20 | delete this.timerHandle;
21 | }
22 | },
23 | closeTime: function(){
24 | this.setState({closed: true});
25 | this.componentWillUnmount();
26 | },
27 | render(){
28 | if (this.state.closed){
29 | return (
);
30 | } else {
31 | return (
32 |
33 | Current time:
34 |
35 | {this.state.time.toString()}
36 |
37 |
38 | ×
39 |
40 |
41 |
42 | );
43 | }
44 | }
45 | });
46 |
--------------------------------------------------------------------------------
/test/todo/components/todoForm.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | module.exports = React.createClass({
4 | getInitialState: function(){
5 | return {text: ''}
6 | },
7 | onChange: function(e){
8 | this.setState({text: e.target.value});
9 | },
10 | handleSubmit: function(e) {
11 | this.props.onAddItem(this.state.text || '');
12 | this.setState({
13 | text: ''
14 | });
15 | e.preventDefault();
16 | },
17 | render: function(){
18 | return (
19 |
33 | );
34 | }
35 | });
36 |
--------------------------------------------------------------------------------
/test/todo/components/todoItem.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | module.exports = React.createClass({
4 | onDelete: function() {
5 | this.props.onDelete(this.props.index);
6 | },
7 |
8 | onToggle: function(e) {
9 | this.props.onToggle(this.props.index, e.target.checked);
10 | },
11 |
12 | moveUp: function(e){
13 | this.props.onMove(this.props.index, -1);
14 | },
15 |
16 | moveDown: function(){
17 | this.props.onMove(this.props.index, 1);
18 | },
19 |
20 | render: function() {
21 | return (
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {this.props.item.text}
34 |
35 |
36 | );
37 | }
38 | });
--------------------------------------------------------------------------------
/test/todo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React-Dom-Worker:: TODO app
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Implemented using Webworkers . View normal version.
24 |
25 |
26 | Implemented with plan react. No webworkers used. View version with web workers
27 |
28 |
29 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/test/todo/normal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {render} from 'react-dom';
3 |
4 | import App from './components/app.jsx';
5 |
6 | render( , document.getElementById('content'));
7 |
--------------------------------------------------------------------------------
/test/todo/worker-impl.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {render} from 'react-worker-dom-worker';
3 |
4 | import App from './components/app.jsx';
5 |
6 | render( );
7 |
--------------------------------------------------------------------------------
/test/todo/worker.jsx:
--------------------------------------------------------------------------------
1 |
2 | import {render} from 'react-worker-dom';
3 |
4 |
5 |
6 | render(new Worker('/react-worker-dom/dist/todo/worker-impl.js'), document.getElementById('content'));
7 |
--------------------------------------------------------------------------------
/test/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | module.exports = {
3 | context: __dirname,
4 | entry: {
5 | // DB Monster
6 | 'dbmonster/main-normal': './dbmonster/main-normal.jsx',
7 | 'dbmonster/main-worker': './dbmonster/main-worker.js',
8 | 'dbmonster/worker-impl': './dbmonster/worker-impl.jsx',
9 |
10 | // ToDo App
11 | 'todo/normal': './todo/normal.jsx',
12 | 'todo/worker': './todo/worker.jsx',
13 | 'todo/worker-impl': './todo/worker-impl.jsx'
14 | },
15 | output: {
16 | filename: '[name].js',
17 | path: path.join(__dirname, '../dist'),
18 | publicPath: '/react-worker-dom/dist'
19 | },
20 | devtool: 'source-map',
21 | module: {
22 | loaders: [{
23 | test: /\.jsx?$/,
24 | loader: 'babel-loader?presets[]=es2015&presets[]=react&presets[]=stage-0',
25 | }]
26 | },
27 | resolve: {
28 | alias: {
29 | 'react-worker-dom': path.resolve(__dirname, './../src/page/index.js'),
30 | 'react-worker-dom-worker': path.resolve(__dirname, './../src/worker/index.js')
31 | }
32 | },
33 | };
34 |
--------------------------------------------------------------------------------