├── .babelrc ├── .gitignore ├── API.md ├── README.md ├── demo ├── nqueen │ ├── actions │ │ └── nqueen │ │ │ └── nqueen.js │ ├── components │ │ ├── App.js │ │ ├── Blinker.js │ │ ├── Counter.js │ │ ├── Nqueen.js │ │ ├── Slider.js │ │ └── Spinner.js │ ├── containers │ │ └── NqueenContainer.js │ ├── dist │ │ ├── main.bundle.js │ │ └── worker.bundle.js │ ├── index.html │ ├── main.js │ ├── package.json │ ├── reducers │ │ ├── index.js │ │ ├── nqueen │ │ │ └── nqueen.js │ │ └── worker.js │ ├── solver.js │ └── webpack.config.js └── sortTable │ ├── actions │ └── users │ │ └── users.js │ ├── components │ ├── App.js │ ├── Generator.js │ ├── Spinner.js │ └── UserTable.js │ ├── containers │ ├── GeneratorContainer.js │ └── UserTableContainer.js │ ├── dist │ ├── main.bundle.js │ └── worker.bundle.js │ ├── index.html │ ├── main.js │ ├── package.json │ ├── reducers │ ├── index.js │ ├── users │ │ └── users.js │ └── worker.js │ ├── usersFixtures.js │ └── webpack.config.js ├── dist └── reduxWorker.js ├── karma.conf.js ├── package.json ├── spec ├── applyWorker.spec.js ├── createWorker.spec.js └── test-context.js ├── src ├── applyWorker.js ├── createWorker.js └── reduxWorker.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "plugins": ["transform-object-assign"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.log 4 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | ## createWorker 4 | This helper function should be called within your worker. It will return an instance of `ReduxWorker`, which provides the following methods for you to set up your web worker: 5 | 6 | - registerReducer(reducer) 7 | - registerTask(taskName, taskCallback) 8 | 9 | Example: 10 | ```js 11 | import reducer from '../reducers' 12 | import solve from '../solver' 13 | import { createWorker } from 'redux-worker' 14 | 15 | // Instantiate ReduxWorker 16 | let worker = createWorker(); 17 | 18 | // Registering your reducer. 19 | worker.registerReducer(reducer); 20 | 21 | // Register tasks to be executable on web worker, if needed 22 | worker.registerTask('NQUEEN_TASK', function(a) { 23 | let n = a.number; 24 | return +n < 16 ? solve(+n).length : 'N is too large...'; 25 | }); 26 | ``` 27 | 28 | ## applyWorker 29 | This helper function is a store enhancer that decorates the Redux store. It is intended to be applied to your store configuration as a middleware. If an invalid web worker is provided here, the middleware will fall back to creating a regular redux store. 30 | 31 | Example: 32 | ```js 33 | import React from 'react' 34 | import { render } from 'react-dom' 35 | import { Provider } from 'react-redux' 36 | import { createStore, applyMiddleware, compose } from 'redux' 37 | import thunk from 'redux-thunk' 38 | import rootReducer from './reducers' 39 | import App from './components/App' 40 | import { applyWorker } from 'redux-worker' 41 | 42 | // Spinning up the web worker. 43 | // worker.bundle.js comes from a build process that I set up to compile and serve worker.js 44 | // created using createWorker. 45 | // If you do not want to have a separate build process for this, look into the following: 46 | // - webworker-loader for Web Pack (https://github.com/bjyoungblood/webworker-loader) 47 | // - webworkify for browerify (https://github.com/substack/webworkify) 48 | 49 | const worker = window.Worker ? new Worker('./dist/worker.bundle.js') : null; 50 | 51 | // Apply worker middleware 52 | const enhancerWithWorker = compose( 53 | applyMiddleware(thunk), 54 | applyWorker(worker) 55 | ); 56 | 57 | const store = createStore(rootReducer, {}, enhancerWithWorker); 58 | 59 | render( 60 | 61 | 62 | , 63 | document.getElementById('app') 64 | ) 65 | ``` 66 | 67 | ## dispatch 68 | The new dispatcher will have the following flow: 69 | 1. Post a message to redux-worker 70 | 2. Redux-worker uses the reducer to change the state of the store 71 | 3. Redux-worker posts a message back to main thread 72 | 4. Main thread updates the new state 73 | 5. All subscribed components are rendered 74 | 75 | You can now also dispatch a task to be executed in `redux-worker` outside the context of Redux: useful for tasks like encryption/decryption, rich text formatting, etc. 76 | 77 | Task Creator 78 | ```js 79 | // This is basically the same as your action creators, except that instead of 'type', it 80 | // expects 'task'. This 'task' attribute must equal to what's provided to registerTask 81 | const calcNqueenTask = (n) => { 82 | return { 83 | task: 'NQUEEN_TASK', 84 | number: n 85 | }; 86 | } 87 | ``` 88 | 89 | Usage 90 | ```js 91 | // When dispatching a task, the dispatcher will return a promise that 92 | // will be resolved when the task is completed. 93 | this.props.actions 94 | .calcNqueenTask(this.state.inputValue) 95 | .then(function(returnObj) { 96 | // The returnObj contains _taskId and response 97 | // response is whatever the return value is based on 98 | // the callback provided to registerTask 99 | alert( 100 | ` 101 | This task is run directly on the web worker without going thru Redux.\n 102 | The taskId is ${returnObj._taskId}.\n 103 | The answer is ${returnObj.response}. 104 | ` 105 | ); 106 | }); 107 | ``` 108 | 109 | 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redux-worker 2 | 3 | Redux-Worker is a [middleware](http://redux.js.org/docs/advanced/Middleware.html) for [Redux](https://github.com/reactjs/redux). 4 | 5 | It helps you build multi-threaded JavaScript applications by moving your reducer into a Web Worker. It also provides a simple [API](https://github.com/chikeichan/redux-worker/blob/master/API.md) for you to register tasks to be executed in the web worker outside of Redux. 6 | 7 | Check out the demo: 8 | ![](http://g.recordit.co/dqxWhYDb1E.gif) 9 | *(Right) Performing synchronous calculations on a separate worker thread, without blocking the main thread.* 10 | 11 | 12 | ## Documentation 13 | See the [API docs](https://github.com/chikeichan/redux-worker/blob/master/API.md). 14 | 15 | ## Inspiration 16 | 17 | The [Web Workers API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) has been around for several years now, but we have yet to see much adoption from the community. I think this is mostly due to setup complexity, asynchronous message passing, and the need to think in a different context (one can't share functions/references between Worker threads). 18 | 19 | It used to be difficult to conceptualize how to harness the power of Web Workers in an MVC application due to the constraint above. However, Redux has changed the way we think about building applications in JavaScript. Its [three principles](https://github.com/reactjs/redux/blob/master/docs/introduction/ThreePrinciples.md) make it easy to move your app logic into a separate task. 20 | 21 | If you are not familiar with web workers, I'd recommend starting with the following materials. 22 | - [How fast are web workers?](https://hacks.mozilla.org/2015/07/how-fast-are-web-workers/) - by Guillaume Cedric Marty 23 | - [Using Web Workers for more responsive apps](https://www.youtube.com/watch?v=Kz_zKXiNGSE) - by Jason Teplitz 24 | 25 | ## Limitations 26 | 27 | `Redux-Worker` helps you use web workers in your Redux applications. It does so by enhancing your Redux store with: 28 | - A dispatcher that posts messages to a worker thread 29 | - A replacement reducer that listens to messages from a Web Worker and then updates state 30 | - A mechanism to register and execute tasks in a Web Worker using the dispatcher, which returns a promise that will resolve when the task completes. 31 | 32 | It spawns just one Web Worker instance, so don't give it more credit than it deserves. It does not provide an interface for spawning multiple workers and balancing work between them: this is a much more complicated problem that Redux-Worker is not attempting to solve. 33 | 34 | ## Demos 35 | - [Solver for N-Queens](http://chikeichan.github.io/redux-worker/demo/nqueen/index.html) 36 | 37 | To run a demo locally, cd into a folder in the demo directory, and then: 38 | ```bash 39 | npm i 40 | npm run watch 41 | ``` 42 | -------------------------------------------------------------------------------- /demo/nqueen/actions/nqueen/nqueen.js: -------------------------------------------------------------------------------- 1 | const calculateNQueen = (n) => { 2 | return function(dispatch) { 3 | dispatch({ 4 | type: 'START_NQUEEN' 5 | }); 6 | 7 | setTimeout(function() { 8 | dispatch({ 9 | type: 'CALCULATE_NQUEEN', 10 | number: n 11 | }); 12 | 13 | dispatch({ 14 | type: 'COMPLETE_NQUEEN' 15 | }); 16 | }, 200); 17 | 18 | } 19 | } 20 | 21 | const testWorker = (n) => { 22 | return { 23 | task: 'NQUEEN_TASK', 24 | number: n 25 | }; 26 | } 27 | 28 | export default { 29 | calculateNQueen, 30 | testWorker 31 | } -------------------------------------------------------------------------------- /demo/nqueen/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import NqueenContainer from '../containers/NqueenContainer' 3 | import Counter from '../components/Counter' 4 | import Blinker from '../components/Blinker' 5 | import Spinner from '../components/Spinner' 6 | import Slider from '../components/Slider' 7 | 8 | const App = () => ( 9 |
18 | 19 |
23 | 24 | 25 | 26 |
27 |
31 | 32 |
33 |
34 | ) 35 | 36 | export default App -------------------------------------------------------------------------------- /demo/nqueen/components/Blinker.js: -------------------------------------------------------------------------------- 1 | import React, { createClass } from 'react' 2 | 3 | export default createClass({ 4 | getInitialState() { 5 | return { 6 | blink: true 7 | }; 8 | }, 9 | 10 | componentDidMount() { 11 | setInterval(function() { 12 | this.setState({ 13 | blink: !this.state.blink 14 | }); 15 | }.bind(this), 500); 16 | }, 17 | 18 | render() { 19 | return ( 20 |
33 |

Blinker

34 |
35 | ) 36 | }, 37 | }); -------------------------------------------------------------------------------- /demo/nqueen/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { createClass } from 'react' 2 | 3 | export default createClass({ 4 | getInitialState() { 5 | return { 6 | counter: 0 7 | }; 8 | }, 9 | 10 | componentDidMount() { 11 | setInterval(function() { 12 | this.setState({ 13 | counter: this.state.counter + 1 14 | }); 15 | }.bind(this), 16); 16 | }, 17 | 18 | render() { 19 | return ( 20 |
32 |

Counter

33 |

{ this.state.counter }

34 |
35 | ) 36 | }, 37 | }); -------------------------------------------------------------------------------- /demo/nqueen/components/Nqueen.js: -------------------------------------------------------------------------------- 1 | import React, { createClass } from 'react' 2 | 3 | export default createClass({ 4 | getDefaultProps() { 5 | return { 6 | numberOfSquares: 1, 7 | answer: null, 8 | actions: { 9 | calculateNQueen: function() {} 10 | } 11 | }; 12 | }, 13 | 14 | getInitialState() { 15 | return { 16 | inputValue: 1 17 | }; 18 | }, 19 | 20 | toggleWebWorker() { 21 | var disableWebWorker = window.disableWebWorker; 22 | window.disableWebWorker = !disableWebWorker; 23 | this.forceUpdate(); 24 | }, 25 | 26 | onChange(e) { 27 | this.setState({ 28 | inputValue: e.target.value 29 | }); 30 | }, 31 | 32 | onClick() { 33 | if (this.props.isCalculating) { 34 | return; 35 | } 36 | this.props.actions.calculateNQueen(this.state.inputValue); 37 | }, 38 | 39 | runTask() { 40 | this.props.actions.testWorker(this.state.inputValue) 41 | .then(function(e) { 42 | alert( 43 | `This task is run directly on the web worker without going thru Redux.\nThe taskId is ${e._taskId}.\nThe answer is ${e.response}.` 44 | ); 45 | }); 46 | }, 47 | 48 | render() { 49 | const { numberOfSquares, answer, isCalculating } = this.props 50 | 51 | let ans, bgColor 52 | 53 | if (isCalculating) { 54 | ans = 'calculating...' 55 | bgColor = 'rgba(255, 0, 0, 0.2)' 56 | } else { 57 | ans = 'Answer is ' + answer; 58 | bgColor = 'rgba(0, 255, 0, 0.2)' 59 | } 60 | 61 | return ( 62 |
73 |

N-Queen Solver

74 |
87 |
Web Worker
88 |
{ window.disableWebWorker ? 'OFF' : 'ON'}
93 |
94 |

{ 'N = ' + numberOfSquares}

95 |
{ ans }
96 | 107 | 118 | 127 |
128 | ) 129 | } 130 | }) -------------------------------------------------------------------------------- /demo/nqueen/components/Slider.js: -------------------------------------------------------------------------------- 1 | import React, { createClass } from 'react' 2 | 3 | export default createClass({ 4 | getInitialState() { 5 | return { 6 | steps: 0 7 | }; 8 | }, 9 | 10 | componentDidMount() { 11 | var v = 5; 12 | setInterval(function() { 13 | if (this.state.steps > 460) { 14 | v = -5; 15 | } 16 | 17 | if (this.state.steps < 0) { 18 | v = 5; 19 | } 20 | 21 | this.setState({ 22 | steps: this.state.steps + v 23 | }); 24 | }.bind(this), 16); 25 | }, 26 | 27 | render() { 28 | return ( 29 |
42 |

Slider

43 |
44 | ) 45 | }, 46 | }); -------------------------------------------------------------------------------- /demo/nqueen/components/Spinner.js: -------------------------------------------------------------------------------- 1 | import React, { createClass } from 'react' 2 | 3 | export default createClass({ 4 | getInitialState() { 5 | return { 6 | degree: 0 7 | }; 8 | }, 9 | 10 | componentDidMount() { 11 | setInterval(function() { 12 | this.setState({ 13 | degree: this.state.degree + 5 14 | }); 15 | }.bind(this), 16); 16 | }, 17 | 18 | render() { 19 | return ( 20 |
33 |

Spinner

34 |
35 | ) 36 | }, 37 | }); -------------------------------------------------------------------------------- /demo/nqueen/containers/NqueenContainer.js: -------------------------------------------------------------------------------- 1 | import React, { createClass } from 'react' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators} from 'redux' 4 | import Nqueen from '../components/Nqueen' 5 | import actions from '../actions/nqueen/nqueen.js' 6 | 7 | const NqueenContainer = createClass({ 8 | render() { 9 | return 10 | } 11 | }) 12 | 13 | function mapStateToProps(state) { 14 | return { 15 | answer: state.nqueen.answer, 16 | numberOfSquares: state.nqueen.numberOfSquares, 17 | isCalculating: state.nqueen.isCalculating 18 | } 19 | } 20 | 21 | function mapDispatcherToProps(dispatch) { 22 | return { 23 | actions: bindActionCreators(actions, dispatch) 24 | } 25 | } 26 | 27 | export default connect( 28 | mapStateToProps, 29 | mapDispatcherToProps 30 | )(NqueenContainer); 31 | -------------------------------------------------------------------------------- /demo/nqueen/dist/worker.bundle.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) 10 | /******/ return installedModules[moduleId].exports; 11 | 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ exports: {}, 15 | /******/ id: moduleId, 16 | /******/ loaded: false 17 | /******/ }; 18 | 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | 22 | /******/ // Flag the module as loaded 23 | /******/ module.loaded = true; 24 | 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | 29 | 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | 36 | /******/ // __webpack_public_path__ 37 | /******/ __webpack_require__.p = ""; 38 | 39 | /******/ // Load entry module and return exports 40 | /******/ return __webpack_require__(0); 41 | /******/ }) 42 | /************************************************************************/ 43 | /******/ ({ 44 | 45 | /***/ 0: 46 | /***/ function(module, exports, __webpack_require__) { 47 | 48 | 'use strict'; 49 | 50 | var _reducers = __webpack_require__(178); 51 | 52 | var _reducers2 = _interopRequireDefault(_reducers); 53 | 54 | var _solver = __webpack_require__(180); 55 | 56 | var _solver2 = _interopRequireDefault(_solver); 57 | 58 | var _reduxWorker = __webpack_require__(189); 59 | 60 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 61 | 62 | var worker = (0, _reduxWorker.createWorker)(); 63 | 64 | worker.registerReducer(_reducers2.default); 65 | 66 | worker.registerTask('NQUEEN_TASK', function (a) { 67 | var n = a.number; 68 | return +n < 16 ? (0, _solver2.default)(+n).length : 'N is too large...'; 69 | }); 70 | 71 | /***/ }, 72 | 73 | /***/ 4: 74 | /***/ function(module, exports) { 75 | 76 | // shim for using process in browser 77 | 78 | var process = module.exports = {}; 79 | var queue = []; 80 | var draining = false; 81 | var currentQueue; 82 | var queueIndex = -1; 83 | 84 | function cleanUpNextTick() { 85 | draining = false; 86 | if (currentQueue.length) { 87 | queue = currentQueue.concat(queue); 88 | } else { 89 | queueIndex = -1; 90 | } 91 | if (queue.length) { 92 | drainQueue(); 93 | } 94 | } 95 | 96 | function drainQueue() { 97 | if (draining) { 98 | return; 99 | } 100 | var timeout = setTimeout(cleanUpNextTick); 101 | draining = true; 102 | 103 | var len = queue.length; 104 | while(len) { 105 | currentQueue = queue; 106 | queue = []; 107 | while (++queueIndex < len) { 108 | if (currentQueue) { 109 | currentQueue[queueIndex].run(); 110 | } 111 | } 112 | queueIndex = -1; 113 | len = queue.length; 114 | } 115 | currentQueue = null; 116 | draining = false; 117 | clearTimeout(timeout); 118 | } 119 | 120 | process.nextTick = function (fun) { 121 | var args = new Array(arguments.length - 1); 122 | if (arguments.length > 1) { 123 | for (var i = 1; i < arguments.length; i++) { 124 | args[i - 1] = arguments[i]; 125 | } 126 | } 127 | queue.push(new Item(fun, args)); 128 | if (queue.length === 1 && !draining) { 129 | setTimeout(drainQueue, 0); 130 | } 131 | }; 132 | 133 | // v8 likes predictible objects 134 | function Item(fun, array) { 135 | this.fun = fun; 136 | this.array = array; 137 | } 138 | Item.prototype.run = function () { 139 | this.fun.apply(null, this.array); 140 | }; 141 | process.title = 'browser'; 142 | process.browser = true; 143 | process.env = {}; 144 | process.argv = []; 145 | process.version = ''; // empty string to avoid regexp issues 146 | process.versions = {}; 147 | 148 | function noop() {} 149 | 150 | process.on = noop; 151 | process.addListener = noop; 152 | process.once = noop; 153 | process.off = noop; 154 | process.removeListener = noop; 155 | process.removeAllListeners = noop; 156 | process.emit = noop; 157 | 158 | process.binding = function (name) { 159 | throw new Error('process.binding is not supported'); 160 | }; 161 | 162 | process.cwd = function () { return '/' }; 163 | process.chdir = function (dir) { 164 | throw new Error('process.chdir is not supported'); 165 | }; 166 | process.umask = function() { return 0; }; 167 | 168 | 169 | /***/ }, 170 | 171 | /***/ 165: 172 | /***/ function(module, exports, __webpack_require__) { 173 | 174 | /* WEBPACK VAR INJECTION */(function(process) {'use strict'; 175 | 176 | exports.__esModule = true; 177 | exports.compose = exports.applyMiddleware = exports.bindActionCreators = exports.combineReducers = exports.createStore = undefined; 178 | 179 | var _createStore = __webpack_require__(166); 180 | 181 | var _createStore2 = _interopRequireDefault(_createStore); 182 | 183 | var _combineReducers = __webpack_require__(170); 184 | 185 | var _combineReducers2 = _interopRequireDefault(_combineReducers); 186 | 187 | var _bindActionCreators = __webpack_require__(172); 188 | 189 | var _bindActionCreators2 = _interopRequireDefault(_bindActionCreators); 190 | 191 | var _applyMiddleware = __webpack_require__(173); 192 | 193 | var _applyMiddleware2 = _interopRequireDefault(_applyMiddleware); 194 | 195 | var _compose = __webpack_require__(174); 196 | 197 | var _compose2 = _interopRequireDefault(_compose); 198 | 199 | var _warning = __webpack_require__(171); 200 | 201 | var _warning2 = _interopRequireDefault(_warning); 202 | 203 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 204 | 205 | /* 206 | * This is a dummy function to check if the function name has been altered by minification. 207 | * If the function has been minified and NODE_ENV !== 'production', warn the user. 208 | */ 209 | function isCrushed() {} 210 | 211 | if (process.env.NODE_ENV !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed') { 212 | (0, _warning2["default"])('You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.'); 213 | } 214 | 215 | exports.createStore = _createStore2["default"]; 216 | exports.combineReducers = _combineReducers2["default"]; 217 | exports.bindActionCreators = _bindActionCreators2["default"]; 218 | exports.applyMiddleware = _applyMiddleware2["default"]; 219 | exports.compose = _compose2["default"]; 220 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4))) 221 | 222 | /***/ }, 223 | 224 | /***/ 166: 225 | /***/ function(module, exports, __webpack_require__) { 226 | 227 | 'use strict'; 228 | 229 | exports.__esModule = true; 230 | exports.ActionTypes = undefined; 231 | exports["default"] = createStore; 232 | 233 | var _isPlainObject = __webpack_require__(167); 234 | 235 | var _isPlainObject2 = _interopRequireDefault(_isPlainObject); 236 | 237 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 238 | 239 | /** 240 | * These are private action types reserved by Redux. 241 | * For any unknown actions, you must return the current state. 242 | * If the current state is undefined, you must return the initial state. 243 | * Do not reference these action types directly in your code. 244 | */ 245 | var ActionTypes = exports.ActionTypes = { 246 | INIT: '@@redux/INIT' 247 | }; 248 | 249 | /** 250 | * Creates a Redux store that holds the state tree. 251 | * The only way to change the data in the store is to call `dispatch()` on it. 252 | * 253 | * There should only be a single store in your app. To specify how different 254 | * parts of the state tree respond to actions, you may combine several reducers 255 | * into a single reducer function by using `combineReducers`. 256 | * 257 | * @param {Function} reducer A function that returns the next state tree, given 258 | * the current state tree and the action to handle. 259 | * 260 | * @param {any} [initialState] The initial state. You may optionally specify it 261 | * to hydrate the state from the server in universal apps, or to restore a 262 | * previously serialized user session. 263 | * If you use `combineReducers` to produce the root reducer function, this must be 264 | * an object with the same shape as `combineReducers` keys. 265 | * 266 | * @param {Function} enhancer The store enhancer. You may optionally specify it 267 | * to enhance the store with third-party capabilities such as middleware, 268 | * time travel, persistence, etc. The only store enhancer that ships with Redux 269 | * is `applyMiddleware()`. 270 | * 271 | * @returns {Store} A Redux store that lets you read the state, dispatch actions 272 | * and subscribe to changes. 273 | */ 274 | function createStore(reducer, initialState, enhancer) { 275 | if (typeof initialState === 'function' && typeof enhancer === 'undefined') { 276 | enhancer = initialState; 277 | initialState = undefined; 278 | } 279 | 280 | if (typeof enhancer !== 'undefined') { 281 | if (typeof enhancer !== 'function') { 282 | throw new Error('Expected the enhancer to be a function.'); 283 | } 284 | 285 | return enhancer(createStore)(reducer, initialState); 286 | } 287 | 288 | if (typeof reducer !== 'function') { 289 | throw new Error('Expected the reducer to be a function.'); 290 | } 291 | 292 | var currentReducer = reducer; 293 | var currentState = initialState; 294 | var currentListeners = []; 295 | var nextListeners = currentListeners; 296 | var isDispatching = false; 297 | 298 | function ensureCanMutateNextListeners() { 299 | if (nextListeners === currentListeners) { 300 | nextListeners = currentListeners.slice(); 301 | } 302 | } 303 | 304 | /** 305 | * Reads the state tree managed by the store. 306 | * 307 | * @returns {any} The current state tree of your application. 308 | */ 309 | function getState() { 310 | return currentState; 311 | } 312 | 313 | /** 314 | * Adds a change listener. It will be called any time an action is dispatched, 315 | * and some part of the state tree may potentially have changed. You may then 316 | * call `getState()` to read the current state tree inside the callback. 317 | * 318 | * You may call `dispatch()` from a change listener, with the following 319 | * caveats: 320 | * 321 | * 1. The subscriptions are snapshotted just before every `dispatch()` call. 322 | * If you subscribe or unsubscribe while the listeners are being invoked, this 323 | * will not have any effect on the `dispatch()` that is currently in progress. 324 | * However, the next `dispatch()` call, whether nested or not, will use a more 325 | * recent snapshot of the subscription list. 326 | * 327 | * 2. The listener should not expect to see all states changes, as the state 328 | * might have been updated multiple times during a nested `dispatch()` before 329 | * the listener is called. It is, however, guaranteed that all subscribers 330 | * registered before the `dispatch()` started will be called with the latest 331 | * state by the time it exits. 332 | * 333 | * @param {Function} listener A callback to be invoked on every dispatch. 334 | * @returns {Function} A function to remove this change listener. 335 | */ 336 | function subscribe(listener) { 337 | if (typeof listener !== 'function') { 338 | throw new Error('Expected listener to be a function.'); 339 | } 340 | 341 | var isSubscribed = true; 342 | 343 | ensureCanMutateNextListeners(); 344 | nextListeners.push(listener); 345 | 346 | return function unsubscribe() { 347 | if (!isSubscribed) { 348 | return; 349 | } 350 | 351 | isSubscribed = false; 352 | 353 | ensureCanMutateNextListeners(); 354 | var index = nextListeners.indexOf(listener); 355 | nextListeners.splice(index, 1); 356 | }; 357 | } 358 | 359 | /** 360 | * Dispatches an action. It is the only way to trigger a state change. 361 | * 362 | * The `reducer` function, used to create the store, will be called with the 363 | * current state tree and the given `action`. Its return value will 364 | * be considered the **next** state of the tree, and the change listeners 365 | * will be notified. 366 | * 367 | * The base implementation only supports plain object actions. If you want to 368 | * dispatch a Promise, an Observable, a thunk, or something else, you need to 369 | * wrap your store creating function into the corresponding middleware. For 370 | * example, see the documentation for the `redux-thunk` package. Even the 371 | * middleware will eventually dispatch plain object actions using this method. 372 | * 373 | * @param {Object} action A plain object representing “what changed”. It is 374 | * a good idea to keep actions serializable so you can record and replay user 375 | * sessions, or use the time travelling `redux-devtools`. An action must have 376 | * a `type` property which may not be `undefined`. It is a good idea to use 377 | * string constants for action types. 378 | * 379 | * @returns {Object} For convenience, the same action object you dispatched. 380 | * 381 | * Note that, if you use a custom middleware, it may wrap `dispatch()` to 382 | * return something else (for example, a Promise you can await). 383 | */ 384 | function dispatch(action) { 385 | if (!(0, _isPlainObject2["default"])(action)) { 386 | throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.'); 387 | } 388 | 389 | if (typeof action.type === 'undefined') { 390 | throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?'); 391 | } 392 | 393 | if (isDispatching) { 394 | throw new Error('Reducers may not dispatch actions.'); 395 | } 396 | 397 | try { 398 | isDispatching = true; 399 | currentState = currentReducer(currentState, action); 400 | } finally { 401 | isDispatching = false; 402 | } 403 | 404 | var listeners = currentListeners = nextListeners; 405 | for (var i = 0; i < listeners.length; i++) { 406 | listeners[i](); 407 | } 408 | 409 | return action; 410 | } 411 | 412 | /** 413 | * Replaces the reducer currently used by the store to calculate the state. 414 | * 415 | * You might need this if your app implements code splitting and you want to 416 | * load some of the reducers dynamically. You might also need this if you 417 | * implement a hot reloading mechanism for Redux. 418 | * 419 | * @param {Function} nextReducer The reducer for the store to use instead. 420 | * @returns {void} 421 | */ 422 | function replaceReducer(nextReducer) { 423 | if (typeof nextReducer !== 'function') { 424 | throw new Error('Expected the nextReducer to be a function.'); 425 | } 426 | 427 | currentReducer = nextReducer; 428 | dispatch({ type: ActionTypes.INIT }); 429 | } 430 | 431 | // When a store is created, an "INIT" action is dispatched so that every 432 | // reducer returns their initial state. This effectively populates 433 | // the initial state tree. 434 | dispatch({ type: ActionTypes.INIT }); 435 | 436 | return { 437 | dispatch: dispatch, 438 | subscribe: subscribe, 439 | getState: getState, 440 | replaceReducer: replaceReducer 441 | }; 442 | } 443 | 444 | /***/ }, 445 | 446 | /***/ 167: 447 | /***/ function(module, exports, __webpack_require__) { 448 | 449 | var isHostObject = __webpack_require__(168), 450 | isObjectLike = __webpack_require__(169); 451 | 452 | /** `Object#toString` result references. */ 453 | var objectTag = '[object Object]'; 454 | 455 | /** Used for built-in method references. */ 456 | var objectProto = Object.prototype; 457 | 458 | /** Used to resolve the decompiled source of functions. */ 459 | var funcToString = Function.prototype.toString; 460 | 461 | /** Used to infer the `Object` constructor. */ 462 | var objectCtorString = funcToString.call(Object); 463 | 464 | /** 465 | * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) 466 | * of values. 467 | */ 468 | var objectToString = objectProto.toString; 469 | 470 | /** Built-in value references. */ 471 | var getPrototypeOf = Object.getPrototypeOf; 472 | 473 | /** 474 | * Checks if `value` is a plain object, that is, an object created by the 475 | * `Object` constructor or one with a `[[Prototype]]` of `null`. 476 | * 477 | * @static 478 | * @memberOf _ 479 | * @category Lang 480 | * @param {*} value The value to check. 481 | * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. 482 | * @example 483 | * 484 | * function Foo() { 485 | * this.a = 1; 486 | * } 487 | * 488 | * _.isPlainObject(new Foo); 489 | * // => false 490 | * 491 | * _.isPlainObject([1, 2, 3]); 492 | * // => false 493 | * 494 | * _.isPlainObject({ 'x': 0, 'y': 0 }); 495 | * // => true 496 | * 497 | * _.isPlainObject(Object.create(null)); 498 | * // => true 499 | */ 500 | function isPlainObject(value) { 501 | if (!isObjectLike(value) || 502 | objectToString.call(value) != objectTag || isHostObject(value)) { 503 | return false; 504 | } 505 | var proto = getPrototypeOf(value); 506 | if (proto === null) { 507 | return true; 508 | } 509 | var Ctor = proto.constructor; 510 | return (typeof Ctor == 'function' && 511 | Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString); 512 | } 513 | 514 | module.exports = isPlainObject; 515 | 516 | 517 | /***/ }, 518 | 519 | /***/ 168: 520 | /***/ function(module, exports) { 521 | 522 | /** 523 | * Checks if `value` is a host object in IE < 9. 524 | * 525 | * @private 526 | * @param {*} value The value to check. 527 | * @returns {boolean} Returns `true` if `value` is a host object, else `false`. 528 | */ 529 | function isHostObject(value) { 530 | // Many host objects are `Object` objects that can coerce to strings 531 | // despite having improperly defined `toString` methods. 532 | var result = false; 533 | if (value != null && typeof value.toString != 'function') { 534 | try { 535 | result = !!(value + ''); 536 | } catch (e) {} 537 | } 538 | return result; 539 | } 540 | 541 | module.exports = isHostObject; 542 | 543 | 544 | /***/ }, 545 | 546 | /***/ 169: 547 | /***/ function(module, exports) { 548 | 549 | /** 550 | * Checks if `value` is object-like. A value is object-like if it's not `null` 551 | * and has a `typeof` result of "object". 552 | * 553 | * @static 554 | * @memberOf _ 555 | * @category Lang 556 | * @param {*} value The value to check. 557 | * @returns {boolean} Returns `true` if `value` is object-like, else `false`. 558 | * @example 559 | * 560 | * _.isObjectLike({}); 561 | * // => true 562 | * 563 | * _.isObjectLike([1, 2, 3]); 564 | * // => true 565 | * 566 | * _.isObjectLike(_.noop); 567 | * // => false 568 | * 569 | * _.isObjectLike(null); 570 | * // => false 571 | */ 572 | function isObjectLike(value) { 573 | return !!value && typeof value == 'object'; 574 | } 575 | 576 | module.exports = isObjectLike; 577 | 578 | 579 | /***/ }, 580 | 581 | /***/ 170: 582 | /***/ function(module, exports, __webpack_require__) { 583 | 584 | /* WEBPACK VAR INJECTION */(function(process) {'use strict'; 585 | 586 | exports.__esModule = true; 587 | exports["default"] = combineReducers; 588 | 589 | var _createStore = __webpack_require__(166); 590 | 591 | var _isPlainObject = __webpack_require__(167); 592 | 593 | var _isPlainObject2 = _interopRequireDefault(_isPlainObject); 594 | 595 | var _warning = __webpack_require__(171); 596 | 597 | var _warning2 = _interopRequireDefault(_warning); 598 | 599 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 600 | 601 | function getUndefinedStateErrorMessage(key, action) { 602 | var actionType = action && action.type; 603 | var actionName = actionType && '"' + actionType.toString() + '"' || 'an action'; 604 | 605 | return 'Reducer "' + key + '" returned undefined handling ' + actionName + '. ' + 'To ignore an action, you must explicitly return the previous state.'; 606 | } 607 | 608 | function getUnexpectedStateShapeWarningMessage(inputState, reducers, action) { 609 | var reducerKeys = Object.keys(reducers); 610 | var argumentName = action && action.type === _createStore.ActionTypes.INIT ? 'initialState argument passed to createStore' : 'previous state received by the reducer'; 611 | 612 | if (reducerKeys.length === 0) { 613 | return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.'; 614 | } 615 | 616 | if (!(0, _isPlainObject2["default"])(inputState)) { 617 | return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"'); 618 | } 619 | 620 | var unexpectedKeys = Object.keys(inputState).filter(function (key) { 621 | return !reducers.hasOwnProperty(key); 622 | }); 623 | 624 | if (unexpectedKeys.length > 0) { 625 | return 'Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + ' ' + ('"' + unexpectedKeys.join('", "') + '" found in ' + argumentName + '. ') + 'Expected to find one of the known reducer keys instead: ' + ('"' + reducerKeys.join('", "') + '". Unexpected keys will be ignored.'); 626 | } 627 | } 628 | 629 | function assertReducerSanity(reducers) { 630 | Object.keys(reducers).forEach(function (key) { 631 | var reducer = reducers[key]; 632 | var initialState = reducer(undefined, { type: _createStore.ActionTypes.INIT }); 633 | 634 | if (typeof initialState === 'undefined') { 635 | throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined.'); 636 | } 637 | 638 | var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.'); 639 | if (typeof reducer(undefined, { type: type }) === 'undefined') { 640 | throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + _createStore.ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined.'); 641 | } 642 | }); 643 | } 644 | 645 | /** 646 | * Turns an object whose values are different reducer functions, into a single 647 | * reducer function. It will call every child reducer, and gather their results 648 | * into a single state object, whose keys correspond to the keys of the passed 649 | * reducer functions. 650 | * 651 | * @param {Object} reducers An object whose values correspond to different 652 | * reducer functions that need to be combined into one. One handy way to obtain 653 | * it is to use ES6 `import * as reducers` syntax. The reducers may never return 654 | * undefined for any action. Instead, they should return their initial state 655 | * if the state passed to them was undefined, and the current state for any 656 | * unrecognized action. 657 | * 658 | * @returns {Function} A reducer function that invokes every reducer inside the 659 | * passed object, and builds a state object with the same shape. 660 | */ 661 | function combineReducers(reducers) { 662 | var reducerKeys = Object.keys(reducers); 663 | var finalReducers = {}; 664 | for (var i = 0; i < reducerKeys.length; i++) { 665 | var key = reducerKeys[i]; 666 | if (typeof reducers[key] === 'function') { 667 | finalReducers[key] = reducers[key]; 668 | } 669 | } 670 | var finalReducerKeys = Object.keys(finalReducers); 671 | 672 | var sanityError; 673 | try { 674 | assertReducerSanity(finalReducers); 675 | } catch (e) { 676 | sanityError = e; 677 | } 678 | 679 | return function combination() { 680 | var state = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 681 | var action = arguments[1]; 682 | 683 | if (sanityError) { 684 | throw sanityError; 685 | } 686 | 687 | if (process.env.NODE_ENV !== 'production') { 688 | var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action); 689 | if (warningMessage) { 690 | (0, _warning2["default"])(warningMessage); 691 | } 692 | } 693 | 694 | var hasChanged = false; 695 | var nextState = {}; 696 | for (var i = 0; i < finalReducerKeys.length; i++) { 697 | var key = finalReducerKeys[i]; 698 | var reducer = finalReducers[key]; 699 | var previousStateForKey = state[key]; 700 | var nextStateForKey = reducer(previousStateForKey, action); 701 | if (typeof nextStateForKey === 'undefined') { 702 | var errorMessage = getUndefinedStateErrorMessage(key, action); 703 | throw new Error(errorMessage); 704 | } 705 | nextState[key] = nextStateForKey; 706 | hasChanged = hasChanged || nextStateForKey !== previousStateForKey; 707 | } 708 | return hasChanged ? nextState : state; 709 | }; 710 | } 711 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4))) 712 | 713 | /***/ }, 714 | 715 | /***/ 171: 716 | /***/ function(module, exports) { 717 | 718 | 'use strict'; 719 | 720 | exports.__esModule = true; 721 | exports["default"] = warning; 722 | /** 723 | * Prints a warning in the console if it exists. 724 | * 725 | * @param {String} message The warning message. 726 | * @returns {void} 727 | */ 728 | function warning(message) { 729 | /* eslint-disable no-console */ 730 | if (typeof console !== 'undefined' && typeof console.error === 'function') { 731 | console.error(message); 732 | } 733 | /* eslint-enable no-console */ 734 | try { 735 | // This error was thrown as a convenience so that you can use this stack 736 | // to find the callsite that caused this warning to fire. 737 | throw new Error(message); 738 | /* eslint-disable no-empty */ 739 | } catch (e) {} 740 | /* eslint-enable no-empty */ 741 | } 742 | 743 | /***/ }, 744 | 745 | /***/ 172: 746 | /***/ function(module, exports) { 747 | 748 | 'use strict'; 749 | 750 | exports.__esModule = true; 751 | exports["default"] = bindActionCreators; 752 | function bindActionCreator(actionCreator, dispatch) { 753 | return function () { 754 | return dispatch(actionCreator.apply(undefined, arguments)); 755 | }; 756 | } 757 | 758 | /** 759 | * Turns an object whose values are action creators, into an object with the 760 | * same keys, but with every function wrapped into a `dispatch` call so they 761 | * may be invoked directly. This is just a convenience method, as you can call 762 | * `store.dispatch(MyActionCreators.doSomething())` yourself just fine. 763 | * 764 | * For convenience, you can also pass a single function as the first argument, 765 | * and get a function in return. 766 | * 767 | * @param {Function|Object} actionCreators An object whose values are action 768 | * creator functions. One handy way to obtain it is to use ES6 `import * as` 769 | * syntax. You may also pass a single function. 770 | * 771 | * @param {Function} dispatch The `dispatch` function available on your Redux 772 | * store. 773 | * 774 | * @returns {Function|Object} The object mimicking the original object, but with 775 | * every action creator wrapped into the `dispatch` call. If you passed a 776 | * function as `actionCreators`, the return value will also be a single 777 | * function. 778 | */ 779 | function bindActionCreators(actionCreators, dispatch) { 780 | if (typeof actionCreators === 'function') { 781 | return bindActionCreator(actionCreators, dispatch); 782 | } 783 | 784 | if (typeof actionCreators !== 'object' || actionCreators === null) { 785 | throw new Error('bindActionCreators expected an object or a function, instead received ' + (actionCreators === null ? 'null' : typeof actionCreators) + '. ' + 'Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?'); 786 | } 787 | 788 | var keys = Object.keys(actionCreators); 789 | var boundActionCreators = {}; 790 | for (var i = 0; i < keys.length; i++) { 791 | var key = keys[i]; 792 | var actionCreator = actionCreators[key]; 793 | if (typeof actionCreator === 'function') { 794 | boundActionCreators[key] = bindActionCreator(actionCreator, dispatch); 795 | } 796 | } 797 | return boundActionCreators; 798 | } 799 | 800 | /***/ }, 801 | 802 | /***/ 173: 803 | /***/ function(module, exports, __webpack_require__) { 804 | 805 | 'use strict'; 806 | 807 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 808 | 809 | exports.__esModule = true; 810 | exports["default"] = applyMiddleware; 811 | 812 | var _compose = __webpack_require__(174); 813 | 814 | var _compose2 = _interopRequireDefault(_compose); 815 | 816 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 817 | 818 | /** 819 | * Creates a store enhancer that applies middleware to the dispatch method 820 | * of the Redux store. This is handy for a variety of tasks, such as expressing 821 | * asynchronous actions in a concise manner, or logging every action payload. 822 | * 823 | * See `redux-thunk` package as an example of the Redux middleware. 824 | * 825 | * Because middleware is potentially asynchronous, this should be the first 826 | * store enhancer in the composition chain. 827 | * 828 | * Note that each middleware will be given the `dispatch` and `getState` functions 829 | * as named arguments. 830 | * 831 | * @param {...Function} middlewares The middleware chain to be applied. 832 | * @returns {Function} A store enhancer applying the middleware. 833 | */ 834 | function applyMiddleware() { 835 | for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) { 836 | middlewares[_key] = arguments[_key]; 837 | } 838 | 839 | return function (createStore) { 840 | return function (reducer, initialState, enhancer) { 841 | var store = createStore(reducer, initialState, enhancer); 842 | var _dispatch = store.dispatch; 843 | var chain = []; 844 | 845 | var middlewareAPI = { 846 | getState: store.getState, 847 | dispatch: function dispatch(action) { 848 | return _dispatch(action); 849 | } 850 | }; 851 | chain = middlewares.map(function (middleware) { 852 | return middleware(middlewareAPI); 853 | }); 854 | _dispatch = _compose2["default"].apply(undefined, chain)(store.dispatch); 855 | 856 | return _extends({}, store, { 857 | dispatch: _dispatch 858 | }); 859 | }; 860 | }; 861 | } 862 | 863 | /***/ }, 864 | 865 | /***/ 174: 866 | /***/ function(module, exports) { 867 | 868 | "use strict"; 869 | 870 | exports.__esModule = true; 871 | exports["default"] = compose; 872 | /** 873 | * Composes single-argument functions from right to left. 874 | * 875 | * @param {...Function} funcs The functions to compose. 876 | * @returns {Function} A function obtained by composing functions from right to 877 | * left. For example, compose(f, g, h) is identical to arg => f(g(h(arg))). 878 | */ 879 | function compose() { 880 | for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) { 881 | funcs[_key] = arguments[_key]; 882 | } 883 | 884 | return function () { 885 | if (funcs.length === 0) { 886 | return arguments.length <= 0 ? undefined : arguments[0]; 887 | } 888 | 889 | var last = funcs[funcs.length - 1]; 890 | var rest = funcs.slice(0, -1); 891 | 892 | return rest.reduceRight(function (composed, f) { 893 | return f(composed); 894 | }, last.apply(undefined, arguments)); 895 | }; 896 | } 897 | 898 | /***/ }, 899 | 900 | /***/ 178: 901 | /***/ function(module, exports, __webpack_require__) { 902 | 903 | 'use strict'; 904 | 905 | Object.defineProperty(exports, "__esModule", { 906 | value: true 907 | }); 908 | 909 | var _redux = __webpack_require__(165); 910 | 911 | var _nqueen = __webpack_require__(179); 912 | 913 | var _nqueen2 = _interopRequireDefault(_nqueen); 914 | 915 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 916 | 917 | var nqueenApp = (0, _redux.combineReducers)({ 918 | nqueen: _nqueen2.default 919 | }); 920 | 921 | exports.default = nqueenApp; 922 | 923 | /***/ }, 924 | 925 | /***/ 179: 926 | /***/ function(module, exports, __webpack_require__) { 927 | 928 | 'use strict'; 929 | 930 | Object.defineProperty(exports, "__esModule", { 931 | value: true 932 | }); 933 | 934 | var _solver = __webpack_require__(180); 935 | 936 | var _solver2 = _interopRequireDefault(_solver); 937 | 938 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 939 | 940 | var intState = { 941 | isCalculating: false, 942 | numberOfSquares: 1, 943 | answer: null 944 | }; 945 | 946 | var startNQueen = function startNQueen(state) { 947 | return Object.assign({}, state, { 948 | isCalculating: true, 949 | answer: null 950 | }); 951 | }; 952 | 953 | var calculateNQueen = function calculateNQueen(state, n) { 954 | return { 955 | numberOfSquares: n, 956 | isCalculating: true, 957 | answer: +n < 16 ? (0, _solver2.default)(+n).length : 'N is too large...' 958 | }; 959 | }; 960 | 961 | var completeNQueen = function completeNQueen(state) { 962 | return Object.assign({}, state, { 963 | isCalculating: false 964 | }); 965 | }; 966 | 967 | exports.default = function () { 968 | var state = arguments.length <= 0 || arguments[0] === undefined ? intState : arguments[0]; 969 | var action = arguments[1]; 970 | 971 | switch (action.type) { 972 | case 'START_NQUEEN': 973 | return startNQueen(state); 974 | case 'CALCULATE_NQUEEN': 975 | return calculateNQueen(state, action.number); 976 | case 'COMPLETE_NQUEEN': 977 | return completeNQueen(state); 978 | default: 979 | return state; 980 | } 981 | }; 982 | 983 | /***/ }, 984 | 985 | /***/ 180: 986 | /***/ function(module, exports) { 987 | 988 | "use strict"; 989 | 990 | Object.defineProperty(exports, "__esModule", { 991 | value: true 992 | }); 993 | 994 | exports.default = function (n, z) { 995 | var sol = []; 996 | 997 | var _solve = function _solve(board) { 998 | var nx = board.length; 999 | 1000 | if (board.length === n) { 1001 | sol.push(board); 1002 | return; 1003 | } 1004 | 1005 | for (var i = 0; i < n; i++) { 1006 | var legal = true; 1007 | for (var j = 0; j < board.length; j++) { 1008 | var ox = j; 1009 | var oy = board[j]; 1010 | var slope = Math.abs((nx - ox) / (i - oy)); 1011 | if (i === oy || slope === 1) { 1012 | legal = false; 1013 | break; 1014 | } 1015 | } 1016 | if (legal) { 1017 | board.push(i); 1018 | _solve(board); 1019 | board.pop(); 1020 | } 1021 | } 1022 | }; 1023 | 1024 | if (z !== undefined) { 1025 | _solve([z]); 1026 | } else { 1027 | _solve([]); 1028 | } 1029 | 1030 | return sol; 1031 | }; 1032 | 1033 | /***/ }, 1034 | 1035 | /***/ 189: 1036 | /***/ function(module, exports, __webpack_require__) { 1037 | 1038 | var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module) {'use strict'; 1039 | 1040 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 1041 | 1042 | (function webpackUniversalModuleDefinition(root, factory) { 1043 | if (( false ? 'undefined' : _typeof(exports)) === 'object' && ( false ? 'undefined' : _typeof(module)) === 'object') module.exports = factory();else if (true) !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));else if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') exports["ReduxWorker"] = factory();else root["ReduxWorker"] = factory(); 1044 | })(undefined, function () { 1045 | return (/******/function (modules) { 1046 | // webpackBootstrap 1047 | /******/ // The module cache 1048 | /******/var installedModules = {}; 1049 | 1050 | /******/ // The require function 1051 | /******/function __webpack_require__(moduleId) { 1052 | 1053 | /******/ // Check if module is in cache 1054 | /******/if (installedModules[moduleId]) 1055 | /******/return installedModules[moduleId].exports; 1056 | 1057 | /******/ // Create a new module (and put it into the cache) 1058 | /******/var module = installedModules[moduleId] = { 1059 | /******/exports: {}, 1060 | /******/id: moduleId, 1061 | /******/loaded: false 1062 | /******/ }; 1063 | 1064 | /******/ // Execute the module function 1065 | /******/modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 1066 | 1067 | /******/ // Flag the module as loaded 1068 | /******/module.loaded = true; 1069 | 1070 | /******/ // Return the exports of the module 1071 | /******/return module.exports; 1072 | /******/ 1073 | } 1074 | 1075 | /******/ // expose the modules object (__webpack_modules__) 1076 | /******/__webpack_require__.m = modules; 1077 | 1078 | /******/ // expose the module cache 1079 | /******/__webpack_require__.c = installedModules; 1080 | 1081 | /******/ // __webpack_public_path__ 1082 | /******/__webpack_require__.p = ""; 1083 | 1084 | /******/ // Load entry module and return exports 1085 | /******/return __webpack_require__(0); 1086 | /******/ 1087 | }( 1088 | /************************************************************************/ 1089 | /******/[ 1090 | /* 0 */ 1091 | /***/function (module, exports, __webpack_require__) { 1092 | 1093 | 'use strict'; 1094 | 1095 | Object.defineProperty(exports, "__esModule", { 1096 | value: true 1097 | }); 1098 | exports.createWorker = exports.applyWorker = undefined; 1099 | 1100 | var _createWorker = __webpack_require__(1); 1101 | 1102 | var _createWorker2 = _interopRequireDefault(_createWorker); 1103 | 1104 | var _applyWorker = __webpack_require__(2); 1105 | 1106 | var _applyWorker2 = _interopRequireDefault(_applyWorker); 1107 | 1108 | function _interopRequireDefault(obj) { 1109 | return obj && obj.__esModule ? obj : { default: obj }; 1110 | } 1111 | 1112 | exports.applyWorker = _applyWorker2.default; 1113 | exports.createWorker = _createWorker2.default; 1114 | exports.default = { applyWorker: _applyWorker2.default, createWorker: _createWorker2.default }; 1115 | 1116 | /***/ 1117 | }, 1118 | /* 1 */ 1119 | /***/function (module, exports) { 1120 | 1121 | 'use strict'; 1122 | 1123 | Object.defineProperty(exports, "__esModule", { 1124 | value: true 1125 | }); 1126 | 1127 | var _createClass = function () { 1128 | function defineProperties(target, props) { 1129 | for (var i = 0; i < props.length; i++) { 1130 | var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor); 1131 | } 1132 | }return function (Constructor, protoProps, staticProps) { 1133 | if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor; 1134 | }; 1135 | }(); 1136 | 1137 | function _classCallCheck(instance, Constructor) { 1138 | if (!(instance instanceof Constructor)) { 1139 | throw new TypeError("Cannot call a class as a function"); 1140 | } 1141 | } 1142 | 1143 | var createWorker = function createWorker(reducer) { 1144 | // Initialize ReduxWorekr 1145 | var worker = new ReduxWorker(); 1146 | 1147 | self.addEventListener('message', function (e) { 1148 | var action = e.data; 1149 | 1150 | if (typeof action.type === 'string') { 1151 | if (!worker.reducer || typeof worker.reducer !== 'function') { 1152 | throw new Error('Expect reducer to be function. Have you registerReducer yet?'); 1153 | } 1154 | 1155 | // Set new state 1156 | var state = worker.state; 1157 | state = worker.state = worker.reducer(state, action); 1158 | state = worker.transform(state); 1159 | 1160 | // Send new state to main thread 1161 | self.postMessage({ 1162 | type: action.type, 1163 | state: state, 1164 | action: action 1165 | }); 1166 | 1167 | return; 1168 | } 1169 | 1170 | if (typeof action.task === 'string' && typeof action._taskId === 'number') { 1171 | var taskRunner = worker.tasks[action.task]; 1172 | 1173 | if (!taskRunner || typeof taskRunner !== 'function') { 1174 | throw new Error('Cannot find runner for task ' + action.task + '. Have you registerTask yet?'); 1175 | } 1176 | 1177 | // Send new state to main thread 1178 | self.postMessage({ 1179 | _taskId: action._taskId, 1180 | response: taskRunner(action) 1181 | }); 1182 | } 1183 | }); 1184 | 1185 | return worker; 1186 | }; 1187 | 1188 | var ReduxWorker = function () { 1189 | function ReduxWorker() { 1190 | _classCallCheck(this, ReduxWorker); 1191 | 1192 | // Taskrunners 1193 | this.tasks = {}; 1194 | 1195 | // Redux-specific variables 1196 | this.state = {}; 1197 | this.reducer = null; 1198 | this.transform = function (state) { 1199 | return state; 1200 | }; 1201 | } 1202 | 1203 | _createClass(ReduxWorker, [{ 1204 | key: 'registerReducer', 1205 | value: function registerReducer(reducer, transform) { 1206 | this.reducer = reducer; 1207 | this.state = reducer({}, {}); 1208 | } 1209 | }, { 1210 | key: 'registerTask', 1211 | value: function registerTask(name, taskFn) { 1212 | this.tasks[name] = taskFn; 1213 | } 1214 | }]); 1215 | 1216 | return ReduxWorker; 1217 | }(); 1218 | 1219 | exports.default = createWorker; 1220 | 1221 | /***/ 1222 | }, 1223 | /* 2 */ 1224 | /***/function (module, exports) { 1225 | 1226 | 'use strict'; 1227 | 1228 | Object.defineProperty(exports, "__esModule", { 1229 | value: true 1230 | }); 1231 | var defer = function defer() { 1232 | var result = {}; 1233 | result.promise = new Promise(function (resolve, reject) { 1234 | result.resolve = resolve; 1235 | result.reject = reject; 1236 | }); 1237 | return result; 1238 | }; 1239 | 1240 | var applyWorker = function applyWorker(worker) { 1241 | return function (createStore) { 1242 | return function (reducer, initialState, enhancer) { 1243 | if (!(worker instanceof Worker)) { 1244 | console.error('Expect input to be a Web Worker. Fall back to normal store.'); 1245 | return createStore(reducer, initialState, enhancer); 1246 | } 1247 | 1248 | // New reducer for workified store 1249 | var replacementReducer = function replacementReducer(state, action) { 1250 | if (action.state) { 1251 | return action.state; 1252 | } 1253 | return state; 1254 | }; 1255 | 1256 | // Start task id; 1257 | var taskId = 0; 1258 | var taskCompleteCallbacks = {}; 1259 | 1260 | // Create store using new reducer 1261 | var store = createStore(replacementReducer, reducer({}, {}), enhancer); 1262 | 1263 | // Store reference of old dispatcher 1264 | var next = store.dispatch; 1265 | 1266 | // Replace dispatcher 1267 | store.dispatch = function (action) { 1268 | if (typeof action.type === 'string') { 1269 | if (window.disableWebWorker) { 1270 | return next({ 1271 | type: action.type, 1272 | state: reducer(store.getState(), action) 1273 | }); 1274 | } 1275 | worker.postMessage(action); 1276 | } 1277 | 1278 | if (typeof action.task === 'string') { 1279 | var task = Object.assign({}, action, { _taskId: taskId }); 1280 | var deferred = defer(); 1281 | 1282 | taskCompleteCallbacks[taskId] = deferred; 1283 | taskId++; 1284 | worker.postMessage(task); 1285 | return deferred.promise; 1286 | } 1287 | }; 1288 | 1289 | store.isWorker = true; 1290 | 1291 | // Add worker events listener 1292 | worker.addEventListener('message', function (e) { 1293 | var action = e.data; 1294 | if (typeof action.type === 'string') { 1295 | next(action); 1296 | } 1297 | 1298 | if (typeof action._taskId === 'number') { 1299 | var wrapped = taskCompleteCallbacks[action._taskId]; 1300 | 1301 | if (wrapped) { 1302 | wrapped.resolve(action); 1303 | delete taskCompleteCallbacks[action._taskId]; 1304 | } 1305 | } 1306 | }); 1307 | 1308 | return store; 1309 | }; 1310 | }; 1311 | }; 1312 | 1313 | exports.default = applyWorker; 1314 | 1315 | /***/ 1316 | } 1317 | /******/]) 1318 | ); 1319 | }); 1320 | ; 1321 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(190)(module))) 1322 | 1323 | /***/ }, 1324 | 1325 | /***/ 190: 1326 | /***/ function(module, exports) { 1327 | 1328 | module.exports = function(module) { 1329 | if(!module.webpackPolyfill) { 1330 | module.deprecate = function() {}; 1331 | module.paths = []; 1332 | // module.parent = undefined by default 1333 | module.children = []; 1334 | module.webpackPolyfill = 1; 1335 | } 1336 | return module; 1337 | } 1338 | 1339 | 1340 | /***/ } 1341 | 1342 | /******/ }); -------------------------------------------------------------------------------- /demo/nqueen/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redux Worker 5 | 26 | 27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /demo/nqueen/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { Provider } from 'react-redux' 4 | import { createStore, applyMiddleware, compose } from 'redux' 5 | import thunk from 'redux-thunk' 6 | import rootReducer from './reducers' 7 | import App from './components/App' 8 | import { applyWorker } from 'redux-worker' 9 | import createLogger from 'redux-logger'; 10 | 11 | const logger = createLogger(); 12 | 13 | const worker = new Worker('./dist/worker.bundle.js'); 14 | 15 | const enhancerWithWorker = compose( 16 | // Middleware you want to use in development: 17 | applyMiddleware(thunk, logger), 18 | applyWorker(worker) 19 | ); 20 | 21 | const store = createStore(rootReducer, {}, enhancerWithWorker); 22 | 23 | render( 24 | 25 | 26 | , 27 | document.getElementById('app') 28 | ) 29 | -------------------------------------------------------------------------------- /demo/nqueen/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-worker-demo-nqueen", 3 | "version": "1.0.0", 4 | "description": "Demo for Redux-Worker", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "watch": "./node_modules/.bin/webpack-dev-server" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "babel-polyfill": "^6.6.1", 14 | "babel-runtime": "^6.6.1", 15 | "react": "^0.14.7", 16 | "react-dom": "^0.14.7", 17 | "react-redux": "^4.4.0", 18 | "redux": "^3.3.1", 19 | "redux-logger": "^2.6.1", 20 | "redux-thunk": "^1.0.3", 21 | "redux-worker": "^0.1.0" 22 | }, 23 | "devDependencies": { 24 | "babel-core": "^6.6.4", 25 | "babel-loader": "^6.2.4", 26 | "babel-preset-es2015": "6.3.13", 27 | "babel-preset-react": "^6.5.0", 28 | "webpack": "^1.12.14", 29 | "webpack-dev-server": "^1.14.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /demo/nqueen/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import nqueen from './nqueen/nqueen' 3 | 4 | const nqueenApp = combineReducers({ 5 | nqueen 6 | }); 7 | 8 | export default nqueenApp -------------------------------------------------------------------------------- /demo/nqueen/reducers/nqueen/nqueen.js: -------------------------------------------------------------------------------- 1 | import solve from '../../solver' 2 | 3 | const intState = { 4 | isCalculating: false, 5 | numberOfSquares: 1, 6 | answer: null 7 | } 8 | 9 | const startNQueen = (state) => { 10 | return Object.assign({}, state, { 11 | isCalculating: true, 12 | answer: null 13 | }); 14 | } 15 | 16 | const calculateNQueen = (state, n) => { 17 | return { 18 | numberOfSquares: n, 19 | isCalculating: true, 20 | answer: +n < 16 ? solve(+n).length : 'N is too large...' 21 | }; 22 | } 23 | 24 | const completeNQueen = (state) => { 25 | return Object.assign({}, state, { 26 | isCalculating: false 27 | }); 28 | } 29 | 30 | export default (state = intState, action) => { 31 | switch (action.type) { 32 | case 'START_NQUEEN': 33 | return startNQueen(state); 34 | case 'CALCULATE_NQUEEN': 35 | return calculateNQueen(state, action.number); 36 | case 'COMPLETE_NQUEEN': 37 | return completeNQueen(state); 38 | default: 39 | return state; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /demo/nqueen/reducers/worker.js: -------------------------------------------------------------------------------- 1 | import reducer from '../reducers' 2 | import solve from '../solver' 3 | import { createWorker } from 'redux-worker' 4 | 5 | let worker = createWorker(); 6 | 7 | worker.registerReducer(reducer); 8 | 9 | worker.registerTask('NQUEEN_TASK', function(a) { 10 | let n = a.number; 11 | return +n < 16 ? solve(+n).length : 'N is too large...'; 12 | }); -------------------------------------------------------------------------------- /demo/nqueen/solver.js: -------------------------------------------------------------------------------- 1 | export default (n,z) => { 2 | var sol = []; 3 | 4 | var _solve = function(board){ 5 | var nx = board.length; 6 | 7 | if (board.length === n){ 8 | sol.push(board); 9 | return; 10 | } 11 | 12 | for (var i = 0; i 22 | 23 | 24 | 25 | ); 26 | } 27 | } 28 | 29 | export default App; -------------------------------------------------------------------------------- /demo/sortTable/components/Generator.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | 3 | class Generator extends Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | testWorker() { 9 | this.props.actions.testWorker(10000) 10 | .then(console.log.bind(console)) 11 | } 12 | 13 | render() { 14 | return ( 15 |
16 |
Sort Table
17 |
22 | 23 | 24 | 25 | 26 |
27 |
28 | ); 29 | } 30 | } 31 | 32 | export default Generator; -------------------------------------------------------------------------------- /demo/sortTable/components/Spinner.js: -------------------------------------------------------------------------------- 1 | import React, { createClass } from 'react' 2 | 3 | export default createClass({ 4 | getInitialState() { 5 | return { 6 | degree: 0 7 | }; 8 | }, 9 | 10 | componentDidMount() { 11 | this.interval = setInterval(function() { 12 | this.setState({ 13 | degree: this.state.degree + 5 14 | }); 15 | }.bind(this), 16); 16 | }, 17 | 18 | componentWillUnmount() { 19 | const interval = this.interval; 20 | if (interval) { 21 | clearInterval(interval); 22 | } 23 | }, 24 | 25 | render() { 26 | return ( 27 |
28 |
43 |
44 | 45 |
60 |
61 | 62 |
77 |
78 |
93 |
94 |
109 |
110 |
111 | 112 | ) 113 | }, 114 | }); -------------------------------------------------------------------------------- /demo/sortTable/components/UserTable.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | import LoadingSpinner from 'react-spinner'; 3 | import Infinite from 'react-infinite'; 4 | 5 | class Table extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | isFirstNameReverse: false, 10 | isLastNameReverse: false, 11 | isDoBReverse: false, 12 | isSorting: false 13 | } 14 | } 15 | 16 | makeOneRow(user, i) { 17 | return ( 18 |
23 |
{user.firstName}
24 |
{user.lastName}
25 |
{user.dateOfBirth.slice(4, 15)}
26 |
27 | ) 28 | } 29 | 30 | componentWillReceiveProps() { 31 | this.setState({ isSorting: false }); 32 | } 33 | 34 | sortFirstName() { 35 | let {isFirstNameReverse} = this.state; 36 | 37 | this.setState({ 38 | isFirstNameReverse: !isFirstNameReverse, 39 | isSorting: true 40 | }); 41 | 42 | setTimeout(() => { 43 | this.props.onClickFirstName(isFirstNameReverse); 44 | }, 200); 45 | } 46 | 47 | sortLastName() { 48 | let {isLastNameReverse} = this.state; 49 | 50 | this.setState({ 51 | isLastNameReverse: !isLastNameReverse, 52 | isSorting: true 53 | }); 54 | 55 | setTimeout(() => { 56 | this.props.onClickLastName(isLastNameReverse); 57 | }, 200); 58 | } 59 | 60 | sortDoB() { 61 | let {isDoBReverse} = this.state; 62 | 63 | this.setState({ 64 | isDoBReverse: !isDoBReverse, 65 | isSorting: true 66 | }); 67 | 68 | setTimeout(() => { 69 | this.props.onClickDoB(isDoBReverse); 70 | }, 200); 71 | } 72 | 73 | render() { 74 | const { 75 | users = [] 76 | } = this.props; 77 | 78 | const { 79 | isSorting 80 | } = this.state; 81 | 82 | return ( 83 |
91 | {isSorting ? : null} 92 |
98 |
100 | First Name 101 |
102 |
104 | Last Name 105 |
106 |
108 | Date of Birth 109 |
110 |
111 |
113 | {users.map(this.makeOneRow)} 114 |
115 |
116 | ); 117 | } 118 | } 119 | 120 | export default Table; -------------------------------------------------------------------------------- /demo/sortTable/containers/GeneratorContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { bindActionCreators } from 'redux' 3 | import { connect } from 'react-redux' 4 | import Generator from '../components/Generator' 5 | import * as UserActions from '../actions/users/users' 6 | 7 | class GeneratorContainer extends Component { 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | render() { 13 | return ( 14 | 18 | ) 19 | } 20 | } 21 | 22 | function mapStateToProps(state) { 23 | return { 24 | users: state.users 25 | } 26 | } 27 | 28 | function mapDispatchToProps(dispatch) { 29 | return { 30 | actions: bindActionCreators(UserActions, dispatch) 31 | } 32 | } 33 | 34 | export default connect( 35 | mapStateToProps, 36 | mapDispatchToProps 37 | )(GeneratorContainer) -------------------------------------------------------------------------------- /demo/sortTable/containers/UserTableContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { bindActionCreators } from 'redux' 3 | import { connect } from 'react-redux' 4 | import UserTable from '../components/UserTable' 5 | import * as UserActions from '../actions/users/users' 6 | 7 | class Table extends Component { 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | render() { 13 | const { users, actions } = this.props; 14 | const { sortFirstName, sortLastName, sortDateOfBirth } = actions; 15 | return ( 16 | 22 | ) 23 | } 24 | } 25 | 26 | function mapStateToProps(state) { 27 | return { 28 | users: state.users 29 | } 30 | } 31 | 32 | function mapDispatchToProps(dispatch) { 33 | return { 34 | actions: bindActionCreators(UserActions, dispatch) 35 | } 36 | } 37 | 38 | export default connect( 39 | mapStateToProps, 40 | mapDispatchToProps 41 | )(Table) -------------------------------------------------------------------------------- /demo/sortTable/dist/worker.bundle.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) 10 | /******/ return installedModules[moduleId].exports; 11 | 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ exports: {}, 15 | /******/ id: moduleId, 16 | /******/ loaded: false 17 | /******/ }; 18 | 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | 22 | /******/ // Flag the module as loaded 23 | /******/ module.loaded = true; 24 | 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | 29 | 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | 36 | /******/ // __webpack_public_path__ 37 | /******/ __webpack_require__.p = ""; 38 | 39 | /******/ // Load entry module and return exports 40 | /******/ return __webpack_require__(0); 41 | /******/ }) 42 | /************************************************************************/ 43 | /******/ ({ 44 | 45 | /***/ 0: 46 | /***/ function(module, exports, __webpack_require__) { 47 | 48 | 'use strict'; 49 | 50 | var _reducers = __webpack_require__(178); 51 | 52 | var _reducers2 = _interopRequireDefault(_reducers); 53 | 54 | var _solver = __webpack_require__(180); 55 | 56 | var _solver2 = _interopRequireDefault(_solver); 57 | 58 | var _reduxWorker = __webpack_require__(189); 59 | 60 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 61 | 62 | var worker = (0, _reduxWorker.createWorker)(); 63 | 64 | worker.registerReducer(_reducers2.default); 65 | 66 | worker.registerTask('NQUEEN_TASK', function (a) { 67 | var n = a.number; 68 | return +n < 16 ? (0, _solver2.default)(+n).length : 'N is too large...'; 69 | }); 70 | 71 | /***/ }, 72 | 73 | /***/ 4: 74 | /***/ function(module, exports) { 75 | 76 | // shim for using process in browser 77 | 78 | var process = module.exports = {}; 79 | var queue = []; 80 | var draining = false; 81 | var currentQueue; 82 | var queueIndex = -1; 83 | 84 | function cleanUpNextTick() { 85 | draining = false; 86 | if (currentQueue.length) { 87 | queue = currentQueue.concat(queue); 88 | } else { 89 | queueIndex = -1; 90 | } 91 | if (queue.length) { 92 | drainQueue(); 93 | } 94 | } 95 | 96 | function drainQueue() { 97 | if (draining) { 98 | return; 99 | } 100 | var timeout = setTimeout(cleanUpNextTick); 101 | draining = true; 102 | 103 | var len = queue.length; 104 | while(len) { 105 | currentQueue = queue; 106 | queue = []; 107 | while (++queueIndex < len) { 108 | if (currentQueue) { 109 | currentQueue[queueIndex].run(); 110 | } 111 | } 112 | queueIndex = -1; 113 | len = queue.length; 114 | } 115 | currentQueue = null; 116 | draining = false; 117 | clearTimeout(timeout); 118 | } 119 | 120 | process.nextTick = function (fun) { 121 | var args = new Array(arguments.length - 1); 122 | if (arguments.length > 1) { 123 | for (var i = 1; i < arguments.length; i++) { 124 | args[i - 1] = arguments[i]; 125 | } 126 | } 127 | queue.push(new Item(fun, args)); 128 | if (queue.length === 1 && !draining) { 129 | setTimeout(drainQueue, 0); 130 | } 131 | }; 132 | 133 | // v8 likes predictible objects 134 | function Item(fun, array) { 135 | this.fun = fun; 136 | this.array = array; 137 | } 138 | Item.prototype.run = function () { 139 | this.fun.apply(null, this.array); 140 | }; 141 | process.title = 'browser'; 142 | process.browser = true; 143 | process.env = {}; 144 | process.argv = []; 145 | process.version = ''; // empty string to avoid regexp issues 146 | process.versions = {}; 147 | 148 | function noop() {} 149 | 150 | process.on = noop; 151 | process.addListener = noop; 152 | process.once = noop; 153 | process.off = noop; 154 | process.removeListener = noop; 155 | process.removeAllListeners = noop; 156 | process.emit = noop; 157 | 158 | process.binding = function (name) { 159 | throw new Error('process.binding is not supported'); 160 | }; 161 | 162 | process.cwd = function () { return '/' }; 163 | process.chdir = function (dir) { 164 | throw new Error('process.chdir is not supported'); 165 | }; 166 | process.umask = function() { return 0; }; 167 | 168 | 169 | /***/ }, 170 | 171 | /***/ 165: 172 | /***/ function(module, exports, __webpack_require__) { 173 | 174 | /* WEBPACK VAR INJECTION */(function(process) {'use strict'; 175 | 176 | exports.__esModule = true; 177 | exports.compose = exports.applyMiddleware = exports.bindActionCreators = exports.combineReducers = exports.createStore = undefined; 178 | 179 | var _createStore = __webpack_require__(166); 180 | 181 | var _createStore2 = _interopRequireDefault(_createStore); 182 | 183 | var _combineReducers = __webpack_require__(170); 184 | 185 | var _combineReducers2 = _interopRequireDefault(_combineReducers); 186 | 187 | var _bindActionCreators = __webpack_require__(172); 188 | 189 | var _bindActionCreators2 = _interopRequireDefault(_bindActionCreators); 190 | 191 | var _applyMiddleware = __webpack_require__(173); 192 | 193 | var _applyMiddleware2 = _interopRequireDefault(_applyMiddleware); 194 | 195 | var _compose = __webpack_require__(174); 196 | 197 | var _compose2 = _interopRequireDefault(_compose); 198 | 199 | var _warning = __webpack_require__(171); 200 | 201 | var _warning2 = _interopRequireDefault(_warning); 202 | 203 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 204 | 205 | /* 206 | * This is a dummy function to check if the function name has been altered by minification. 207 | * If the function has been minified and NODE_ENV !== 'production', warn the user. 208 | */ 209 | function isCrushed() {} 210 | 211 | if (process.env.NODE_ENV !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed') { 212 | (0, _warning2["default"])('You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.'); 213 | } 214 | 215 | exports.createStore = _createStore2["default"]; 216 | exports.combineReducers = _combineReducers2["default"]; 217 | exports.bindActionCreators = _bindActionCreators2["default"]; 218 | exports.applyMiddleware = _applyMiddleware2["default"]; 219 | exports.compose = _compose2["default"]; 220 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4))) 221 | 222 | /***/ }, 223 | 224 | /***/ 166: 225 | /***/ function(module, exports, __webpack_require__) { 226 | 227 | 'use strict'; 228 | 229 | exports.__esModule = true; 230 | exports.ActionTypes = undefined; 231 | exports["default"] = createStore; 232 | 233 | var _isPlainObject = __webpack_require__(167); 234 | 235 | var _isPlainObject2 = _interopRequireDefault(_isPlainObject); 236 | 237 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 238 | 239 | /** 240 | * These are private action types reserved by Redux. 241 | * For any unknown actions, you must return the current state. 242 | * If the current state is undefined, you must return the initial state. 243 | * Do not reference these action types directly in your code. 244 | */ 245 | var ActionTypes = exports.ActionTypes = { 246 | INIT: '@@redux/INIT' 247 | }; 248 | 249 | /** 250 | * Creates a Redux store that holds the state tree. 251 | * The only way to change the data in the store is to call `dispatch()` on it. 252 | * 253 | * There should only be a single store in your app. To specify how different 254 | * parts of the state tree respond to actions, you may combine several reducers 255 | * into a single reducer function by using `combineReducers`. 256 | * 257 | * @param {Function} reducer A function that returns the next state tree, given 258 | * the current state tree and the action to handle. 259 | * 260 | * @param {any} [initialState] The initial state. You may optionally specify it 261 | * to hydrate the state from the server in universal apps, or to restore a 262 | * previously serialized user session. 263 | * If you use `combineReducers` to produce the root reducer function, this must be 264 | * an object with the same shape as `combineReducers` keys. 265 | * 266 | * @param {Function} enhancer The store enhancer. You may optionally specify it 267 | * to enhance the store with third-party capabilities such as middleware, 268 | * time travel, persistence, etc. The only store enhancer that ships with Redux 269 | * is `applyMiddleware()`. 270 | * 271 | * @returns {Store} A Redux store that lets you read the state, dispatch actions 272 | * and subscribe to changes. 273 | */ 274 | function createStore(reducer, initialState, enhancer) { 275 | if (typeof initialState === 'function' && typeof enhancer === 'undefined') { 276 | enhancer = initialState; 277 | initialState = undefined; 278 | } 279 | 280 | if (typeof enhancer !== 'undefined') { 281 | if (typeof enhancer !== 'function') { 282 | throw new Error('Expected the enhancer to be a function.'); 283 | } 284 | 285 | return enhancer(createStore)(reducer, initialState); 286 | } 287 | 288 | if (typeof reducer !== 'function') { 289 | throw new Error('Expected the reducer to be a function.'); 290 | } 291 | 292 | var currentReducer = reducer; 293 | var currentState = initialState; 294 | var currentListeners = []; 295 | var nextListeners = currentListeners; 296 | var isDispatching = false; 297 | 298 | function ensureCanMutateNextListeners() { 299 | if (nextListeners === currentListeners) { 300 | nextListeners = currentListeners.slice(); 301 | } 302 | } 303 | 304 | /** 305 | * Reads the state tree managed by the store. 306 | * 307 | * @returns {any} The current state tree of your application. 308 | */ 309 | function getState() { 310 | return currentState; 311 | } 312 | 313 | /** 314 | * Adds a change listener. It will be called any time an action is dispatched, 315 | * and some part of the state tree may potentially have changed. You may then 316 | * call `getState()` to read the current state tree inside the callback. 317 | * 318 | * You may call `dispatch()` from a change listener, with the following 319 | * caveats: 320 | * 321 | * 1. The subscriptions are snapshotted just before every `dispatch()` call. 322 | * If you subscribe or unsubscribe while the listeners are being invoked, this 323 | * will not have any effect on the `dispatch()` that is currently in progress. 324 | * However, the next `dispatch()` call, whether nested or not, will use a more 325 | * recent snapshot of the subscription list. 326 | * 327 | * 2. The listener should not expect to see all states changes, as the state 328 | * might have been updated multiple times during a nested `dispatch()` before 329 | * the listener is called. It is, however, guaranteed that all subscribers 330 | * registered before the `dispatch()` started will be called with the latest 331 | * state by the time it exits. 332 | * 333 | * @param {Function} listener A callback to be invoked on every dispatch. 334 | * @returns {Function} A function to remove this change listener. 335 | */ 336 | function subscribe(listener) { 337 | if (typeof listener !== 'function') { 338 | throw new Error('Expected listener to be a function.'); 339 | } 340 | 341 | var isSubscribed = true; 342 | 343 | ensureCanMutateNextListeners(); 344 | nextListeners.push(listener); 345 | 346 | return function unsubscribe() { 347 | if (!isSubscribed) { 348 | return; 349 | } 350 | 351 | isSubscribed = false; 352 | 353 | ensureCanMutateNextListeners(); 354 | var index = nextListeners.indexOf(listener); 355 | nextListeners.splice(index, 1); 356 | }; 357 | } 358 | 359 | /** 360 | * Dispatches an action. It is the only way to trigger a state change. 361 | * 362 | * The `reducer` function, used to create the store, will be called with the 363 | * current state tree and the given `action`. Its return value will 364 | * be considered the **next** state of the tree, and the change listeners 365 | * will be notified. 366 | * 367 | * The base implementation only supports plain object actions. If you want to 368 | * dispatch a Promise, an Observable, a thunk, or something else, you need to 369 | * wrap your store creating function into the corresponding middleware. For 370 | * example, see the documentation for the `redux-thunk` package. Even the 371 | * middleware will eventually dispatch plain object actions using this method. 372 | * 373 | * @param {Object} action A plain object representing “what changed”. It is 374 | * a good idea to keep actions serializable so you can record and replay user 375 | * sessions, or use the time travelling `redux-devtools`. An action must have 376 | * a `type` property which may not be `undefined`. It is a good idea to use 377 | * string constants for action types. 378 | * 379 | * @returns {Object} For convenience, the same action object you dispatched. 380 | * 381 | * Note that, if you use a custom middleware, it may wrap `dispatch()` to 382 | * return something else (for example, a Promise you can await). 383 | */ 384 | function dispatch(action) { 385 | if (!(0, _isPlainObject2["default"])(action)) { 386 | throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.'); 387 | } 388 | 389 | if (typeof action.type === 'undefined') { 390 | throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?'); 391 | } 392 | 393 | if (isDispatching) { 394 | throw new Error('Reducers may not dispatch actions.'); 395 | } 396 | 397 | try { 398 | isDispatching = true; 399 | currentState = currentReducer(currentState, action); 400 | } finally { 401 | isDispatching = false; 402 | } 403 | 404 | var listeners = currentListeners = nextListeners; 405 | for (var i = 0; i < listeners.length; i++) { 406 | listeners[i](); 407 | } 408 | 409 | return action; 410 | } 411 | 412 | /** 413 | * Replaces the reducer currently used by the store to calculate the state. 414 | * 415 | * You might need this if your app implements code splitting and you want to 416 | * load some of the reducers dynamically. You might also need this if you 417 | * implement a hot reloading mechanism for Redux. 418 | * 419 | * @param {Function} nextReducer The reducer for the store to use instead. 420 | * @returns {void} 421 | */ 422 | function replaceReducer(nextReducer) { 423 | if (typeof nextReducer !== 'function') { 424 | throw new Error('Expected the nextReducer to be a function.'); 425 | } 426 | 427 | currentReducer = nextReducer; 428 | dispatch({ type: ActionTypes.INIT }); 429 | } 430 | 431 | // When a store is created, an "INIT" action is dispatched so that every 432 | // reducer returns their initial state. This effectively populates 433 | // the initial state tree. 434 | dispatch({ type: ActionTypes.INIT }); 435 | 436 | return { 437 | dispatch: dispatch, 438 | subscribe: subscribe, 439 | getState: getState, 440 | replaceReducer: replaceReducer 441 | }; 442 | } 443 | 444 | /***/ }, 445 | 446 | /***/ 167: 447 | /***/ function(module, exports, __webpack_require__) { 448 | 449 | var isHostObject = __webpack_require__(168), 450 | isObjectLike = __webpack_require__(169); 451 | 452 | /** `Object#toString` result references. */ 453 | var objectTag = '[object Object]'; 454 | 455 | /** Used for built-in method references. */ 456 | var objectProto = Object.prototype; 457 | 458 | /** Used to resolve the decompiled source of functions. */ 459 | var funcToString = Function.prototype.toString; 460 | 461 | /** Used to infer the `Object` constructor. */ 462 | var objectCtorString = funcToString.call(Object); 463 | 464 | /** 465 | * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) 466 | * of values. 467 | */ 468 | var objectToString = objectProto.toString; 469 | 470 | /** Built-in value references. */ 471 | var getPrototypeOf = Object.getPrototypeOf; 472 | 473 | /** 474 | * Checks if `value` is a plain object, that is, an object created by the 475 | * `Object` constructor or one with a `[[Prototype]]` of `null`. 476 | * 477 | * @static 478 | * @memberOf _ 479 | * @category Lang 480 | * @param {*} value The value to check. 481 | * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. 482 | * @example 483 | * 484 | * function Foo() { 485 | * this.a = 1; 486 | * } 487 | * 488 | * _.isPlainObject(new Foo); 489 | * // => false 490 | * 491 | * _.isPlainObject([1, 2, 3]); 492 | * // => false 493 | * 494 | * _.isPlainObject({ 'x': 0, 'y': 0 }); 495 | * // => true 496 | * 497 | * _.isPlainObject(Object.create(null)); 498 | * // => true 499 | */ 500 | function isPlainObject(value) { 501 | if (!isObjectLike(value) || 502 | objectToString.call(value) != objectTag || isHostObject(value)) { 503 | return false; 504 | } 505 | var proto = getPrototypeOf(value); 506 | if (proto === null) { 507 | return true; 508 | } 509 | var Ctor = proto.constructor; 510 | return (typeof Ctor == 'function' && 511 | Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString); 512 | } 513 | 514 | module.exports = isPlainObject; 515 | 516 | 517 | /***/ }, 518 | 519 | /***/ 168: 520 | /***/ function(module, exports) { 521 | 522 | /** 523 | * Checks if `value` is a host object in IE < 9. 524 | * 525 | * @private 526 | * @param {*} value The value to check. 527 | * @returns {boolean} Returns `true` if `value` is a host object, else `false`. 528 | */ 529 | function isHostObject(value) { 530 | // Many host objects are `Object` objects that can coerce to strings 531 | // despite having improperly defined `toString` methods. 532 | var result = false; 533 | if (value != null && typeof value.toString != 'function') { 534 | try { 535 | result = !!(value + ''); 536 | } catch (e) {} 537 | } 538 | return result; 539 | } 540 | 541 | module.exports = isHostObject; 542 | 543 | 544 | /***/ }, 545 | 546 | /***/ 169: 547 | /***/ function(module, exports) { 548 | 549 | /** 550 | * Checks if `value` is object-like. A value is object-like if it's not `null` 551 | * and has a `typeof` result of "object". 552 | * 553 | * @static 554 | * @memberOf _ 555 | * @category Lang 556 | * @param {*} value The value to check. 557 | * @returns {boolean} Returns `true` if `value` is object-like, else `false`. 558 | * @example 559 | * 560 | * _.isObjectLike({}); 561 | * // => true 562 | * 563 | * _.isObjectLike([1, 2, 3]); 564 | * // => true 565 | * 566 | * _.isObjectLike(_.noop); 567 | * // => false 568 | * 569 | * _.isObjectLike(null); 570 | * // => false 571 | */ 572 | function isObjectLike(value) { 573 | return !!value && typeof value == 'object'; 574 | } 575 | 576 | module.exports = isObjectLike; 577 | 578 | 579 | /***/ }, 580 | 581 | /***/ 170: 582 | /***/ function(module, exports, __webpack_require__) { 583 | 584 | /* WEBPACK VAR INJECTION */(function(process) {'use strict'; 585 | 586 | exports.__esModule = true; 587 | exports["default"] = combineReducers; 588 | 589 | var _createStore = __webpack_require__(166); 590 | 591 | var _isPlainObject = __webpack_require__(167); 592 | 593 | var _isPlainObject2 = _interopRequireDefault(_isPlainObject); 594 | 595 | var _warning = __webpack_require__(171); 596 | 597 | var _warning2 = _interopRequireDefault(_warning); 598 | 599 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 600 | 601 | function getUndefinedStateErrorMessage(key, action) { 602 | var actionType = action && action.type; 603 | var actionName = actionType && '"' + actionType.toString() + '"' || 'an action'; 604 | 605 | return 'Reducer "' + key + '" returned undefined handling ' + actionName + '. ' + 'To ignore an action, you must explicitly return the previous state.'; 606 | } 607 | 608 | function getUnexpectedStateShapeWarningMessage(inputState, reducers, action) { 609 | var reducerKeys = Object.keys(reducers); 610 | var argumentName = action && action.type === _createStore.ActionTypes.INIT ? 'initialState argument passed to createStore' : 'previous state received by the reducer'; 611 | 612 | if (reducerKeys.length === 0) { 613 | return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.'; 614 | } 615 | 616 | if (!(0, _isPlainObject2["default"])(inputState)) { 617 | return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"'); 618 | } 619 | 620 | var unexpectedKeys = Object.keys(inputState).filter(function (key) { 621 | return !reducers.hasOwnProperty(key); 622 | }); 623 | 624 | if (unexpectedKeys.length > 0) { 625 | return 'Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + ' ' + ('"' + unexpectedKeys.join('", "') + '" found in ' + argumentName + '. ') + 'Expected to find one of the known reducer keys instead: ' + ('"' + reducerKeys.join('", "') + '". Unexpected keys will be ignored.'); 626 | } 627 | } 628 | 629 | function assertReducerSanity(reducers) { 630 | Object.keys(reducers).forEach(function (key) { 631 | var reducer = reducers[key]; 632 | var initialState = reducer(undefined, { type: _createStore.ActionTypes.INIT }); 633 | 634 | if (typeof initialState === 'undefined') { 635 | throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined.'); 636 | } 637 | 638 | var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.'); 639 | if (typeof reducer(undefined, { type: type }) === 'undefined') { 640 | throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + _createStore.ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined.'); 641 | } 642 | }); 643 | } 644 | 645 | /** 646 | * Turns an object whose values are different reducer functions, into a single 647 | * reducer function. It will call every child reducer, and gather their results 648 | * into a single state object, whose keys correspond to the keys of the passed 649 | * reducer functions. 650 | * 651 | * @param {Object} reducers An object whose values correspond to different 652 | * reducer functions that need to be combined into one. One handy way to obtain 653 | * it is to use ES6 `import * as reducers` syntax. The reducers may never return 654 | * undefined for any action. Instead, they should return their initial state 655 | * if the state passed to them was undefined, and the current state for any 656 | * unrecognized action. 657 | * 658 | * @returns {Function} A reducer function that invokes every reducer inside the 659 | * passed object, and builds a state object with the same shape. 660 | */ 661 | function combineReducers(reducers) { 662 | var reducerKeys = Object.keys(reducers); 663 | var finalReducers = {}; 664 | for (var i = 0; i < reducerKeys.length; i++) { 665 | var key = reducerKeys[i]; 666 | if (typeof reducers[key] === 'function') { 667 | finalReducers[key] = reducers[key]; 668 | } 669 | } 670 | var finalReducerKeys = Object.keys(finalReducers); 671 | 672 | var sanityError; 673 | try { 674 | assertReducerSanity(finalReducers); 675 | } catch (e) { 676 | sanityError = e; 677 | } 678 | 679 | return function combination() { 680 | var state = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 681 | var action = arguments[1]; 682 | 683 | if (sanityError) { 684 | throw sanityError; 685 | } 686 | 687 | if (process.env.NODE_ENV !== 'production') { 688 | var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action); 689 | if (warningMessage) { 690 | (0, _warning2["default"])(warningMessage); 691 | } 692 | } 693 | 694 | var hasChanged = false; 695 | var nextState = {}; 696 | for (var i = 0; i < finalReducerKeys.length; i++) { 697 | var key = finalReducerKeys[i]; 698 | var reducer = finalReducers[key]; 699 | var previousStateForKey = state[key]; 700 | var nextStateForKey = reducer(previousStateForKey, action); 701 | if (typeof nextStateForKey === 'undefined') { 702 | var errorMessage = getUndefinedStateErrorMessage(key, action); 703 | throw new Error(errorMessage); 704 | } 705 | nextState[key] = nextStateForKey; 706 | hasChanged = hasChanged || nextStateForKey !== previousStateForKey; 707 | } 708 | return hasChanged ? nextState : state; 709 | }; 710 | } 711 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(4))) 712 | 713 | /***/ }, 714 | 715 | /***/ 171: 716 | /***/ function(module, exports) { 717 | 718 | 'use strict'; 719 | 720 | exports.__esModule = true; 721 | exports["default"] = warning; 722 | /** 723 | * Prints a warning in the console if it exists. 724 | * 725 | * @param {String} message The warning message. 726 | * @returns {void} 727 | */ 728 | function warning(message) { 729 | /* eslint-disable no-console */ 730 | if (typeof console !== 'undefined' && typeof console.error === 'function') { 731 | console.error(message); 732 | } 733 | /* eslint-enable no-console */ 734 | try { 735 | // This error was thrown as a convenience so that you can use this stack 736 | // to find the callsite that caused this warning to fire. 737 | throw new Error(message); 738 | /* eslint-disable no-empty */ 739 | } catch (e) {} 740 | /* eslint-enable no-empty */ 741 | } 742 | 743 | /***/ }, 744 | 745 | /***/ 172: 746 | /***/ function(module, exports) { 747 | 748 | 'use strict'; 749 | 750 | exports.__esModule = true; 751 | exports["default"] = bindActionCreators; 752 | function bindActionCreator(actionCreator, dispatch) { 753 | return function () { 754 | return dispatch(actionCreator.apply(undefined, arguments)); 755 | }; 756 | } 757 | 758 | /** 759 | * Turns an object whose values are action creators, into an object with the 760 | * same keys, but with every function wrapped into a `dispatch` call so they 761 | * may be invoked directly. This is just a convenience method, as you can call 762 | * `store.dispatch(MyActionCreators.doSomething())` yourself just fine. 763 | * 764 | * For convenience, you can also pass a single function as the first argument, 765 | * and get a function in return. 766 | * 767 | * @param {Function|Object} actionCreators An object whose values are action 768 | * creator functions. One handy way to obtain it is to use ES6 `import * as` 769 | * syntax. You may also pass a single function. 770 | * 771 | * @param {Function} dispatch The `dispatch` function available on your Redux 772 | * store. 773 | * 774 | * @returns {Function|Object} The object mimicking the original object, but with 775 | * every action creator wrapped into the `dispatch` call. If you passed a 776 | * function as `actionCreators`, the return value will also be a single 777 | * function. 778 | */ 779 | function bindActionCreators(actionCreators, dispatch) { 780 | if (typeof actionCreators === 'function') { 781 | return bindActionCreator(actionCreators, dispatch); 782 | } 783 | 784 | if (typeof actionCreators !== 'object' || actionCreators === null) { 785 | throw new Error('bindActionCreators expected an object or a function, instead received ' + (actionCreators === null ? 'null' : typeof actionCreators) + '. ' + 'Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?'); 786 | } 787 | 788 | var keys = Object.keys(actionCreators); 789 | var boundActionCreators = {}; 790 | for (var i = 0; i < keys.length; i++) { 791 | var key = keys[i]; 792 | var actionCreator = actionCreators[key]; 793 | if (typeof actionCreator === 'function') { 794 | boundActionCreators[key] = bindActionCreator(actionCreator, dispatch); 795 | } 796 | } 797 | return boundActionCreators; 798 | } 799 | 800 | /***/ }, 801 | 802 | /***/ 173: 803 | /***/ function(module, exports, __webpack_require__) { 804 | 805 | 'use strict'; 806 | 807 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 808 | 809 | exports.__esModule = true; 810 | exports["default"] = applyMiddleware; 811 | 812 | var _compose = __webpack_require__(174); 813 | 814 | var _compose2 = _interopRequireDefault(_compose); 815 | 816 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 817 | 818 | /** 819 | * Creates a store enhancer that applies middleware to the dispatch method 820 | * of the Redux store. This is handy for a variety of tasks, such as expressing 821 | * asynchronous actions in a concise manner, or logging every action payload. 822 | * 823 | * See `redux-thunk` package as an example of the Redux middleware. 824 | * 825 | * Because middleware is potentially asynchronous, this should be the first 826 | * store enhancer in the composition chain. 827 | * 828 | * Note that each middleware will be given the `dispatch` and `getState` functions 829 | * as named arguments. 830 | * 831 | * @param {...Function} middlewares The middleware chain to be applied. 832 | * @returns {Function} A store enhancer applying the middleware. 833 | */ 834 | function applyMiddleware() { 835 | for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) { 836 | middlewares[_key] = arguments[_key]; 837 | } 838 | 839 | return function (createStore) { 840 | return function (reducer, initialState, enhancer) { 841 | var store = createStore(reducer, initialState, enhancer); 842 | var _dispatch = store.dispatch; 843 | var chain = []; 844 | 845 | var middlewareAPI = { 846 | getState: store.getState, 847 | dispatch: function dispatch(action) { 848 | return _dispatch(action); 849 | } 850 | }; 851 | chain = middlewares.map(function (middleware) { 852 | return middleware(middlewareAPI); 853 | }); 854 | _dispatch = _compose2["default"].apply(undefined, chain)(store.dispatch); 855 | 856 | return _extends({}, store, { 857 | dispatch: _dispatch 858 | }); 859 | }; 860 | }; 861 | } 862 | 863 | /***/ }, 864 | 865 | /***/ 174: 866 | /***/ function(module, exports) { 867 | 868 | "use strict"; 869 | 870 | exports.__esModule = true; 871 | exports["default"] = compose; 872 | /** 873 | * Composes single-argument functions from right to left. 874 | * 875 | * @param {...Function} funcs The functions to compose. 876 | * @returns {Function} A function obtained by composing functions from right to 877 | * left. For example, compose(f, g, h) is identical to arg => f(g(h(arg))). 878 | */ 879 | function compose() { 880 | for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) { 881 | funcs[_key] = arguments[_key]; 882 | } 883 | 884 | return function () { 885 | if (funcs.length === 0) { 886 | return arguments.length <= 0 ? undefined : arguments[0]; 887 | } 888 | 889 | var last = funcs[funcs.length - 1]; 890 | var rest = funcs.slice(0, -1); 891 | 892 | return rest.reduceRight(function (composed, f) { 893 | return f(composed); 894 | }, last.apply(undefined, arguments)); 895 | }; 896 | } 897 | 898 | /***/ }, 899 | 900 | /***/ 178: 901 | /***/ function(module, exports, __webpack_require__) { 902 | 903 | 'use strict'; 904 | 905 | Object.defineProperty(exports, "__esModule", { 906 | value: true 907 | }); 908 | 909 | var _redux = __webpack_require__(165); 910 | 911 | var _nqueen = __webpack_require__(179); 912 | 913 | var _nqueen2 = _interopRequireDefault(_nqueen); 914 | 915 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 916 | 917 | var nqueenApp = (0, _redux.combineReducers)({ 918 | nqueen: _nqueen2.default 919 | }); 920 | 921 | exports.default = nqueenApp; 922 | 923 | /***/ }, 924 | 925 | /***/ 179: 926 | /***/ function(module, exports, __webpack_require__) { 927 | 928 | 'use strict'; 929 | 930 | Object.defineProperty(exports, "__esModule", { 931 | value: true 932 | }); 933 | 934 | var _solver = __webpack_require__(180); 935 | 936 | var _solver2 = _interopRequireDefault(_solver); 937 | 938 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 939 | 940 | var intState = { 941 | isCalculating: false, 942 | numberOfSquares: 1, 943 | answer: null 944 | }; 945 | 946 | var startNQueen = function startNQueen(state) { 947 | return Object.assign({}, state, { 948 | isCalculating: true, 949 | answer: null 950 | }); 951 | }; 952 | 953 | var calculateNQueen = function calculateNQueen(state, n) { 954 | return { 955 | numberOfSquares: n, 956 | isCalculating: true, 957 | answer: +n < 16 ? (0, _solver2.default)(+n).length : 'N is too large...' 958 | }; 959 | }; 960 | 961 | var completeNQueen = function completeNQueen(state) { 962 | return Object.assign({}, state, { 963 | isCalculating: false 964 | }); 965 | }; 966 | 967 | exports.default = function () { 968 | var state = arguments.length <= 0 || arguments[0] === undefined ? intState : arguments[0]; 969 | var action = arguments[1]; 970 | 971 | switch (action.type) { 972 | case 'START_NQUEEN': 973 | return startNQueen(state); 974 | case 'CALCULATE_NQUEEN': 975 | return calculateNQueen(state, action.number); 976 | case 'COMPLETE_NQUEEN': 977 | return completeNQueen(state); 978 | default: 979 | return state; 980 | } 981 | }; 982 | 983 | /***/ }, 984 | 985 | /***/ 180: 986 | /***/ function(module, exports) { 987 | 988 | "use strict"; 989 | 990 | Object.defineProperty(exports, "__esModule", { 991 | value: true 992 | }); 993 | 994 | exports.default = function (n, z) { 995 | var sol = []; 996 | 997 | var _solve = function _solve(board) { 998 | var nx = board.length; 999 | 1000 | if (board.length === n) { 1001 | sol.push(board); 1002 | return; 1003 | } 1004 | 1005 | for (var i = 0; i < n; i++) { 1006 | var legal = true; 1007 | for (var j = 0; j < board.length; j++) { 1008 | var ox = j; 1009 | var oy = board[j]; 1010 | var slope = Math.abs((nx - ox) / (i - oy)); 1011 | if (i === oy || slope === 1) { 1012 | legal = false; 1013 | break; 1014 | } 1015 | } 1016 | if (legal) { 1017 | board.push(i); 1018 | _solve(board); 1019 | board.pop(); 1020 | } 1021 | } 1022 | }; 1023 | 1024 | if (z !== undefined) { 1025 | _solve([z]); 1026 | } else { 1027 | _solve([]); 1028 | } 1029 | 1030 | return sol; 1031 | }; 1032 | 1033 | /***/ }, 1034 | 1035 | /***/ 189: 1036 | /***/ function(module, exports, __webpack_require__) { 1037 | 1038 | var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module) {'use strict'; 1039 | 1040 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 1041 | 1042 | (function webpackUniversalModuleDefinition(root, factory) { 1043 | if (( false ? 'undefined' : _typeof(exports)) === 'object' && ( false ? 'undefined' : _typeof(module)) === 'object') module.exports = factory();else if (true) !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));else if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') exports["ReduxWorker"] = factory();else root["ReduxWorker"] = factory(); 1044 | })(undefined, function () { 1045 | return (/******/function (modules) { 1046 | // webpackBootstrap 1047 | /******/ // The module cache 1048 | /******/var installedModules = {}; 1049 | 1050 | /******/ // The require function 1051 | /******/function __webpack_require__(moduleId) { 1052 | 1053 | /******/ // Check if module is in cache 1054 | /******/if (installedModules[moduleId]) 1055 | /******/return installedModules[moduleId].exports; 1056 | 1057 | /******/ // Create a new module (and put it into the cache) 1058 | /******/var module = installedModules[moduleId] = { 1059 | /******/exports: {}, 1060 | /******/id: moduleId, 1061 | /******/loaded: false 1062 | /******/ }; 1063 | 1064 | /******/ // Execute the module function 1065 | /******/modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 1066 | 1067 | /******/ // Flag the module as loaded 1068 | /******/module.loaded = true; 1069 | 1070 | /******/ // Return the exports of the module 1071 | /******/return module.exports; 1072 | /******/ 1073 | } 1074 | 1075 | /******/ // expose the modules object (__webpack_modules__) 1076 | /******/__webpack_require__.m = modules; 1077 | 1078 | /******/ // expose the module cache 1079 | /******/__webpack_require__.c = installedModules; 1080 | 1081 | /******/ // __webpack_public_path__ 1082 | /******/__webpack_require__.p = ""; 1083 | 1084 | /******/ // Load entry module and return exports 1085 | /******/return __webpack_require__(0); 1086 | /******/ 1087 | }( 1088 | /************************************************************************/ 1089 | /******/[ 1090 | /* 0 */ 1091 | /***/function (module, exports, __webpack_require__) { 1092 | 1093 | 'use strict'; 1094 | 1095 | Object.defineProperty(exports, "__esModule", { 1096 | value: true 1097 | }); 1098 | exports.createWorker = exports.applyWorker = undefined; 1099 | 1100 | var _createWorker = __webpack_require__(1); 1101 | 1102 | var _createWorker2 = _interopRequireDefault(_createWorker); 1103 | 1104 | var _applyWorker = __webpack_require__(2); 1105 | 1106 | var _applyWorker2 = _interopRequireDefault(_applyWorker); 1107 | 1108 | function _interopRequireDefault(obj) { 1109 | return obj && obj.__esModule ? obj : { default: obj }; 1110 | } 1111 | 1112 | exports.applyWorker = _applyWorker2.default; 1113 | exports.createWorker = _createWorker2.default; 1114 | exports.default = { applyWorker: _applyWorker2.default, createWorker: _createWorker2.default }; 1115 | 1116 | /***/ 1117 | }, 1118 | /* 1 */ 1119 | /***/function (module, exports) { 1120 | 1121 | 'use strict'; 1122 | 1123 | Object.defineProperty(exports, "__esModule", { 1124 | value: true 1125 | }); 1126 | 1127 | var _createClass = function () { 1128 | function defineProperties(target, props) { 1129 | for (var i = 0; i < props.length; i++) { 1130 | var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor); 1131 | } 1132 | }return function (Constructor, protoProps, staticProps) { 1133 | if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor; 1134 | }; 1135 | }(); 1136 | 1137 | function _classCallCheck(instance, Constructor) { 1138 | if (!(instance instanceof Constructor)) { 1139 | throw new TypeError("Cannot call a class as a function"); 1140 | } 1141 | } 1142 | 1143 | var createWorker = function createWorker(reducer) { 1144 | // Initialize ReduxWorekr 1145 | var worker = new ReduxWorker(); 1146 | 1147 | self.addEventListener('message', function (e) { 1148 | var action = e.data; 1149 | 1150 | if (typeof action.type === 'string') { 1151 | if (!worker.reducer || typeof worker.reducer !== 'function') { 1152 | throw new Error('Expect reducer to be function. Have you registerReducer yet?'); 1153 | } 1154 | 1155 | // Set new state 1156 | var state = worker.state; 1157 | state = worker.state = worker.reducer(state, action); 1158 | state = worker.transform(state); 1159 | 1160 | // Send new state to main thread 1161 | self.postMessage({ 1162 | type: action.type, 1163 | state: state, 1164 | action: action 1165 | }); 1166 | 1167 | return; 1168 | } 1169 | 1170 | if (typeof action.task === 'string' && typeof action._taskId === 'number') { 1171 | var taskRunner = worker.tasks[action.task]; 1172 | 1173 | if (!taskRunner || typeof taskRunner !== 'function') { 1174 | throw new Error('Cannot find runner for task ' + action.task + '. Have you registerTask yet?'); 1175 | } 1176 | 1177 | // Send new state to main thread 1178 | self.postMessage({ 1179 | _taskId: action._taskId, 1180 | response: taskRunner(action) 1181 | }); 1182 | } 1183 | }); 1184 | 1185 | return worker; 1186 | }; 1187 | 1188 | var ReduxWorker = function () { 1189 | function ReduxWorker() { 1190 | _classCallCheck(this, ReduxWorker); 1191 | 1192 | // Taskrunners 1193 | this.tasks = {}; 1194 | 1195 | // Redux-specific variables 1196 | this.state = {}; 1197 | this.reducer = null; 1198 | this.transform = function (state) { 1199 | return state; 1200 | }; 1201 | } 1202 | 1203 | _createClass(ReduxWorker, [{ 1204 | key: 'registerReducer', 1205 | value: function registerReducer(reducer, transform) { 1206 | this.reducer = reducer; 1207 | this.state = reducer({}, {}); 1208 | } 1209 | }, { 1210 | key: 'registerTask', 1211 | value: function registerTask(name, taskFn) { 1212 | this.tasks[name] = taskFn; 1213 | } 1214 | }]); 1215 | 1216 | return ReduxWorker; 1217 | }(); 1218 | 1219 | exports.default = createWorker; 1220 | 1221 | /***/ 1222 | }, 1223 | /* 2 */ 1224 | /***/function (module, exports) { 1225 | 1226 | 'use strict'; 1227 | 1228 | Object.defineProperty(exports, "__esModule", { 1229 | value: true 1230 | }); 1231 | var defer = function defer() { 1232 | var result = {}; 1233 | result.promise = new Promise(function (resolve, reject) { 1234 | result.resolve = resolve; 1235 | result.reject = reject; 1236 | }); 1237 | return result; 1238 | }; 1239 | 1240 | var applyWorker = function applyWorker(worker) { 1241 | return function (createStore) { 1242 | return function (reducer, initialState, enhancer) { 1243 | if (!(worker instanceof Worker)) { 1244 | console.error('Expect input to be a Web Worker. Fall back to normal store.'); 1245 | return createStore(reducer, initialState, enhancer); 1246 | } 1247 | 1248 | // New reducer for workified store 1249 | var replacementReducer = function replacementReducer(state, action) { 1250 | if (action.state) { 1251 | return action.state; 1252 | } 1253 | return state; 1254 | }; 1255 | 1256 | // Start task id; 1257 | var taskId = 0; 1258 | var taskCompleteCallbacks = {}; 1259 | 1260 | // Create store using new reducer 1261 | var store = createStore(replacementReducer, reducer({}, {}), enhancer); 1262 | 1263 | // Store reference of old dispatcher 1264 | var next = store.dispatch; 1265 | 1266 | // Replace dispatcher 1267 | store.dispatch = function (action) { 1268 | if (typeof action.type === 'string') { 1269 | if (window.disableWebWorker) { 1270 | return next({ 1271 | type: action.type, 1272 | state: reducer(store.getState(), action) 1273 | }); 1274 | } 1275 | worker.postMessage(action); 1276 | } 1277 | 1278 | if (typeof action.task === 'string') { 1279 | var task = Object.assign({}, action, { _taskId: taskId }); 1280 | var deferred = defer(); 1281 | 1282 | taskCompleteCallbacks[taskId] = deferred; 1283 | taskId++; 1284 | worker.postMessage(task); 1285 | return deferred.promise; 1286 | } 1287 | }; 1288 | 1289 | store.isWorker = true; 1290 | 1291 | // Add worker events listener 1292 | worker.addEventListener('message', function (e) { 1293 | var action = e.data; 1294 | if (typeof action.type === 'string') { 1295 | next(action); 1296 | } 1297 | 1298 | if (typeof action._taskId === 'number') { 1299 | var wrapped = taskCompleteCallbacks[action._taskId]; 1300 | 1301 | if (wrapped) { 1302 | wrapped.resolve(action); 1303 | delete taskCompleteCallbacks[action._taskId]; 1304 | } 1305 | } 1306 | }); 1307 | 1308 | return store; 1309 | }; 1310 | }; 1311 | }; 1312 | 1313 | exports.default = applyWorker; 1314 | 1315 | /***/ 1316 | } 1317 | /******/]) 1318 | ); 1319 | }); 1320 | ; 1321 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(190)(module))) 1322 | 1323 | /***/ }, 1324 | 1325 | /***/ 190: 1326 | /***/ function(module, exports) { 1327 | 1328 | module.exports = function(module) { 1329 | if(!module.webpackPolyfill) { 1330 | module.deprecate = function() {}; 1331 | module.paths = []; 1332 | // module.parent = undefined by default 1333 | module.children = []; 1334 | module.webpackPolyfill = 1; 1335 | } 1336 | return module; 1337 | } 1338 | 1339 | 1340 | /***/ } 1341 | 1342 | /******/ }); -------------------------------------------------------------------------------- /demo/sortTable/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redux Worker 5 | 104 | 105 | 106 |
107 | 108 | 109 | -------------------------------------------------------------------------------- /demo/sortTable/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { Provider } from 'react-redux' 4 | import { createStore, applyMiddleware, compose } from 'redux' 5 | import thunk from 'redux-thunk' 6 | import rootReducer from './reducers' 7 | import App from './components/App' 8 | import { applyWorker } from 'redux-worker' 9 | import createLogger from 'redux-logger'; 10 | 11 | const logger = createLogger(); 12 | 13 | const worker = new Worker('./dist/worker.bundle.js'); 14 | 15 | const enhancerWithWorker = compose( 16 | // Middleware you want to use in development: 17 | applyMiddleware(thunk, logger), 18 | applyWorker(worker) 19 | ); 20 | 21 | const store = createStore(rootReducer, {}, enhancerWithWorker); 22 | 23 | render( 24 | 25 | 26 | , 27 | document.getElementById('app') 28 | ) 29 | 30 | 31 | -------------------------------------------------------------------------------- /demo/sortTable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-worker-demo-sort-table", 3 | "version": "1.0.0", 4 | "description": "Demo for Redux-Worker", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "watch": "./node_modules/.bin/webpack-dev-server" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "babel-polyfill": "^6.6.1", 14 | "babel-runtime": "^6.6.1", 15 | "faker": "^3.1.0", 16 | "react": "^0.14.7", 17 | "react-dom": "^0.14.7", 18 | "react-infinite": "^0.9.2", 19 | "react-redux": "^4.4.0", 20 | "react-virtual-list": "^1.8.0", 21 | "redux": "^3.3.1", 22 | "redux-logger": "^2.6.1", 23 | "redux-thunk": "^1.0.3", 24 | "redux-worker": "^0.1.0" 25 | }, 26 | "devDependencies": { 27 | "babel-core": "^6.6.4", 28 | "babel-loader": "^6.2.4", 29 | "babel-preset-es2015": "6.3.13", 30 | "babel-preset-react": "^6.5.0", 31 | "webpack": "^1.12.14", 32 | "webpack-dev-server": "^1.14.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /demo/sortTable/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import users from './users/users' 3 | 4 | const sortTableApp = combineReducers({ 5 | users 6 | }); 7 | 8 | export default sortTableApp -------------------------------------------------------------------------------- /demo/sortTable/reducers/users/users.js: -------------------------------------------------------------------------------- 1 | import Faker, {fake} from 'faker' 2 | import USERS from '../../usersFixtures' 3 | 4 | const generateUser = () => { 5 | return { 6 | uuid: Faker.random.uuid(), 7 | firstName: fake('{{name.firstName}}'), 8 | lastName: fake('{{name.lastName}}'), 9 | dateOfBirth: fake('{{date.past}}') 10 | }; 11 | } 12 | 13 | const generateUsers = (n) => { 14 | let users = []; 15 | for (let i = 0; i < n; i++) { 16 | users.push(generateUser()); 17 | } 18 | 19 | return users; 20 | } 21 | 22 | const intState = []; 23 | 24 | const sortByFirstName = (users, reverse) => { 25 | return users.sort(function(a, b){ 26 | if(a.firstName < b.firstName) return reverse ? 1 : -1; 27 | if(a.firstName > b.firstName) return reverse ? -1 : 1; 28 | return 0; 29 | }); 30 | } 31 | 32 | const sortByLastName = (users, reverse) => { 33 | return users.sort(function(a, b){ 34 | if(a.lastName < b.lastName) return reverse ? 1 : -1; 35 | if(a.lastName > b.lastName) return reverse ? -1 : 1; 36 | return 0; 37 | }); 38 | } 39 | 40 | const sortByDOB = (users, reverse) => { 41 | return users.sort(function(userA, userB) { 42 | let result = reverse ? 43 | getTime(userA.dateOfBirth) - getTime(userB.dateOfBirth) : 44 | getTime(userB.dateOfBirth) - getTime(userA.dateOfBirth) 45 | return result; 46 | }) 47 | 48 | function getTime(timestamp) { 49 | return new Date(timestamp).getTime(); 50 | } 51 | } 52 | 53 | export default (state = intState, action) => { 54 | switch (action.type) { 55 | case 'SORT_BY_FIRST': 56 | return sortByFirstName(state, action.reverse); 57 | case 'SORT_BY_LAST': 58 | return sortByLastName(state, action.reverse); 59 | case 'SORT_BY_DOB': 60 | return sortByDOB(state, action.reverse); 61 | case 'GENERATE_USERS': 62 | return generateUsers(action.number || 10); 63 | default: 64 | return state; 65 | } 66 | 67 | return state; 68 | }; -------------------------------------------------------------------------------- /demo/sortTable/reducers/worker.js: -------------------------------------------------------------------------------- 1 | import reducer from '../reducers' 2 | import { createWorker } from 'redux-worker' 3 | 4 | import Faker, {fake} from 'faker' 5 | 6 | const generateUser = () => { 7 | return { 8 | uuid: Faker.random.uuid(), 9 | firstName: fake('{{name.firstName}}'), 10 | lastName: fake('{{name.lastName}}'), 11 | dateOfBirth: fake('{{date.past}}') 12 | }; 13 | } 14 | 15 | const generateUsers = (n) => { 16 | let users = []; 17 | for (let i = 0; i < n; i++) { 18 | users.push(generateUser()); 19 | } 20 | 21 | return users; 22 | } 23 | 24 | 25 | let worker = createWorker(); 26 | 27 | worker.registerReducer(reducer); 28 | 29 | worker.registerTask('GENERATE', function(a) { 30 | let num = a.number; 31 | return generateUsers(num); 32 | }); 33 | 34 | function getTime(timestamp) { 35 | return new Date(timestamp).getTime(); 36 | } -------------------------------------------------------------------------------- /demo/sortTable/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: { 6 | main: './main.js', 7 | worker: './reducers/worker.js' 8 | }, 9 | output: { 10 | path: __dirname, 11 | filename: './dist/[name].bundle.js' 12 | }, 13 | module: { 14 | loaders: [ 15 | { 16 | test: /.jsx?$/, 17 | loader: 'babel-loader', 18 | exclude: /node_modules/, 19 | query: { 20 | presets: ['es2015', 'react'] 21 | } 22 | } 23 | ] 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /dist/reduxWorker.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define([], factory); 6 | else if(typeof exports === 'object') 7 | exports["ReduxWorker"] = factory(); 8 | else 9 | root["ReduxWorker"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | 39 | 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | 'use strict'; 58 | 59 | Object.defineProperty(exports, "__esModule", { 60 | value: true 61 | }); 62 | exports.createWorker = exports.applyWorker = undefined; 63 | 64 | var _createWorker = __webpack_require__(1); 65 | 66 | var _createWorker2 = _interopRequireDefault(_createWorker); 67 | 68 | var _applyWorker = __webpack_require__(2); 69 | 70 | var _applyWorker2 = _interopRequireDefault(_applyWorker); 71 | 72 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 73 | 74 | exports.applyWorker = _applyWorker2.default; 75 | exports.createWorker = _createWorker2.default; 76 | exports.default = { applyWorker: _applyWorker2.default, createWorker: _createWorker2.default }; 77 | 78 | /***/ }, 79 | /* 1 */ 80 | /***/ function(module, exports) { 81 | 82 | 'use strict'; 83 | 84 | Object.defineProperty(exports, "__esModule", { 85 | value: true 86 | }); 87 | 88 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 89 | 90 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 91 | 92 | var createWorker = function createWorker(reducer) { 93 | // Initialize ReduxWorekr 94 | var worker = new ReduxWorker(); 95 | 96 | self.addEventListener('message', function (e) { 97 | var action = e.data; 98 | 99 | if (typeof action.type === 'string') { 100 | if (!worker.reducer || typeof worker.reducer !== 'function') { 101 | throw new Error('Expect reducer to be function. Have you registerReducer yet?'); 102 | } 103 | 104 | // Set new state 105 | var state = worker.state; 106 | state = worker.state = worker.reducer(state, action); 107 | state = worker.transform(state); 108 | 109 | // Send new state to main thread 110 | self.postMessage({ 111 | type: action.type, 112 | state: state, 113 | action: action 114 | }); 115 | 116 | return; 117 | } 118 | 119 | if (typeof action.task === 'string' && typeof action._taskId === 'number') { 120 | var taskRunner = worker.tasks[action.task]; 121 | 122 | if (!taskRunner || typeof taskRunner !== 'function') { 123 | throw new Error('Cannot find runner for task ' + action.task + '. Have you registerTask yet?'); 124 | } 125 | 126 | // Send new state to main thread 127 | self.postMessage({ 128 | _taskId: action._taskId, 129 | response: taskRunner(action) 130 | }); 131 | } 132 | }); 133 | 134 | return worker; 135 | }; 136 | 137 | var ReduxWorker = function () { 138 | function ReduxWorker() { 139 | _classCallCheck(this, ReduxWorker); 140 | 141 | // Taskrunners 142 | this.tasks = {}; 143 | 144 | // Redux-specific variables 145 | this.state = {}; 146 | this.reducer = null; 147 | this.transform = function (state) { 148 | return state; 149 | }; 150 | } 151 | 152 | _createClass(ReduxWorker, [{ 153 | key: 'registerReducer', 154 | value: function registerReducer(reducer, transform) { 155 | this.reducer = reducer; 156 | this.state = reducer({}, {}); 157 | } 158 | }, { 159 | key: 'registerTask', 160 | value: function registerTask(name, taskFn) { 161 | this.tasks[name] = taskFn; 162 | } 163 | }]); 164 | 165 | return ReduxWorker; 166 | }(); 167 | 168 | exports.default = createWorker; 169 | 170 | /***/ }, 171 | /* 2 */ 172 | /***/ function(module, exports) { 173 | 174 | 'use strict'; 175 | 176 | Object.defineProperty(exports, "__esModule", { 177 | value: true 178 | }); 179 | var defer = function defer() { 180 | var result = {}; 181 | result.promise = new Promise(function (resolve, reject) { 182 | result.resolve = resolve; 183 | result.reject = reject; 184 | }); 185 | return result; 186 | }; 187 | 188 | var applyWorker = function applyWorker(worker) { 189 | return function (createStore) { 190 | return function (reducer, initialState, enhancer) { 191 | if (!(worker instanceof Worker)) { 192 | console.error('Expect input to be a Web Worker. Fall back to normal store.'); 193 | return createStore(reducer, initialState, enhancer); 194 | } 195 | 196 | // New reducer for workified store 197 | var replacementReducer = function replacementReducer(state, action) { 198 | if (action.state) { 199 | return action.state; 200 | } 201 | return state; 202 | }; 203 | 204 | // Start task id; 205 | var taskId = 0; 206 | var taskCompleteCallbacks = {}; 207 | 208 | // Create store using new reducer 209 | var store = createStore(replacementReducer, reducer({}, {}), enhancer); 210 | 211 | // Store reference of old dispatcher 212 | var next = store.dispatch; 213 | 214 | // Replace dispatcher 215 | store.dispatch = function (action) { 216 | if (typeof action.type === 'string') { 217 | if (window.disableWebWorker) { 218 | return next({ 219 | type: action.type, 220 | state: reducer(store.getState(), action) 221 | }); 222 | } 223 | worker.postMessage(action); 224 | } 225 | 226 | if (typeof action.task === 'string') { 227 | var task = Object.assign({}, action, { _taskId: taskId }); 228 | var deferred = defer(); 229 | 230 | taskCompleteCallbacks[taskId] = deferred; 231 | taskId++; 232 | worker.postMessage(task); 233 | return deferred.promise; 234 | } 235 | }; 236 | 237 | store.isWorker = true; 238 | 239 | // Add worker events listener 240 | worker.addEventListener('message', function (e) { 241 | var action = e.data; 242 | if (typeof action.type === 'string') { 243 | next(action); 244 | } 245 | 246 | if (typeof action._taskId === 'number') { 247 | var wrapped = taskCompleteCallbacks[action._taskId]; 248 | 249 | if (wrapped) { 250 | wrapped.resolve(action); 251 | delete taskCompleteCallbacks[action._taskId]; 252 | } 253 | } 254 | }); 255 | 256 | return store; 257 | }; 258 | }; 259 | }; 260 | 261 | exports.default = applyWorker; 262 | 263 | /***/ } 264 | /******/ ]) 265 | }); 266 | ; -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | browsers: [ 'PhantomJS' ], 4 | 5 | files: [ 6 | { 7 | pattern: './spec/test-context.js', 8 | watched: false 9 | } 10 | ], 11 | 12 | frameworks: [ 'jasmine' ], 13 | 14 | preprocessors: { 15 | 'spec/test-context.js': [ 'webpack' ] 16 | }, 17 | 18 | reporters: [ 'mocha' ], 19 | 20 | webpack: { 21 | module: { 22 | loaders: [ 23 | { 24 | test: /\.js/, 25 | exclude: /node_modules/, 26 | loader: 'babel-loader' 27 | } 28 | ] 29 | }, 30 | 31 | watch: true 32 | }, 33 | 34 | webpackServer: { 35 | noInfo: true 36 | } 37 | }); 38 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-worker", 3 | "version": "0.1.2", 4 | "description": "", 5 | "main": "./dist/reduxWorker.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/karma start --browsers PhantomJS", 8 | "build": "./node_modules/.bin/webpack src/reduxWorker.js dist/reduxWorker.js --color --progress" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "babel-polyfill": "^6.7.4", 14 | "babel-runtime": "^6.6.1" 15 | }, 16 | "devDependencies": { 17 | "babel-cli": "^6.6.5", 18 | "babel-core": "^6.6.4", 19 | "babel-loader": "^6.2.4", 20 | "babel-plugin-transform-object-assign": "^6.5.0", 21 | "babel-polyfill": "^6.7.4", 22 | "babel-preset-es2015": "6.3.13", 23 | "babel-preset-react": "^6.5.0", 24 | "jasmine": "^2.4.1", 25 | "jasmine-core": "^2.4.1", 26 | "karma": "^0.13.22", 27 | "karma-jasmine": "^0.3.8", 28 | "karma-junit-reporter": "^0.4.1", 29 | "karma-mocha-reporter": "^2.0.0", 30 | "karma-phantomjs-launcher": "^1.0.0", 31 | "karma-webpack": "^1.7.0", 32 | "phantomjs-prebuilt": "^2.1.7", 33 | "webpack": "^1.12.14" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spec/applyWorker.spec.js: -------------------------------------------------------------------------------- 1 | import applyWorker from '../src/applyWorker'; 2 | import 'babel-polyfill'; 3 | 4 | const blob = new Blob([ 'console.log(\'I am a worker!\');'], { type: 'text/javascript' }); 5 | const url = window.URL.createObjectURL(blob); 6 | 7 | describe('applyWorker', () => { 8 | const retState = {}; 9 | const reducer = () => retState; 10 | it('should return a function', () => { 11 | let result = applyWorker(); 12 | expect(typeof result).toBe('function'); 13 | }); 14 | 15 | it('should fallback to regular createStore method if worker is not passed in', () => { 16 | let makeStoreCreator = applyWorker(); 17 | let intState = {}; 18 | let enhancer = {}; 19 | let createStore = makeStoreCreator((reducer, intState, enhancer) => { 20 | return { 21 | reducer, 22 | intState, 23 | enhancer 24 | }; 25 | }); 26 | let store = createStore(reducer, intState, enhancer); 27 | expect(store.reducer).toBe(reducer); 28 | expect(store.intState).toBe(intState); 29 | expect(store.enhancer).toBe(enhancer); 30 | }); 31 | 32 | it('should replace reducer and intialState when worker is passed in', () => { 33 | let worker = new Worker(url); 34 | let makeStoreCreator = applyWorker(worker); 35 | let intState = {}; 36 | let enhancer = {}; 37 | let createStore = makeStoreCreator((reducer, intState, enhancer) => { 38 | return { 39 | reducer, 40 | intState, 41 | enhancer 42 | }; 43 | }); 44 | let store = createStore(reducer, intState, enhancer); 45 | expect(store.reducer).not.toBe(reducer); 46 | expect(store.intState).toBe(retState); 47 | expect(store.enhancer).toBe(enhancer); 48 | expect(typeof store.dispatch).toBe('function'); 49 | }); 50 | 51 | describe('behavior of stubbed dispatch', () => { 52 | let store, worker; 53 | 54 | beforeEach(function() { 55 | worker = new Worker(url); 56 | 57 | let makeStoreCreator = applyWorker(worker); 58 | let intState = {}; 59 | let enhancer = {}; 60 | let createStore = makeStoreCreator((reducer, intState, enhancer) => { 61 | return { 62 | reducer, 63 | intState, 64 | enhancer 65 | }; 66 | }); 67 | 68 | store = createStore(reducer, intState, enhancer); 69 | spyOn(worker, 'postMessage').and.callThrough(); 70 | }); 71 | 72 | it('should postMessage when dispatching an action', () => { 73 | const action = { 74 | type: 'ACTION', 75 | data: 'DATA' 76 | }; 77 | 78 | store.dispatch(action); 79 | expect(worker.postMessage).toHaveBeenCalledWith(action); 80 | }); 81 | 82 | it('should return a promise when dispatching a task', function() { 83 | const task = { 84 | task: 'TASK', 85 | data: 'DATA' 86 | }; 87 | 88 | store.dispatch(task); 89 | expect(worker.postMessage).toHaveBeenCalledWith({ 90 | task: 'TASK', 91 | data: 'DATA', 92 | _taskId: jasmine.any(Number) 93 | }); 94 | }); 95 | }) 96 | }); -------------------------------------------------------------------------------- /spec/createWorker.spec.js: -------------------------------------------------------------------------------- 1 | import createWorker from '../src/createWorker'; 2 | import 'babel-polyfill'; 3 | 4 | describe('createWorker', () => { 5 | let worker; 6 | 7 | beforeEach(() => { 8 | worker = createWorker(); 9 | }) 10 | 11 | afterEach(() => { 12 | worker.destroy(); 13 | }); 14 | 15 | it('should have create ReduxWorker', () => { 16 | expect(typeof worker.tasks).toBe('object'); 17 | expect(typeof worker.state).toBe('object'); 18 | expect(worker.reducer).toBe(null); 19 | expect(typeof worker.transform).toBe('function'); 20 | }); 21 | 22 | it('should registerReducer', () => { 23 | let reducer = () => {}; 24 | worker.registerReducer(reducer); 25 | expect(worker.reducer).toBe(reducer); 26 | }); 27 | 28 | it('should registerTask', () => { 29 | let task = () => {}; 30 | worker.registerTask('testTask', task); 31 | expect(worker.tasks.testTask).toBe(task); 32 | }); 33 | 34 | describe('Behavior of ReduxWorker', () => { 35 | let taskOne = (data) => { 36 | return data.num + 1; 37 | }; 38 | 39 | let reducer; 40 | 41 | beforeEach(() => { 42 | reducer = jasmine.createSpy('reducer'); 43 | worker.registerReducer(reducer); 44 | worker.registerTask('taskOne', taskOne); 45 | spyOn(self, 'postMessage'); 46 | reducer.calls.reset(); 47 | self.postMessage.calls.reset(); 48 | }); 49 | 50 | it('should run action through reducer', () => { 51 | let event = new Event('message'); 52 | 53 | event.data = { 54 | type: 'ACTION', 55 | data: 'DATA' 56 | } 57 | 58 | self.dispatchEvent(event); 59 | let args = reducer.calls.first().args; 60 | 61 | expect(self.postMessage).toHaveBeenCalledWith({ 62 | type: 'ACTION', 63 | state: undefined, 64 | action: { 65 | type: 'ACTION', 66 | data: 'DATA' 67 | } 68 | }); 69 | 70 | expect(args[ 0 ]).toBe(undefined); 71 | expect(args[ 1 ]).toEqual({ 72 | type: 'ACTION', 73 | data: 'DATA' 74 | }); 75 | }); 76 | 77 | it('should run task through tasksRunner', () => { 78 | let event = new Event('message'); 79 | 80 | event.data = { 81 | task: 'taskOne', 82 | num: 2, 83 | _taskId: 0 84 | } 85 | 86 | self.dispatchEvent(event); 87 | 88 | expect(self.postMessage).toHaveBeenCalledWith({ 89 | _taskId: 0, 90 | response: 3 91 | }); 92 | }); 93 | }); 94 | 95 | 96 | }); -------------------------------------------------------------------------------- /spec/test-context.js: -------------------------------------------------------------------------------- 1 | var context = require.context('./', true, /\.spec\.js$/); 2 | context.keys().forEach(context); 3 | -------------------------------------------------------------------------------- /src/applyWorker.js: -------------------------------------------------------------------------------- 1 | const defer = function() { 2 | let result = {}; 3 | result.promise = new Promise(function(resolve, reject) { 4 | result.resolve = resolve; 5 | result.reject = reject; 6 | }); 7 | return result; 8 | }; 9 | 10 | const applyWorker = (worker) => { 11 | return createStore => (reducer, initialState, enhancer) => { 12 | if (!(worker instanceof Worker)) { 13 | console.error('Expect input to be a Web Worker. Fall back to normal store.'); 14 | return createStore(reducer, initialState, enhancer); 15 | } 16 | 17 | // New reducer for workified store 18 | let replacementReducer = (state, action) => { 19 | if (action.state) { 20 | return action.state; 21 | } 22 | return state; 23 | } 24 | 25 | // Start task id; 26 | let taskId = 0; 27 | let taskCompleteCallbacks = {}; 28 | 29 | // Create store using new reducer 30 | let store = createStore(replacementReducer, reducer({}, {}), enhancer); 31 | 32 | // Store reference of old dispatcher 33 | let next = store.dispatch; 34 | 35 | // Replace dispatcher 36 | store.dispatch = (action) => { 37 | if (typeof action.type === 'string') { 38 | if (window.disableWebWorker) { 39 | return next({ 40 | type: action.type, 41 | state: reducer(store.getState(), action) 42 | }); 43 | } 44 | worker.postMessage(action); 45 | } 46 | 47 | if (typeof action.task === 'string') { 48 | let task = Object.assign({}, action, { _taskId: taskId }); 49 | let deferred = defer(); 50 | 51 | taskCompleteCallbacks[ taskId ] = deferred 52 | taskId++; 53 | worker.postMessage(task); 54 | return deferred.promise; 55 | } 56 | } 57 | 58 | store.isWorker = true; 59 | 60 | // Add worker events listener 61 | worker.addEventListener('message', function(e) { 62 | let action = e.data; 63 | if (typeof action.type === 'string') { 64 | next(action); 65 | } 66 | 67 | if (typeof action._taskId === 'number') { 68 | let wrapped = taskCompleteCallbacks[ action._taskId ]; 69 | 70 | if (wrapped) { 71 | wrapped.resolve(action); 72 | delete taskCompleteCallbacks[ action._taskId ]; 73 | } 74 | } 75 | }); 76 | 77 | return store; 78 | } 79 | } 80 | 81 | export default applyWorker -------------------------------------------------------------------------------- /src/createWorker.js: -------------------------------------------------------------------------------- 1 | const createWorker = (reducer) => { 2 | // Initialize ReduxWorekr 3 | let worker = new ReduxWorker(); 4 | 5 | let messageHandler = (e) => { 6 | var action = e.data; 7 | 8 | if (typeof action.type === 'string') { 9 | if (!worker.reducer || typeof worker.reducer !== 'function') { 10 | throw new Error('Expect reducer to be function. Have you registerReducer yet?'); 11 | } 12 | 13 | // Set new state 14 | let state = worker.state; 15 | state = worker.state = worker.reducer(state, action); 16 | state = worker.transform(state); 17 | 18 | // Send new state to main thread 19 | self.postMessage({ 20 | type: action.type, 21 | state: state, 22 | action: action 23 | }); 24 | 25 | return; 26 | } 27 | 28 | if (typeof action.task === 'string' && typeof action._taskId === 'number') { 29 | let taskRunner = worker.tasks[ action.task ]; 30 | 31 | if (!taskRunner || typeof taskRunner !== 'function') { 32 | throw new Error('Cannot find runner for task ' + action.task + '. Have you registerTask yet?'); 33 | } 34 | 35 | // Send new state to main thread 36 | self.postMessage({ 37 | _taskId: action._taskId, 38 | response: taskRunner(action) 39 | }); 40 | } 41 | } 42 | 43 | worker.destroy = () => { 44 | self.removeEventListener('message', messageHandler); 45 | } 46 | 47 | self.addEventListener('message', messageHandler); 48 | 49 | 50 | return worker; 51 | } 52 | 53 | class ReduxWorker { 54 | constructor() { 55 | // Taskrunners 56 | this.tasks = {}; 57 | 58 | // Redux-specific variables 59 | this.state = {}; 60 | this.reducer = null; 61 | this.transform = function(state) { return state; } 62 | } 63 | 64 | registerReducer(reducer, transform) { 65 | this.reducer = reducer; 66 | this.state = reducer({}, {}); 67 | } 68 | 69 | registerTask(name, taskFn) { 70 | this.tasks[ name ] = taskFn; 71 | } 72 | 73 | } 74 | 75 | export default createWorker -------------------------------------------------------------------------------- /src/reduxWorker.js: -------------------------------------------------------------------------------- 1 | import createWorker from './createWorker' 2 | import applyWorker from './applyWorker' 3 | 4 | export { applyWorker, createWorker } 5 | export default { applyWorker, createWorker } 6 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: { 6 | lib: './src/reduxWorker.js' 7 | }, 8 | output: { 9 | library: 'ReduxWorker', 10 | libraryTarget: 'umd' 11 | }, 12 | module: { 13 | loaders: [ 14 | { 15 | test: /.jsx?$/, 16 | loader: 'babel-loader', 17 | exclude: /node_modules/, 18 | query: { 19 | presets: ['es2015', 'react'] 20 | } 21 | } 22 | ] 23 | }, 24 | }; 25 | --------------------------------------------------------------------------------