├── .babelrc
├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── examples
├── .gitignore
└── universal-counter
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── client
│ │ └── index.js
│ ├── server
│ │ ├── index.js
│ │ └── utils
│ │ │ ├── getIntentStream.js
│ │ │ └── render.js
│ └── shared
│ │ ├── constants
│ │ └── IntentTypes.js
│ │ ├── index.js
│ │ ├── intents
│ │ ├── CounterIntentList.js
│ │ └── CounterIntents.js
│ │ ├── reducers
│ │ └── counter.js
│ │ ├── rx-middleware
│ │ └── commonMiddleware.js
│ │ ├── utils
│ │ ├── appendIntentId.js
│ │ └── isBrowser.js
│ │ └── view
│ │ └── components
│ │ └── Counter.js
│ └── webpack
│ ├── dev-server.js
│ ├── webpack.config.client.js
│ ├── webpack.config.js
│ └── webpack.config.server.js
├── package.json
├── src
├── createStore.js
├── index.js
└── utils
│ ├── combineReducers.js
│ └── isPlainObject.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0,
3 | "loose": "all"
4 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "env": {
4 | "browser": true,
5 | "mocha": true,
6 | "node": true
7 | },
8 | "rules": {
9 | //Temporarirly disabled due to a possible bug in babel-eslint (todomvc example)
10 | "block-scoped-var": 0,
11 | // Temporarily disabled for test/* until babel/babel-eslint#33 is resolved
12 | "padded-blocks": 0
13 | }
14 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | npm-debug.log
4 | dist
5 | lib
6 | .idea
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | examples
3 | .idea
4 | .babelrc
5 | .eslintrc
6 | .npmignore
7 | webpack.config.js
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Jas Chen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/redux-core)
2 |
3 | redux-core
4 | ==========
5 |
6 | Minimal [redux](https://github.com/gaearon/redux).
7 |
8 | ## Why?
9 | - Only ~60 lines of code to read, easy to understand and customize.
10 | - You can implement your own state subscription and store enhancement system.
11 |
12 | ## Provided APIs
13 | - createStore
14 | - getState
15 | - dispatch (***will return state***)
16 | - getReducer
17 | - replaceReducer
18 | - combineReducers
19 |
20 | ## License
21 | [The MIT License (MIT)](./LICENSE)
22 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | npm-debug.log
4 | build
--------------------------------------------------------------------------------
/examples/universal-counter/README.md:
--------------------------------------------------------------------------------
1 | Universal Counter Demo
2 | ======================
3 |
4 | ## redux-core + React + RxJS
5 |
6 | In redux, to make an universal app you have to gather all your Promises returned from dispatch() in server side, this could be painful if you have many Promises.
7 |
8 | With RxJS and [RxMiddleware](https://github.com/jas-chen/rx-redux/#best-practice-to-make-your-app-all-the-way-reactive), subscribe for stream complete event and you are done.
9 |
10 | ## To run this example
11 |
12 | ```
13 | cd examples/universal-counter
14 | npm install
15 |
16 | # Production mode
17 | npm run build
18 | npm start
19 | open http://localhost:3000
20 |
21 |
22 | # Development mode, start 3 consoles to run each command
23 | npm run build-server-watch
24 | npm run build-client-watch
25 | npm run dev
26 | open http://localhost:3000
27 | ```
28 |
29 |
--------------------------------------------------------------------------------
/examples/universal-counter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "universal-counter-rx",
3 | "version": "0.1.0",
4 | "description": "Universal Counter with redux-core",
5 | "main": "index.js",
6 | "scripts": {
7 | "clean": "rimraf build",
8 |
9 | "build": "NODE_ENV=production webpack --config ./webpack/webpack.config.js --devtool source-map --progress --display-modules",
10 | "start": "NODE_ENV=production node ./build/server/server.js",
11 |
12 | "build-server-watch": "webpack -d --config ./webpack/webpack.config.server.js --watch",
13 | "build-client-watch": "node ./webpack/dev-server.js",
14 | "dev": "nodemon ./build/server/server.js --watch build/server",
15 |
16 | "test": "echo \"Error: no test specified\" && exit 1"
17 | },
18 | "author": "Jas Chen",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "babel": "^5.8.19",
22 | "babel-core": "^5.8.19",
23 | "babel-loader": "^5.3.2",
24 | "express": "^4.13.1",
25 | "json-loader": "^0.5.2",
26 | "nodemon": "^1.4.0",
27 | "react": "^0.13.3",
28 | "redux-core": "^0.1.1",
29 | "rimraf": "^2.4.2",
30 | "rx-lite": "^2.5.2",
31 | "rx-lite-joinpatterns": "^2.5.2",
32 | "webpack": "^1.10.5",
33 | "webpack-dev-server": "^1.10.1"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/examples/universal-counter/src/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Rx from 'rx';
3 | import { createStore } from 'redux-core';
4 | import { reducer, applyRxMiddleware } from '../shared';
5 | import Counter from '../shared/view/components/Counter';
6 |
7 | const root = document.getElementById('root');
8 |
9 | const store = createStore(reducer, window.__state);
10 |
11 | const intent$ = new Rx.Subject();
12 | const action$ = applyRxMiddleware(intent$, store);
13 | const state$ = action$.map(store.dispatch).startWith(store.getState());
14 |
15 | state$.subscribe(
16 | (state) => { React.render(
13 | 14 | {state.counter} 15 | 16 | 17 | 18 | 19 |
20 | ); 21 | } 22 | } 23 | 24 | Counter.propTypes = { 25 | state: PropTypes.object.isRequired, 26 | intent$: PropTypes.object.isRequired 27 | }; 28 | 29 | export default Counter; -------------------------------------------------------------------------------- /examples/universal-counter/webpack/dev-server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var WebpackDevServer = require("webpack-dev-server"); 4 | var webpack = require("webpack"); 5 | var config = require('./webpack.config.client'); 6 | 7 | var compiler = webpack(config); 8 | 9 | var server = new WebpackDevServer(compiler, { 10 | watchOptions: { 11 | aggregateTimeout: 1500, 12 | poll: 1000 13 | }, 14 | stats: { colors: true } 15 | }); 16 | 17 | server.listen(8080, "localhost", function() {}); -------------------------------------------------------------------------------- /examples/universal-counter/webpack/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | var path = require('path'); 5 | 6 | var plugins = [ 7 | new webpack.DefinePlugin({ 8 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 9 | }) 10 | ]; 11 | 12 | if (process.env.NODE_ENV === 'production') { 13 | plugins.push( 14 | new webpack.optimize.UglifyJsPlugin({ 15 | compressor: { 16 | screw_ie8: true 17 | } 18 | }) 19 | ); 20 | 21 | plugins.push(new webpack.optimize.OccurenceOrderPlugin()); 22 | } 23 | 24 | var client = { 25 | entry: { 26 | client: './src/client/' 27 | }, 28 | output: { 29 | path: path.join(__dirname, '..', 'build', 'assets', 'js'), 30 | filename: 'bundle.js' 31 | }, 32 | resolve: { 33 | alias: { 34 | 'rx': 'rx-lite' 35 | }, 36 | extensions: ['', '.js'] 37 | }, 38 | module: { 39 | loaders: [ 40 | { 41 | test: /\.js$/, 42 | loaders: ['babel-loader'], 43 | exclude: /node_modules/ 44 | } 45 | ] 46 | }, 47 | plugins: plugins 48 | }; 49 | 50 | module.exports = client; -------------------------------------------------------------------------------- /examples/universal-counter/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var server = require('./webpack.config.server'); 4 | var client = require('./webpack.config.client'); 5 | 6 | module.exports = [server, client]; -------------------------------------------------------------------------------- /examples/universal-counter/webpack/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | var path = require('path'); 5 | var fs = require('fs'); 6 | 7 | var plugins = [ 8 | new webpack.DefinePlugin({ 9 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 10 | }) 11 | ]; 12 | 13 | if (process.env.NODE_ENV === 'production') { 14 | plugins.push( 15 | new webpack.optimize.UglifyJsPlugin({ 16 | compressor: { 17 | screw_ie8: true 18 | } 19 | }) 20 | ); 21 | 22 | plugins.push(new webpack.optimize.OccurenceOrderPlugin()); 23 | } 24 | 25 | var nodeModules = {}; 26 | fs.readdirSync('node_modules') 27 | .filter(function(x) { 28 | return ['.bin'].indexOf(x) === -1; 29 | }) 30 | .forEach(function(mod) { 31 | nodeModules[mod] = 'commonjs ' + mod; 32 | }); 33 | 34 | var server = { 35 | entry: { 36 | server: './src/server/' 37 | }, 38 | target: 'node', 39 | output: { 40 | path: path.join(__dirname, '..', 'build', 'server'), 41 | filename: '[name].js' 42 | }, 43 | resolve: { 44 | alias: { 45 | 'rx': 'rx-lite' 46 | }, 47 | extensions: ['', '.js'] 48 | }, 49 | module: { 50 | loaders: [ 51 | { 52 | test: /\.js$/, 53 | loaders: ['babel-loader'], 54 | exclude: /node_modules/ 55 | }, 56 | { 57 | test: /\.json$/, loader: 'json-loader' 58 | } 59 | ] 60 | }, 61 | plugins: plugins, 62 | externals: nodeModules 63 | }; 64 | 65 | module.exports = server; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-core", 3 | "version": "0.1.1", 4 | "description": "Redux core", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "clean": "rimraf lib dist", 8 | "build:lib": "babel src --out-dir lib", 9 | "build:umd": "webpack src/index.js dist/redux-core.js --display-modules --progress && NODE_ENV=production webpack src/index.js dist/redux-core.min.js --display-modules --progress", 10 | "build": "npm run build:lib && npm run build:umd", 11 | "lint": "eslint src", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/jas-chen/redux-core.git" 17 | }, 18 | "keywords": [ 19 | "redux", 20 | "flux" 21 | ], 22 | "author": "Jas Chen", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/jas-chen/redux-core/issues" 26 | }, 27 | "homepage": "https://github.com/jas-chen/redux-core#readme", 28 | "devDependencies": { 29 | "babel-core": "^5.8.3", 30 | "babel-eslint": "^4.0.5", 31 | "babel-loader": "^5.3.2", 32 | "eslint": "^0.24.1", 33 | "eslint-config-airbnb": "0.0.6", 34 | "rimraf": "^2.4.2", 35 | "webpack": "^1.10.3" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/createStore.js: -------------------------------------------------------------------------------- 1 | import isPlainObject from './utils/isPlainObject'; 2 | 3 | export default function createStore(reducer, initState) { 4 | if (typeof reducer !== 'function') { 5 | throw new Error('Expected the reducer to be a function.'); 6 | } 7 | 8 | const initAction = {type: '@@redux-core/INIT_' + (new Date()).getTime()}; 9 | let currentReducer = reducer; 10 | let state = currentReducer(initState, initAction); 11 | 12 | function dispatch(action) { 13 | if (!isPlainObject(action)) { 14 | throw new Error('Action must be a plain object.'); 15 | } 16 | 17 | state = currentReducer(state, action); 18 | return state; 19 | } 20 | 21 | function replaceReducer(newReducer) { 22 | currentReducer = newReducer; 23 | dispatch(initAction); 24 | } 25 | 26 | return { 27 | getState: () => state, 28 | dispatch, 29 | getReducer: () => currentReducer, 30 | replaceReducer 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import createStore from './createStore'; 2 | import combineReducers from './utils/combineReducers'; 3 | 4 | export default { 5 | createStore, 6 | combineReducers 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/combineReducers.js: -------------------------------------------------------------------------------- 1 | function pickReducer(reducers) { 2 | return Object.keys(reducers).reduce((result, key) => { 3 | const reducer = reducers[key]; 4 | if (typeof reducer === 'function') { 5 | result[key] = reducer; 6 | } 7 | 8 | return result; 9 | }, {}); 10 | } 11 | 12 | export default function combineReducers(reducers) { 13 | const finalReducers = pickReducer(reducers); 14 | const keys = Object.keys(finalReducers); 15 | 16 | return (state = {}, action = undefined) => { 17 | return keys.reduce((result, key) => { 18 | result[key] = finalReducers[key](state[key], action); 19 | return result; 20 | }, {}); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/isPlainObject.js: -------------------------------------------------------------------------------- 1 | /* 2 | https://github.com/gaearon/redux/blob/breaking-changes-1.0/src/utils/isPlainObject.js 3 | */ 4 | 5 | function fnToString(fn) { return Function.prototype.toString.call(fn); } 6 | 7 | /** 8 | * @param {any} obj The object to inspect. 9 | * @returns {boolean} True if the argument appears to be a plain object. 10 | */ 11 | export default function isPlainObject(obj) { 12 | if (!obj || typeof obj !== 'object') { 13 | return false; 14 | } 15 | 16 | const proto = typeof obj.constructor === 'function' ? Object.getPrototypeOf(obj) : Object.prototype; 17 | 18 | if (proto === null) { 19 | return true; 20 | } 21 | 22 | const constructor = proto.constructor; 23 | 24 | return typeof constructor === 'function' 25 | && constructor instanceof constructor 26 | && fnToString(constructor) === fnToString(Object); 27 | } 28 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | 5 | var plugins = [ 6 | new webpack.DefinePlugin({ 7 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 8 | }), 9 | new webpack.optimize.OccurenceOrderPlugin() 10 | ]; 11 | 12 | if (process.env.NODE_ENV === 'production') { 13 | plugins.push( 14 | new webpack.optimize.UglifyJsPlugin({ 15 | compressor: { 16 | screw_ie8: true, 17 | warnings: false 18 | } 19 | }) 20 | ); 21 | } 22 | 23 | module.exports = { 24 | entry: './src/index', 25 | output: { 26 | library: 'RxRedux', 27 | libraryTarget: 'umd' 28 | }, 29 | module: { 30 | loaders: [ 31 | {test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/} 32 | ] 33 | }, 34 | plugins: plugins, 35 | resolve: { 36 | extensions: ['', '.js'] 37 | } 38 | }; --------------------------------------------------------------------------------