Commit 322b3c36 authored by René Jochum's avatar René Jochum

Initial commit

parents
const { NODE_ENV } = process.env
module.exports = {
presets: [
'@babel/typescript',
[
'@babel/env',
{
targets: {
browsers: ['ie >= 11']
},
exclude: ['transform-async-to-generator', 'transform-regenerator'],
modules: false,
loose: true
}
]
],
plugins: [
// don't use `loose` mode here - need to copy symbols when spreading
'@babel/proposal-object-rest-spread',
NODE_ENV === 'test' && '@babel/transform-modules-commonjs'
].filter(Boolean)
}
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
module.exports = {
extends: 'react-app',
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
settings: {
react: {
version: '16.8'
},
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx']
},
'import/resolver': {
// use <root>/tsconfig.json
typescript: {}
}
},
rules: {
'jsx-a11y/href-no-hash': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
vars: 'all',
args: 'after-used',
ignoreRestSiblings: true,
argsIgnorePattern: '^_' // ignore unused variables whose name is '_'
}
]
}
}
node_modules
coverage
dist
lib
es
types
\ No newline at end of file
src
tests
coverage
.*
*.log
language: node_js
node_js:
- node
- 7
- 6
- 5
notifications:
email: false
sudo: false
script:
- npm run test
- npm run lint
- npm run build
after_success:
- rm -fr ./dist
- NODE_ENV=production npm run build
- semantic-release pre && npm publish && semantic-release post
Copyright (c) 2016, Gajus Kuizinas (http://gajus.com/)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Gajus Kuizinas (http://gajus.com/) nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# `redux-immutable-ts`
[![GitSpo Mentions](https://gitspo.com/badges/mentions/gajus/redux-immutable?style=flat-square)](https://gitspo.com/mentions/gajus/redux-immutable)
[![Travis build status](http://img.shields.io/travis/gajus/redux-immutable/master.svg?style=flat-square)](https://travis-ci.org/gajus/redux-immutable)
[![NPM version](http://img.shields.io/npm/v/redux-immutable.svg?style=flat-square)](https://www.npmjs.org/package/redux-immutable)
[![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical)
`redux-immutable` is used to create an equivalent function of Redux [`combineReducers`](http://redux.js.org/docs/api/combineReducers.html) that works with [Immutable.js](https://facebook.github.io/immutable-js/) state.
When Redux [`createStore`](https://github.com/reactjs/redux/blob/master/docs/api/createStore.md) `reducer` is created using `redux-immutable` then `initialState` must be an instance of [`Immutable.Collection`](https://facebook.github.io/immutable-js/docs/#/Collection).
## Problem
When [`createStore`](https://github.com/reactjs/redux/blob/v3.0.6/docs/api/createStore.md) is invoked with `initialState` that is an instance of `Immutable.Collection` further invocation of reducer will [produce an error](https://github.com/reactjs/redux/blob/v3.0.6/src/combineReducers.js#L31-L38):
> The initialState argument passed to createStore has unexpected type of "Object".
> Expected argument to be an object with the following keys: "data"
This is because Redux `combineReducers` [treats `state` object as a plain JavaScript object](https://github.com/reactjs/redux/blob/v3.0.6/src/combineReducers.js#L120-L129).
`combineReducers` created using `redux-immutable` uses Immutable.js API to iterate the state.
## Usage
Create a store with `initialState` set to an instance of [`Immutable.Collection`](https://facebook.github.io/immutable-js/docs/#/Collection):
```js
import { combineReducers } from "redux-immutable";
import { createStore } from "redux";
const initialState = Immutable.Map();
const rootReducer = combineReducers({});
const store = createStore(rootReducer, initialState);
```
By default, if `state` is `undefined`, `rootReducer(state, action)` is called with `state = Immutable.Map()`. A different default function can be provided as the second parameter to `combineReducers(reducers, getDefaultState)`, for example:
```js
const StateRecord = Immutable.Record({
foo: "bar"
});
const rootReducer = combineReducers({ foo: fooReducer }, StateRecord);
// rootReducer now has signature of rootReducer(state = StateRecord(), action)
// state now must always have 'foo' property with 'bar' as its default value
```
When using `Immutable.Record` it is possible to delegate default values to child reducers:
```js
const StateRecord = Immutable.Record({
foo: undefined
});
const rootReducer = combineReducers({ foo: fooReducer }, StateRecord);
// state now must always have 'foo' property with its default value returned from fooReducer(undefined, action)
```
In general, `getDefaultState` function must return an instance of `Immutable.Record` or `Immutable.Collection` that implements `get`, `set` and `withMutations` methods. Such collections are `List`, `Map` and `OrderedMap`.
### Using with `react-router-redux` v4 and under
`react-router-redux` [`routeReducer`](https://github.com/reactjs/react-router-redux/tree/v4.0.2#routerreducer) does not work with Immutable.js. You need to use a custom reducer:
```js
import Immutable from "immutable";
import { LOCATION_CHANGE } from "react-router-redux";
const initialState = Immutable.fromJS({
locationBeforeTransitions: null
});
export default (state = initialState, action) => {
if (action.type === LOCATION_CHANGE) {
return state.set("locationBeforeTransitions", action.payload);
}
return state;
};
```
Pass a selector to access the payload state and convert it to a JavaScript object via the [`selectLocationState` option on `syncHistoryWithStore`](https://github.com/reactjs/react-router-redux/tree/v4.0.2#history--synchistorywithstorehistory-store-options):
```js
import { browserHistory } from "react-router";
import { syncHistoryWithStore } from "react-router-redux";
const history = syncHistoryWithStore(browserHistory, store, {
selectLocationState(state) {
return state.get("routing").toJS();
}
});
```
The `'routing'` path depends on the `rootReducer` definition. This example assumes that `routeReducer` is made available under `routing` property of the `rootReducer`.
### Using with `react-router-redux` v5
To make [`react-router-redux` v5](https://github.com/ReactTraining/react-router/tree/master/packages/react-router-redux) work with Immutable.js you only need to use a custom reducer:
```js
import { Map } from "immutable";
import { LOCATION_CHANGE } from "react-router-redux";
const initialState = Map({
location: null,
action: null
});
export function routerReducer(
state = initialState,
{ type, payload = {} } = {}
) {
if (type === LOCATION_CHANGE) {
const location = payload.location || payload;
const action = payload.action;
return state.set("location", location).set("action", action);
}
return state;
}
```
{
"name": "@minadmin/redux-immutable-ts",
"version": "0.1.0-dev0",
"description": "redux-immutable-ts is used to create an equivalent function of Redux combineReducers that works with Immutable.js state which is written in typescript.",
"main": "lib/redux-immutable-ts.js",
"unpkg": "dist/redux-immutable-ts.js",
"module": "es/redux-immutable-ts.js",
"types": "types/index.d.ts",
"files": [
"dist",
"lib",
"es",
"src",
"types"
],
"repository": {
"type": "git",
"url": "https://git.webmeisterei.com/minadmin/redux-immutable-ts"
},
"keywords": [
"immutable",
"redux"
],
"author": {
"name": "Gajus Kuizinas",
"email": "gajus@anuary.com",
"url": "http://gajus.com"
},
"license": "BSD-3-Clause",
"peerDependencies": {
"immutable": "^4.0.0-rc.12",
"redux": "^4.0.5"
},
"devDependencies": {
"@babel/cli": "^7.7.7",
"@babel/core": "^7.7.7",
"@babel/node": "^7.7.7",
"@babel/plugin-external-helpers": "^7.7.4",
"@babel/plugin-proposal-object-rest-spread": "^7.7.7",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.7",
"@babel/preset-flow": "^7.7.4",
"@babel/preset-typescript": "^7.7.7",
"@babel/register": "^7.7.7",
"@rollup/plugin-node-resolve": "^6.0.0",
"@types/jest": "^24.0.25",
"@types/node": "^13.1.1",
"@typescript-eslint/eslint-plugin": "^2.13.0",
"@typescript-eslint/parser": "^2.13.0",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"babel-plugin-add-module-exports": "^1.0.2",
"cross-env": "^6.0.3",
"eslint": "^6.8.0",
"eslint-config-react-app": "^5.1.0",
"eslint-import-resolver-typescript": "^2.0.0",
"eslint-plugin-flowtype": "^4.5.2",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-react-hooks": "^2.3.0",
"glob": "^7.1.6",
"immutable": "^4.0.0-rc.12",
"jest": "^24.9.0",
"prettier": "^1.19.1",
"redux": "^4.0.5",
"rimraf": "^3.0.0",
"rollup": "^1.27.14",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-terser": "^5.1.3",
"rollup-plugin-typescript2": "^0.25.3",
"rxjs": "^6.5.4",
"typescript": "^3.7.4",
"babel-preset-es2015": "^6.24.1"
},
"scripts": {
"clean": "rimraf lib dist es coverage types",
"format": "prettier --write \"{src,test}/**/*.{js,ts}\" index.d.ts \"**/*.md\"",
"format:check": "prettier --list-different \"{src,test}/**/*.{js,ts}\" index.d.ts \"**/*.md\"",
"lint": "eslint --ext js,ts src",
"check-types": "tsc --noEmit",
"pretest": "yarn run build",
"test": "jest",
"test:watch": "test -- --watch",
"test:cov": "test -- --coverage",
"build": "rollup -c",
"prepare": "yarn run clean && yarn run check-types && yarn run format:check && yarn run lint"
},
"dependencies": {
"@babel/runtime": "^7.7.7"
}
}
import nodeResolve from '@rollup/plugin-node-resolve'
import babel from 'rollup-plugin-babel'
import replace from 'rollup-plugin-replace'
import typescript from 'rollup-plugin-typescript2'
import { terser } from 'rollup-plugin-terser'
import pkg from './package.json'
const extensions = ['.ts']
const noDeclarationFiles = { compilerOptions: { declaration: false } }
const babelRuntimeVersion = pkg.dependencies['@babel/runtime'].replace(
/^[^0-9]*/,
''
)
const makeExternalPredicate = externalArr => {
if (externalArr.length === 0) {
return () => false
}
const pattern = new RegExp(`^(${externalArr.join('|')})($|/)`)
return id => pattern.test(id)
}
export default [
// CommonJS
{
input: 'src/index.ts',
output: { file: 'lib/redux-immutable-ts.js', format: 'cjs', indent: false },
external: makeExternalPredicate([
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {})
]),
plugins: [
nodeResolve({
extensions
}),
typescript({ useTsconfigDeclarationDir: true }),
babel({
extensions,
plugins: [
['@babel/plugin-transform-runtime', { version: babelRuntimeVersion }]
],
runtimeHelpers: true
})
]
},
// ES
{
input: 'src/index.ts',
output: { file: 'es/redux-immutable-ts.js', format: 'es', indent: false },
external: makeExternalPredicate([
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {})
]),
plugins: [
nodeResolve({
extensions
}),
typescript({ tsconfigOverride: noDeclarationFiles }),
babel({
extensions,
plugins: [
[
'@babel/plugin-transform-runtime',
{ version: babelRuntimeVersion, useESModules: true }
]
],
runtimeHelpers: true
})
]
},
// ES for Browsers
{
input: 'src/index.ts',
output: { file: 'es/redux-immutable-ts.mjs', format: 'es', indent: false },
plugins: [
nodeResolve({
extensions
}),
replace({
'process.env.NODE_ENV': JSON.stringify('production')
}),
typescript({ tsconfigOverride: noDeclarationFiles }),
babel({
extensions,
exclude: 'node_modules/**'
}),
terser({
compress: {
pure_getters: true,
unsafe: true,
unsafe_comps: true,
warnings: false
}
})
]
},
// UMD Development
{
input: 'src/index.ts',
output: {
file: 'dist/redux-immutable-ts.js',
format: 'umd',
name: 'Redux',
indent: false
},
plugins: [
nodeResolve({
extensions
}),
typescript({ tsconfigOverride: noDeclarationFiles }),
babel({
extensions,
exclude: 'node_modules/**'
}),
replace({
'process.env.NODE_ENV': JSON.stringify('development')
})
]
},
// UMD Production
{
input: 'src/index.ts',
output: {
file: 'dist/redux-immutable-ts.min.js',
format: 'umd',
name: 'Redux',
indent: false
},
plugins: [
nodeResolve({
extensions
}),
typescript({ tsconfigOverride: noDeclarationFiles }),
babel({
extensions,
exclude: 'node_modules/**'
}),
replace({
'process.env.NODE_ENV': JSON.stringify('production')
}),
terser({
compress: {
pure_getters: true,
unsafe: true,
unsafe_comps: true,
warnings: false
}
})
]
}
]
import { AnyAction, ReducersMapObject } from "redux";
import Immutable from "immutable";
import {
getUnexpectedInvocationParameterMessage,
validateNextState
} from "./utilities";
export default (
reducers: ReducersMapObject<any, AnyAction>,
getDefaultState: () => Immutable.Map<any, any> = Immutable.Map
): Function => {
const reducerKeys = Object.keys(reducers);
let defaultState = Immutable.Map();
const fn = getDefaultState as Function;
if (typeof getDefaultState !== "undefined") {
defaultState = fn();
}
// eslint-disable-next-line space-infix-ops
return (
inputState: Immutable.Map<any, any> = defaultState,
action: AnyAction
): Immutable.Map<any, any> => {
// eslint-disable-next-line no-process-env
if (process.env.NODE_ENV !== "production") {
const warningMessage = getUnexpectedInvocationParameterMessage(
inputState,
reducers,
action
);
if (warningMessage) {
// eslint-disable-next-line no-console
console.error(warningMessage);
}
}
return inputState.withMutations(temporaryState => {
reducerKeys.forEach(reducerName => {
const reducer = reducers[reducerName];
const currentDomainState = temporaryState.get(reducerName);
const nextDomainState = reducer(currentDomainState, action);
validateNextState(nextDomainState, reducerName, action);
temporaryState.set(reducerName, nextDomainState);
});
});
};
};
import combineReducers from "./combineReducers";
export { combineReducers };
import { AnyAction } from "redux";
export default (action: AnyAction): string => {
return action && action.type === "@@redux/INIT"
? "initialState argument passed to createStore"
: "previous state received by the reducer";
};
import Immutable from "immutable";
import getStateName from "./getStateName";
import { AnyAction } from "redux";
export default (
state: Immutable.Map<any, any>,
reducers: Object,
action: AnyAction
) => {
const reducerNames = Object.keys(reducers);
if (!reducerNames.length) {
return "Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.";
}
const stateName = getStateName(action);
if (
Immutable.isImmutable
? !Immutable.isImmutable(state)
: !Immutable.isCollection(state)
) {
return (
"The " +
stateName +
' is of unexpected type. Expected argument to be an instance of Immutable.Collection or Immutable.Record with the following properties: "' +
reducerNames.join('", "') +
'".'
);
}
const unexpectedStatePropertyNames = state
.toSeq()
.keySeq()
.toArray()
.filter(name => {
return !reducers.hasOwnProperty(name);
});
if (unexpectedStatePropertyNames.length > 0) {
return (
"Unexpected " +
(unexpectedStatePropertyNames.length === 1 ? "property" : "properties