├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── __tests__
├── index.test.js
├── reducerWrapper.test.js
└── registry.test.js
├── circle.yml
├── docs
├── _config.yml
└── logo.png
├── index.js
├── package.json
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"]
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = space
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [**.{json,js,ts}]
11 | indent_size = 2
12 |
13 | [*.md]
14 | insert_final_newline = false
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "standard"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # editor-specific
2 | .vscode
3 | .idea
4 |
5 | # coverage
6 | coverage
7 |
8 | # modules
9 | node_modules
10 | bower_components
11 |
12 | # basic tmp
13 | tmp
14 |
15 | # basic build folders
16 | build
17 | build_dist
18 |
19 | # logs
20 | npm-debug.log
21 | npm-debug*.log
22 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # editor-specific
2 | .vscode
3 | .idea
4 |
5 | # coverage
6 | coverage
7 |
8 | # modules
9 | node_modules
10 | bower_components
11 |
12 | # basic tmp
13 | tmp
14 |
15 | # logs
16 | npm-debug.log
17 | npm-debug*.log
18 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 |
6 | ## [1.2.1](https://github.com/asteridux/paradux/compare/v1.2.0...v1.2.1) (2017-02-21)
7 |
8 |
9 | ### Bug Fixes
10 |
11 | * **publishing:** Publishing actually deployes build folder ([f0e1174](https://github.com/asteridux/paradux/commit/f0e1174))
12 |
13 |
14 |
15 |
16 | # [1.2.0](https://github.com/asteridux/paradux/compare/v1.1.0...v1.2.0) (2017-01-27)
17 |
18 |
19 | ### Features
20 |
21 | * **all:** Paradux tests and trimming down middleware options ([a5173e3](https://github.com/asteridux/paradux/commit/a5173e3))
22 | * **compile:** Babel-only compile, removal of webpack ([6c40580](https://github.com/asteridux/paradux/commit/6c40580))
23 |
24 |
25 |
26 |
27 | # 1.1.0 (2017-01-20)
28 |
29 |
30 | ### Features
31 |
32 | * **all:** Setup entire project ([5171bc2](https://github.com/asteridux/paradux/commit/5171bc2))
33 |
34 |
35 |
36 |
37 | # 1.0.0 (2017-01-18)
38 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing To Project
2 |
3 | Please refer to [Standard Version](https://github.com/conventional-changelog/standard-version) for commit reference.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Antonin Januska
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Paradux
4 |
5 | [](https://circleci.com/gh/AntJanus/paradux:Why)
6 | [](http://standardjs.com/)
7 |
8 | ## Why?
9 |
10 | When bootstrapping Redux, Redux assumes that all of your reducers are ready right then and there. There is no way to add new reducers, no way to deregister them, and no way to manipulate interceptors.
11 |
12 | And while the immutable nature of "available Reducers" is great, there are some caveats to this process which Paradux tries to solve.
13 |
14 | ### Self-bootstrapping
15 |
16 | Paradux can create an instance of itself in a module file and while it's being bootstrapped into a Redux store, reducers can register themselves to Paradux like so:
17 |
18 | ```js
19 | // a paradux setup file
20 | import Paradux from 'paradux';
21 | import defaultReducers from './defaults'; // reducers that are always present
22 |
23 | export default new Paradux(defaultReducers);
24 | ```
25 |
26 | And additional reducers need only to require that module and register themselves and be immediately available to Redux:
27 |
28 | ```js
29 | // a random reducer file
30 | import paradux from '../paradux';
31 |
32 | function reducer(state, action) {
33 | // ... all kinds of logic goes here
34 | }
35 |
36 | paradux.register(reducer); // no need to export due to module singleton nature
37 | ```
38 |
39 | This means that reducers and reducer logic can be kept close to the modules and components it actually affects. Adding new component/modules/reducers doesn't require bloated import statements during the Redux bootstrap process.
40 |
41 | It's a little bit like dependency inversion where the store doesn't "require" all the reducers. The reducers, instead, register themselves.
42 |
43 | ### Code-splitting friendly
44 |
45 | Redux, right now, is not very friendly to code-splitting. Making a reducer available down the line is pretty much unheard of; however, there are plenty of cases for it.
46 |
47 | Since code-splitting requires asynchrony, it can't be used directly within reducers and that's okay. So where does one split? You can't. With Paradux, you can!
48 |
49 | Let's imagine a React component that requires complex reducer logic but only for a subset of certain routes. We can use require ensure for this!
50 |
51 | ```js
52 | // sample route config file
53 | import paradux from './paradux';
54 |
55 | export default {
56 | component: App,
57 | childRoutes: [
58 | {
59 | path: '/admin',
60 | getComponent(location, cb) {
61 | require.ensure('./adminReducers', function(require) {
62 | var adminReducers = require('./adminReducers');
63 | paradux.register(adminReducers);
64 | })
65 | }
66 | }
67 | ]
68 | }
69 | ```
70 |
71 | ### Cleanup friendly
72 |
73 | Ever find yourself in a situation where you don't need reducer logic available anymore? Let's consider the previous example and disregard the code splitting portion. Our `adminReducers` are complex, have a ton of logic in them, a ton of switch/case statements. And all of that because they run almost an entirely separate app for a user that is able to login.
74 |
75 | But what if you logged out? Let's consider the following action:
76 |
77 |
78 | ```js
79 | import paradux from './paradox';
80 |
81 | export function logoutUser() {
82 | return (dispatch) => {
83 | return fetch('/api/logout')
84 | .then((res) => res.toJSON())
85 | .then(() => {
86 | paradux.deregisterByNamespace('adminReducers');
87 |
88 | // admin reducers no longer available or run.
89 | dispatch(userLoggedOut());
90 | })
91 | ;
92 | }
93 | }
94 | ```
95 |
96 | The only requirement for deregistration by namespace is to specify the namespace in the first place:
97 |
98 | ```js
99 | paradux.register(adminReducers, 'adminReducers');
100 | ```
101 |
102 | ### Middleware - all of the above advantages (WIP)
103 |
104 | The cool thing is that Paradux allows you to add and run middleware whenever you want to as well. You can add logger middleware when you want to, start DevTools when you want to, and so on. On demand and only when needed.
105 |
106 | This opens up a whole new world of possibilities like having toggleable `debug` functionality, logging bootup on certain actions, registering/deregistering middleware on the fly for various situations.
107 |
108 | ## Installation and setup
109 |
110 | Installation:
111 |
112 | ```bash
113 | npm install paradux --save
114 | ```
115 |
116 | And I suggest creating a separate file for the paradux instance for easy importing:
117 |
118 | ```js
119 | // bootstrap.js file or something similar
120 | import Paradux from 'paradux';
121 | import defaultReducers from './reducers'; //import any reducers you always want present
122 |
123 | export default const paradux = new Paradux(defaultReducers);
124 | ```
125 |
126 | You can then import it into your redux store:
127 |
128 | ```js
129 | import { createStore } from 'redux';
130 | import paradux from './bootstrap';
131 |
132 | let store = createStore(paradux.reducerWrapper());
133 | ```
134 |
135 | ### Adding reducers
136 |
137 | Adding reducers is easy. Given the setup above, you simply need to import the new instance of Paradux and use the `register` function
138 |
139 | ```js
140 | import paradux from './bootstrap';
141 |
142 | function myReducer(state, action) {
143 | //some logic
144 | }
145 |
146 | paradux.register(myReducer);
147 | ```
148 |
149 | You can also register a reducer by namespace:
150 |
151 | ```js
152 | paradux.register(myReducer, 'my reducer');
153 | ```
154 |
155 | ### Removing reducer
156 |
157 | There are several ways of removing a reducer from the reducer collection in Paradux.
158 |
159 | First, via the returned handler:
160 |
161 | ```js
162 | const removeReducer = paradux.register(myReducer);
163 |
164 | //when you need to remove it
165 | removeReducer();
166 | ```
167 |
168 | Second, if you still have the original reducer handy, you can use that to deregister:
169 |
170 | ```js
171 | paradux.redergister(myReducer);
172 | ```
173 |
174 | Third, if you specified a namespace, you can deregister by namespace:
175 |
176 | ```js
177 | paradux.deregisterByNamespace('my reducer');
178 | ```
179 |
180 | ### Non-removable reducers
181 |
182 | There's always a need for reducers that cannot be removed, there are two ways of adding these.
183 |
184 | First, via initialization:
185 |
186 | ```js
187 | var paradux = new Paradux(arrayOfReducersThatCannotBeRemoved);
188 | ```
189 |
190 | And second, via initializing the reducerWrapper:
191 |
192 | ```js
193 | createStore(paradux.reducerWrapper(arrayOfReducersThatCannotBeRemoved));
194 | ```
195 |
196 | You can use a mix and match of either techniques or both at the same time.
197 |
--------------------------------------------------------------------------------
/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | import Paradux from '../index'
2 |
3 | describe('Core functionality test', () => {
4 | const paradux = new Paradux([])
5 |
6 | it('should have an empty collection of reducers', () => {
7 | expect(paradux._reducers.length).toEqual(0)
8 | })
9 |
10 | it('should register a reducer successfully', () => {
11 | var func = function () { }
12 |
13 | paradux.register(func)
14 |
15 | expect(paradux._reducers.indexOf(func)).toBeGreaterThan(-1)
16 | })
17 |
18 | it('should register/deregister a reducer successuflly', () => {
19 | var func = () => { }
20 | var deregister = paradux.register(func)
21 |
22 | expect(paradux._reducers.indexOf(func)).toBeGreaterThan(-1)
23 |
24 | deregister()
25 |
26 | expect(paradux._reducers.indexOf(func)).toBeLessThan(0)
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/__tests__/reducerWrapper.test.js:
--------------------------------------------------------------------------------
1 | import Paradux from '../index'
2 |
3 | describe('Reducer wrapper test', () => {
4 | it('should return plain state if no reducers registered', () => {
5 | const paradux = new Paradux()
6 | const wrapper = paradux.reducerWrapper()
7 |
8 | var state = { test: 'no val' }
9 | var action = {
10 | type: 'FOO',
11 | payload: {
12 | test: 'val'
13 | }
14 | }
15 |
16 | expect(wrapper(state, action)).toEqual(state)
17 | })
18 |
19 | it('should modify state based on initial reducer', () => {
20 | const REPLACE = 'REPLACE'
21 | const paradux = new Paradux()
22 | const wrapper = paradux.reducerWrapper([ (state, action) => {
23 | if (action.type === REPLACE) {
24 | return action.payload
25 | }
26 |
27 | return state
28 | }])
29 |
30 | var state = { test: 'no val' }
31 | var action = {
32 | type: REPLACE,
33 | payload: {
34 | test: 'val'
35 | }
36 | }
37 |
38 | var newState = wrapper(state, action)
39 |
40 | expect(newState).toBeDefined()
41 | expect(newState.test).toEqual('val')
42 | })
43 |
44 | it('should modify state depending on currently-available reducers', () => {
45 | const REPLACE = 'REPLACE'
46 | const EXTEND = 'EXTEND'
47 | const paradux = new Paradux()
48 | const wrapper = paradux.reducerWrapper()
49 |
50 | const replacer = (state, action) => {
51 | if (action.type === REPLACE) {
52 | return action.payload
53 | }
54 |
55 | return state
56 | }
57 |
58 | const extender = (state, action) => {
59 | if (action.type === EXTEND) {
60 | return Object.assign({}, state, action.payload)
61 | }
62 |
63 | return state
64 | }
65 |
66 | var state = { test: 'no val' }
67 | var replaceAction = {
68 | type: REPLACE,
69 | payload: {
70 | test: 'val'
71 | }
72 | }
73 |
74 | var extendAction = {
75 | type: EXTEND,
76 | payload: {
77 | test2: 'val'
78 | }
79 | }
80 |
81 | paradux.register(replacer)
82 |
83 | var newState = wrapper(state, replaceAction)
84 |
85 | expect(newState).toBeDefined()
86 | expect(newState.test).toEqual('val')
87 | expect(newState.test2).toBeUndefined()
88 |
89 | paradux.deregister(replacer)
90 | paradux.register(extender)
91 |
92 | newState = wrapper(state, extendAction)
93 |
94 | expect(newState).toBeDefined()
95 | expect(newState.test).toEqual('no val')
96 | expect(newState.test2).toEqual('val')
97 | })
98 | })
99 |
--------------------------------------------------------------------------------
/__tests__/registry.test.js:
--------------------------------------------------------------------------------
1 | import Paradux from '../index'
2 |
3 | describe('Registry test', () => {
4 | it('should have an empty registry', () => {
5 | const paradux = new Paradux([])
6 |
7 | expect(Object.keys(paradux._registry).length).toEqual(0)
8 | })
9 |
10 | it('should register a reducer successfully', () => {
11 | const paradux = new Paradux([])
12 |
13 | var func = function () { }
14 |
15 | paradux.register(func, 'func')
16 |
17 | expect(paradux._registry.func).toBeDefined()
18 | })
19 |
20 | it('should register/deregister a reducer successuflly via registry', () => {
21 | const paradux = new Paradux([])
22 |
23 | var func = () => { }
24 | var deregister = paradux.register(func, 'func')
25 |
26 | expect(paradux._registry.func).toBeDefined()
27 |
28 | paradux.deregisterByNamespace('func')
29 |
30 | expect(paradux._registry.func).toBeUndefined()
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 6.9.1
4 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/docs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asteridux/paradux/20b07a9f2eb5131c427f687e86dbeb9071fe8c03/docs/logo.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | export default class Paradux {
2 |
3 | constructor (initialReducers = []) {
4 | this._reducers = initialReducers
5 | this._registry = {}
6 |
7 | this.reducerWrapper = this.reducerWrapper.bind(this)
8 | this.register = this.register.bind(this)
9 | this.deregister = this.deregister.bind(this)
10 | }
11 |
12 | reducerWrapper (initReducers = []) {
13 | this._reducers = this._reducers.concat(initReducers)
14 |
15 | return (state, action) => {
16 | return this._reducers.reduce((collectiveState, reducer) => {
17 | return reducer(collectiveState, action)
18 | }, state)
19 | }
20 | }
21 |
22 | register (reducer, namespace) {
23 | this._reducers.push(reducer)
24 |
25 | var deregister = this.deregister(reducer, namespace)
26 |
27 | if (namespace) {
28 | this._registry[namespace] = {
29 | reducer,
30 | deregister
31 | }
32 | }
33 |
34 | return deregister
35 | }
36 |
37 | deregister (reducer, namespace) {
38 | return () => {
39 | this._reducers.splice(this._reducers.indexOf(reducer), 1)
40 |
41 | if (namespace) {
42 | delete this._registry[namespace]
43 | }
44 |
45 | return true
46 | }
47 | }
48 |
49 | deregisterByNamespace (namespace) {
50 | this._registry[namespace].deregister()
51 |
52 | delete this._registry[namespace]
53 |
54 | return true
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "paradux",
3 | "version": "1.2.1",
4 | "description": "A Redux middleware that adds uncertainty",
5 | "main": "./build/lib.js",
6 | "scripts": {
7 | "release": "standard-version",
8 | "prepublish": "npm run build",
9 | "prebuild": "rimraf build && mkdir build",
10 | "build": "babel index.js --out-file ./build/lib.js",
11 | "test": "jest",
12 | "test:watch": "jest --watch"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/asteridux/paradux.git"
17 | },
18 | "author": "Antonin Januska ",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/asteridux/paradux/issues"
22 | },
23 | "homepage": "https://github.com/asteridux/paradux#readme",
24 | "devDependencies": {
25 | "babel-cli": "6.22.2",
26 | "babel-core": "6.21.0",
27 | "babel-jest": "18.0.0",
28 | "babel-preset-env": "1.1.8",
29 | "eslint-config-standard": "6.2.1",
30 | "eslint-plugin-promise": "3.4.0",
31 | "eslint-plugin-standard": "2.0.1",
32 | "jest": "18.1.0",
33 | "standard": "8.6.0"
34 | },
35 | "jest": {
36 | "bail": true,
37 | "collectCoverage": true,
38 | "verbose": true
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | cache: true,
6 | entry: './index.js',
7 | output: {
8 | path: path.resolve('./build'),
9 | filename: 'lib.js'
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.js?$/,
15 | exclude: /(node_modules)/,
16 | loader: 'babel-loader',
17 | options: {
18 | cacheDirectory: true
19 | }
20 | },
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------