This commit is contained in:
Navneet Gupta 2018-12-27 21:16:10 -08:00 коммит произвёл GitHub
Родитель fef0e18680
Коммит 31363af6f7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
152 изменённых файлов: 85190 добавлений и 86553 удалений

4
.prettierignore Normal file
Просмотреть файл

@ -0,0 +1,4 @@
node_modules
dist
lib
etc

17
.vscode/extensions.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,17 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"esbenp.prettier-vscode",
"coenraads.bracket-pair-colorizer",
"kisstkondoros.vscode-codemetrics",
"orta.vscode-jest",
"wayou.vscode-todo-highlight",
"eg2.tslint",
"rbbit.typescript-hero"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
}

23
.vscode/settings.json поставляемый
Просмотреть файл

@ -1,6 +1,19 @@
{
"cSpell.words": [
"middlewares",
"reset"
]
}
"cSpell.words": ["middlewares", "reset"],
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/node_modules": true,
"**/.gitignore": true,
"**/.npmignore": true,
"**/package-lock.json": true,
"**/lib": true,
"**/etc": true,
"**/dist": true
},
"editor.formatOnPaste": true,
"editor.formatOnSave": true
}

103
README.md
Просмотреть файл

@ -1,4 +1,3 @@
<div>
<img src="docs/redux-dynamic-modules.png" alt="logo" width="100">
</img>
@ -7,102 +6,111 @@
[![Pipelines](https://dev.azure.com/redux-dynamic-modules/redux-dynamic-modules/_apis/build/status/Microsoft.redux-dynamic-modules?branchName=master)](https://dev.azure.com/redux-dynamic-modules/redux-dynamic-modules/redux-dynamic-modules%20Team/_build?definitionId=1&_a=summary) [![npm (scoped)](https://img.shields.io/npm/v/redux-dynamic-modules.svg)](https://npmjs.org/package/redux-dynamic-modules) ![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)
## What is it?
**redux-dynamic-modules** is a library that aims to make Redux Reducers and middleware easy to modular-ize and add/remove dynamically.
**redux-dynamic-modules** is a library that aims to make Redux Reducers and middleware easy to modular-ize and add/remove dynamically.
## Motivation
In large Javascript applications, it is often desired to perform some kind of code-splitting, so that the initial script size is small. However, in Redux, you are required to define your reducers and middleware up-front; there is no good way to dynamically add/remove these constructs at runtime.
**redux-dynamic-modules** is designed to make dynamically loading these constructs easier. You can define a **module**, which contains reducers and middleware, and add it to the Redux store at runtime. We also provide a React component `DynamicModuleLoader`, which can load/unload modules on mount/unmount.
Modules provide the following benefits:
* Modules can be easily re-used across the application, or between multiple similar applications.
* Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.
* Modules can be added/removed from the store dynamically, ex. when a component mounts or when a user performs an action
- Modules can be easily re-used across the application, or between multiple similar applications.
- Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.
- Modules can be added/removed from the store dynamically, ex. when a component mounts or when a user performs an action
## Features
* Group together reducers, middleware, and state into a single, re-usable module.
* Add and remove modules from a Redux store at any time.
* Use the included `<DynamicModuleLoader />` component to automatically add a module when a component is rendered
* Extensions provide integration with popular libraries, including `redux-saga` and `redux-thunk`
- Group together reducers, middleware, and state into a single, re-usable module.
- Add and remove modules from a Redux store at any time.
- Use the included `<DynamicModuleLoader />` component to automatically add a module when a component is rendered
- Extensions provide integration with popular libraries, including `redux-saga` and `redux-thunk`
## Example Scenarios
* You don't want to load the code for all your reducers up front. Define a module for some reducers and use `DynamicModuleLoader` and a library like [react-loadable](https://github.com/jamiebuilds/react-loadable) to download and add your module at runtime.
* You have some common reducers/middleware that need to be re-used in different areas of your application. Define a module and easily include it in those areas.
* You have a mono-repo that contains multiple applications which share similar state. Create a package containing some modules and re-use them across your applications
- You don't want to load the code for all your reducers up front. Define a module for some reducers and use `DynamicModuleLoader` and a library like [react-loadable](https://github.com/jamiebuilds/react-loadable) to download and add your module at runtime.
- You have some common reducers/middleware that need to be re-used in different areas of your application. Define a module and easily include it in those areas.
- You have a mono-repo that contains multiple applications which share similar state. Create a package containing some modules and re-use them across your applications
## Install
Run
Run
```
npm install redux-dynamic-modules
```
or
or
```
yarn add redux-dynamic-modules
```
## Usage
* Create a module with the following format
- Create a module with the following format
```typescript
export function getUsersModule(): IModule<IUserState> {
return {
id: "users",
reducerMap: {
users: usersReducer
},
// Actions to fire when this module is added/removed
// initialActions: [],
// finalActions: []
}
return {
id: "users",
reducerMap: {
users: usersReducer,
},
// Actions to fire when this module is added/removed
// initialActions: [],
// finalActions: []
};
}
```
* Create a `ModuleStore`
- Create a `ModuleStore`
```typescript
import {createStore, IModuleStore} from "redux-dynamic-modules";
import {getUsersModule} from "./usersModule";
import { createStore, IModuleStore } from "redux-dynamic-modules";
import { getUsersModule } from "./usersModule";
const store: IModuleStore<IState> = createStore(
/* initial state */
{},
/** enhancers **/
[],
/* initial state */
{},
/* extensions to include */
[],
/** enhancers **/
[],
getUsersModule(),
/* ...any additional modules */
/* extensions to include */
[],
getUsersModule()
/* ...any additional modules */
);
```
* Use like a standard Redux store
* Use the `DynamicModuleLoader` React component to add/remove modules when components mount/unmount
- Use like a standard Redux store
- Use the `DynamicModuleLoader` React component to add/remove modules when components mount/unmount
```jsx
<DynamicModuleLoader modules={[getHelloWorldModule()]}>
<div>Hello World!!</div>
<div>Hello World!!</div>
</DynamicModuleLoader>
```
```
## Extensions
* redux-saga - [redux-dynamic-modules-saga](https://www.npmjs.com/package/redux-dynamic-modules-saga)
* redux-thunk - [redux-dynamic-modules-thunk](https://www.npmjs.com/package/redux-dynamic-modules-thunk)
- redux-saga - [redux-dynamic-modules-saga](https://www.npmjs.com/package/redux-dynamic-modules-saga)
- redux-thunk - [redux-dynamic-modules-thunk](https://www.npmjs.com/package/redux-dynamic-modules-thunk)
## Examples
See the [Widgets](https://github.com/Microsoft/redux-dynamic-modules/tree/master/packages/widgets-example) for a quick of example of the library in practice.
Step by step walthrough of start consuming ```redux-dynamic-modules``` in the widget app. [Youtube](https://www.youtube.com/watch?v=SktRbSZ-4Tk)
Step by step walthrough of start consuming `redux-dynamic-modules` in the widget app. [Youtube](https://www.youtube.com/watch?v=SktRbSZ-4Tk)
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
@ -113,4 +121,3 @@ provided by the bot. You will only need to do this once across all repos using o
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

Просмотреть файл

@ -4,16 +4,16 @@
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
pool:
vmImage: 'Ubuntu 16.04'
vmImage: "Ubuntu 16.04"
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: NodeTool@0
inputs:
versionSpec: "8.x"
displayName: "Install Node.js"
- script: |
npm install
npm run bootstrap
npm run test
displayName: 'npm install and build'
- script: |
npm install
npm run bootstrap
npm run test
displayName: "npm install and build"

Просмотреть файл

@ -1,58 +1,62 @@
# Install
Run
Run
```
npm install redux-dynamic-modules
```
or
or
```
yarn add redux-dynamic-modules
```
# Usage
* Create a module with the following format
- Create a module with the following format
```typescript
export function getUsersModule(): IModule<IUserState> {
return {
id: "users",
reducerMap: {
users: usersReducer
},
// Actions to fire when this module is added/removed
// initialActions: [],
// finalActions: []
}
return {
id: "users",
reducerMap: {
users: usersReducer,
},
// Actions to fire when this module is added/removed
// initialActions: [],
// finalActions: []
};
}
```
* Create a `ModuleStore`
- Create a `ModuleStore`
```typescript
import {createStore, IModuleStore} from "redux-dynamic-modules";
import {getUsersModule} from "./usersModule";
import { createStore, IModuleStore } from "redux-dynamic-modules";
import { getUsersModule } from "./usersModule";
const store: IModuleStore<IState> = createStore(
/* initial state */
{},
/* initial state */
{},
/** enhancers **/
[],
/** enhancers **/
[],
/* Extensions to load */
[],
/* Extensions to load */
getUsersModule(),
/* ...any additional modules */
[],
getUsersModule()
/* ...any additional modules */
);
```
* Use like a standard Redux store
* Use the `DynamicModuleLoader` React component to add/remove modules when components mount/unmount
- Use like a standard Redux store
- Use the `DynamicModuleLoader` React component to add/remove modules when components mount/unmount
```jsx
<DynamicModuleLoader modules={modules}>
<div>Hello World!!</div>
<div>Hello World!!</div>
</DynamicModuleLoader>
```
```

Просмотреть файл

@ -1,36 +1,41 @@
[![Pipelines](https://dev.azure.com/redux-dynamic-modules/redux-dynamic-modules/_apis/build/status/Microsoft.redux-dynamic-modules?branchName=master)](https://dev.azure.com/redux-dynamic-modules/redux-dynamic-modules/redux-dynamic-modules%20Team/_build?definitionId=1&_a=summary) ![npm (scoped)](https://img.shields.io/npm/v/redux-dynamic-modules.svg) ![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)
## What is it?
**redux-dynamic-modules** is a library that aims to make Redux Reducers and middleware easy to modular-ize and add/remove dynamically.
**redux-dynamic-modules** is a library that aims to make Redux Reducers and middleware easy to modular-ize and add/remove dynamically.
## Motivation
In large Javascript applications, it is often desired to perform some kind of code-splitting, so that the initial script size is small. However, in Redux, you are required to define the your reducers and middleware up-front; there is no good way to dynamically add/remove these constructs at runtime.
**redux-dynamic-modules** is designed to make dynamically loading these constructs easier. You can define a **module**, which contains reducers and middleware, and add it to the Redux store at runtime. We also provide a React component `DynamicModuleLoader`, which can load/unload modules on mount/unmount.
Modules provide the following benefits:
* Modules can be easily re-used across the application, or between multiple similar applications.
* Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.
* Modules can be added/removed from the store dynamically, ex. when a component mounts or when a user performs an action
- Modules can be easily re-used across the application, or between multiple similar applications.
- Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.
- Modules can be added/removed from the store dynamically, ex. when a component mounts or when a user performs an action
## Features
* Group together reducers, middleware, and state into a single, re-usable module.
* Add and remove modules from a Redux store at any time.
* Use the included `<DynamicModuleLoader />` component to automatically add a module when a component is rendered
* Extensions provide integration with popular libraries, including `redux-saga` and `redux-thunk`
- Group together reducers, middleware, and state into a single, re-usable module.
- Add and remove modules from a Redux store at any time.
- Use the included `<DynamicModuleLoader />` component to automatically add a module when a component is rendered
- Extensions provide integration with popular libraries, including `redux-saga` and `redux-thunk`
## Example Scenarios
* You don't want to load the code for all your reducers up front. Define a module for some reducers and use `DynamicModuleLoader` and a library like [react-loadable](https://github.com/jamiebuilds/react-loadable) to download and add your module at runtime.
* You have some common reducers/middleware that need to be re-used in different areas of your application. Define a module and easily include it in those areas.
* You have a mono-repo that contains multiple applications which share similar state. Create a package containing some modules and re-use them across your applications
- You don't want to load the code for all your reducers up front. Define a module for some reducers and use `DynamicModuleLoader` and a library like [react-loadable](https://github.com/jamiebuilds/react-loadable) to download and add your module at runtime.
- You have some common reducers/middleware that need to be re-used in different areas of your application. Define a module and easily include it in those areas.
- You have a mono-repo that contains multiple applications which share similar state. Create a package containing some modules and re-use them across your applications
## Examples
See the [Widgets](https://github.com/Microsoft/redux-dynamic-modules/tree/master/packages/widgets-example) for a quick of example of the library in practice.
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.

Просмотреть файл

@ -1,10 +1,10 @@
<!-- _coverpage.md -->
![logo](./redux-dynamic-modules.png)
# redux-dynamic-modules
> A modular approach to Redux
[GitHub](https://github.com/Microsoft/redux-dynamic-modules/)
[Get Started](#what-is-it)
<!-- _coverpage.md -->
![logo](./redux-dynamic-modules.png)
# redux-dynamic-modules
> A modular approach to Redux
[GitHub](https://github.com/Microsoft/redux-dynamic-modules/)
[Get Started](#what-is-it)

Просмотреть файл

@ -1,12 +1,13 @@
* Getting Started
* [Quickstart](GettingStarted.md)
- Getting Started
* Guide
* [Modules](reference/Modules.md)
* [Module Store](reference/ModuleStore.md)
* [Dynamic Module Loader](reference/DynamicModuleLoader.md)
* [Usage with Redux Saga](reference/ReduxSaga.md)
* [Usage with Redux Thunk](reference/ReduxThunk.md)
* [Module Reference Counting](reference/ModuleCounting.md)
* [Extensions](reference/Extensions.md)
* [Lifecycle Actions](reference/LifecycleActions.md)
- [Quickstart](GettingStarted.md)
- Guide
- [Modules](reference/Modules.md)
- [Module Store](reference/ModuleStore.md)
- [Dynamic Module Loader](reference/DynamicModuleLoader.md)
- [Usage with Redux Saga](reference/ReduxSaga.md)
- [Usage with Redux Thunk](reference/ReduxThunk.md)
- [Module Reference Counting](reference/ModuleCounting.md)
- [Extensions](reference/Extensions.md)
- [Lifecycle Actions](reference/LifecycleActions.md)

Просмотреть файл

@ -1,7 +1,9 @@
# Dynamic Module Loader
`redux-dynamic-modules` provides a `DynamicModuleLoader` React component, which allows you to add/remove modules when a component is rendered.
## Example {docsify-ignore}
```typescript
import { DynamicModuleLoader } from "redux-dynamic-modules";
import { getNewUserDialogModule } from "./newUserDialogModule";
@ -10,10 +12,11 @@ export class NewUserDialog extends React.Component {
public render() {
return (
<DynamicModuleLoader modules={[getNewUserDialogModule()]}>
<ConnectedNewUserDialogContent/>
<ConnectedNewUserDialogContent />
</DynamicModuleLoader>
)
);
}
}
```
When `<NewUserDialog>` is rendered, the `newUserDialog` module will be added to the store. When it is unmounted, the module will be removed from the store and the state will be cleaned up.
When `<NewUserDialog>` is rendered, the `newUserDialog` module will be added to the store. When it is unmounted, the module will be removed from the store and the state will be cleaned up.

Просмотреть файл

@ -1,8 +1,10 @@
# Extensions
`redux-dynamic-modules` supports the concept of extensions, which allow for custom behavior when adding/removing modules.
`redux-dynamic-modules` supports the concept of extensions, which allow for custom behavior when adding/removing modules.
The `redux-dynamic-modules-saga` and `redux-dynamic-modules-thunk` packages are built as extensions.
## Building a custom extension
To build a custom extension, create an object that implements the following interface
```typescript
@ -21,20 +23,24 @@ export interface IExtension {
```
## Adding extensions to the store
To add an extension to the `ModuleStore`, pass it as the second argument to `createStore`
```typescript
const store: IModuleStore<IState> = createStore({}, [], [getMyExtension()])
const store: IModuleStore<IState> = createStore({}, [], [getMyExtension()]);
```
## Example
```typescript
//LoggingExtension.ts
export function getLoggingExtension(): IExtension {
return {
onModuleAdded: (module: IModule<any>) => console.log(`Module ${module.id} added`),
onModuleRemoved: (module: IModule<any>) => console.log(`Module ${module.id} removed`),
}
onModuleAdded: (module: IModule<any>) =>
console.log(`Module ${module.id} added`),
onModuleRemoved: (module: IModule<any>) =>
console.log(`Module ${module.id} removed`),
};
}
```
```

Просмотреть файл

@ -1,4 +1,5 @@
# Lifecycle Actions
In addition to the `initialActions` and `finalActions` properties specified on the module, the `ModuleStore` will also dispatch lifecycle actions that indicate a module has been added or removed.
When a module is added, the `"@@Internal/ModuleManager/ModuleAdded"` action is fired, where the payload is the module id. Similarly, when a module is removed, the `"@@Internal/ModuleManager/ModuleRemoved"` action is fired.
When a module is added, the `"@@Internal/ModuleManager/ModuleAdded"` action is fired, where the payload is the module id. Similarly, when a module is removed, the `"@@Internal/ModuleManager/ModuleRemoved"` action is fired.

Просмотреть файл

@ -1,10 +1,13 @@
# Module Reference Counting
**redux-dynamic-modules** will automatically peform "reference counting" of your modules and their contents.
## For Modules
The library will reference count entire modules. This means:
* If you add a module, adding it again is a NO-OP
* If you add a module twice, it must be removed twice.
- If you add a module, adding it again is a NO-OP
- If you add a module twice, it must be removed twice.
Consider the following example:
@ -46,8 +49,10 @@ render() {
```
## For Module Contents
The library will also reference count the contents of your modules, including reducers and middleware. This means:
* If two different modules add the same middleware, only one copy of the middleware is added. Only until **both** modules are removed will the middleware be removed
- If two different modules add the same middleware, only one copy of the middleware is added. Only until **both** modules are removed will the middleware be removed
```jsx
export function createModuleA() {
@ -72,8 +77,8 @@ removeB(); // LoggingMiddleware is removed
```
* If two different modules add a reducer with the same key, only the first added reducer will be used.
For this reason, **it is recommended to keep state keys unique where possible.**
- If two different modules add a reducer with the same key, only the first added reducer will be used.
For this reason, **it is recommended to keep state keys unique where possible.**
```jsx
export function createModuleA() {

Просмотреть файл

@ -1,28 +1,30 @@
# Module Store
The **Module Store** is a Redux store with the added capability of managing **Redux Modules**. It adds the following additional functions to the basic Redux store.
* `addModule(module: IModule<any>)`: Add a single module to the store. The return result is a function that can be used to remove the added module
* `addModules(modules: IModule<any>[])`: Same as `addModule`, but for multiple modules. The return function will remove all the added modules.
* `dispose()`: Remove all of the modules added to the store and dispose of the object
- `addModule(module: IModule<any>)`: Add a single module to the store. The return result is a function that can be used to remove the added module
- `addModules(modules: IModule<any>[])`: Same as `addModule`, but for multiple modules. The return function will remove all the added modules.
- `dispose()`: Remove all of the modules added to the store and dispose of the object
To create a `ModuleStore`, use the `createStore` function from our package
## Example {docsify-ignore}
```typescript
import { createStore, IModuleStore } from "redux-dynamic-modules";
import {getUsersModule} from "./usersModule";
import { getUsersModule } from "./usersModule";
const store: IModuleStore<IState> = createStore(
/* initial state */
{},
/* initial state */
{},
/** enhancers **/
[],
/** enhancers **/
[],
/* Extensions to load */
[],
/* Extensions to load */
[],
getUsersModule(),
/* ...any additional modules */
getUsersModule()
/* ...any additional modules */
);
```

Просмотреть файл

@ -1,24 +1,25 @@
# Module
A 'module' consists of the following properties
* `id`: A unique ID to identify this module
* `reducerMap`: The reducers that this module will register
* `initialActions`: Any optional actions to fire when this module is added
* `finalActions`: Any optional actions to fire when this module is removed.
- `id`: A unique ID to identify this module
- `reducerMap`: The reducers that this module will register
- `initialActions`: Any optional actions to fire when this module is added
- `finalActions`: Any optional actions to fire when this module is removed.
An example module definition looks like the following:
```typescript
export function getUsersModule(): IModule<IUserState> {
return {
id: "todo-module",
reducerMap: {
todoState: todoReducer
},
initialActions: [TodoActions.initializeTodos()],
finalActions: [TodoActions.disposeTodos()]
}
return {
id: "todo-module",
reducerMap: {
todoState: todoReducer,
},
initialActions: [TodoActions.initializeTodos()],
finalActions: [TodoActions.disposeTodos()],
};
}
```
In the above example, the key `todoState` will be added to the Redux store along with the `todoReducer`. Note that you can use `combineReducers` here to allow for more nested state.

Просмотреть файл

@ -1,9 +1,11 @@
## Usage with redux-observable
You can use `redux-dynamic-modules` alongside `redux-observable` so that you can add/remove Epics along with your modules.
To use
* `npm install redux-dynamic-modules-observable`
* Add the observable extension to the `createStore` call
- `npm install redux-dynamic-modules-observable`
- Add the observable extension to the `createStore` call
```typescript
import { createStore, IModuleStore } from "redux-dynamic-modules";
@ -11,27 +13,28 @@ import { getObservableExtension } from "redux-dynamic-modules-observable";
import { getUsersModule } from "./usersModule";
const store: IModuleStore<IState> = createStore(
/* initial state */
{},
/* initial state */
{},
/** enhancers **/
[],
/** enhancers **/
[],
/* Extensions to load */
[getObservableExtension()],
/* Extensions to load */
[getObservableExtension()],
getUsersModule(),
/* ...any additional modules */
getUsersModule()
/* ...any additional modules */
);
```
* Add the `epics` property to your modules, and specify a list of observables to run
- Add the `epics` property to your modules, and specify a list of observables to run
```typescript
return {
id: "users-module",
reducerMap: {
users: usersReducer
users: usersReducer,
},
epics: [usersEpic]
epics: [usersEpic],
};
```
```

Просмотреть файл

@ -1,42 +1,43 @@
## Usage with redux-saga
You can use `redux-dynamic-modules` alongside `redux-saga` so that you can add/remove sagas along with your modules.
To use
* `npm install redux-dynamic-modules-saga`
* Add the saga extension to the `createStore` call
- `npm install redux-dynamic-modules-saga`
- Add the saga extension to the `createStore` call
```typescript
import { createStore, IModuleStore } from "redux-dynamic-modules";
import { getSagaExtension } from "redux-dynamic-modules-saga";
import { getUsersModule } from "./usersModule";
const store: IModuleStore<IState> = createStore(
/* initial state */
{},
/* initial state */
{},
/** enhancers **/
[],
/** enhancers **/
[],
/* Extensions to load */
[getSagaExtension({} /* saga context */)],
/* Extensions to load */
[getSagaExtension({} /* saga context */)],
getUsersModule(),
/* ...any additional modules */
getUsersModule()
/* ...any additional modules */
);
```
* Add the `sagas` property to your modules, and specify a list of sagas to run
- Add the `sagas` property to your modules, and specify a list of sagas to run
```typescript
return {
id: "users-module",
reducerMap: {
users: usersReducer
users: usersReducer,
},
sagas: [
usersSaga,
{ saga: usersSagaWithArguments, argument: {a: "argument"}}
]
{ saga: usersSagaWithArguments, argument: { a: "argument" } },
],
};
```
```

Просмотреть файл

@ -1,9 +1,11 @@
## Usage with redux-thunk
You can use `redux-dynamic-modules` alongside `redux-thunk`.
To use
* `npm install redux-dynamic-modules-thunk`
* Add the thunk extension to the `createStore` call
- `npm install redux-dynamic-modules-thunk`
- Add the thunk extension to the `createStore` call
```typescript
import { createStore, IModuleStore } from "redux-dynamic-modules";
@ -11,18 +13,18 @@ import { getThunkExtension } from "redux-dynamic-modules-thunk";
import { getUsersModule } from "./usersModule";
const store: IModuleStore<IState> = createStore(
/* initial state */
{},
/* initial state */
{},
/** enhancers **/
[],
/** enhancers **/
[],
/* Extensions to load */
[getThunkExtension()],
/* Extensions to load */
[getThunkExtension()],
getUsersModule(),
/* ...any additional modules */
getUsersModule()
/* ...any additional modules */
);
```
Now, you will be able to use thunks as well
Now, you will be able to use thunks as well

Просмотреть файл

@ -1,3 +1,7 @@
.cover-main img {max-width: 100px;}
.cover-main img {
max-width: 100px;
}
.app-name-link img {max-width: 100px;}
.app-name-link img {
max-width: 100px;
}

Просмотреть файл

@ -1,6 +1,4 @@
{
"packages": [
"packages/*"
],
"version": "3.0.3"
"packages": ["packages/*"],
"version": "3.0.3"
}

12679
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,12 +1,14 @@
{
"name": "root",
"private": true,
"scripts": {
"bootstrap": "lerna bootstrap",
"build:prod": "lerna run build:prod",
"test": "npm run build:prod && lerna run test"
},
"devDependencies": {
"lerna": "^3.4.0"
}
}
"name": "root",
"private": true,
"scripts": {
"bootstrap": "lerna bootstrap",
"build:prod": "lerna run build:prod",
"test": "npm run build:prod && lerna run test",
"prettier": "prettier \"**/*.{ts,tsx,json,js,scss,yaml,yml,css,md}\" --write --config prettier.config.js"
},
"devDependencies": {
"lerna": "^3.4.0",
"prettier": "1.15.3"
}
}

Просмотреть файл

@ -1 +1 @@
#This is still a WIP. Contributions are welcome
#This is still a WIP. Contributions are welcome

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -43,35 +43,39 @@ export function getEpicManager(): IEpicManager {
});
runningEpics = runningEpics.filter(e => {
!!epicNameMap[e.name] && epicRefCounter.getCount(e.name) !== 0
!!epicNameMap[e.name] && epicRefCounter.getCount(e.name) !== 0;
});
},
dispose: () => {
runningEpics = null;
}
},
};
}
function createRootEpic(runningEpics: Epic[]): Epic {
const merger = (...args) => merge(
runningEpics.map(epic => {
//@ts-ignore
const output$ = epic(...args);
if (!output$) {
throw new TypeError(`combineEpics: one of the provided Epics "${epic.name || '<anonymous>'}" does not return a stream. Double check you\'re not missing a return statement!`);
}
return output$;
})
);
const merger = (...args) =>
merge(
runningEpics.map(epic => {
//@ts-ignore
const output$ = epic(...args);
if (!output$) {
throw new TypeError(
`combineEpics: one of the provided Epics "${epic.name ||
"<anonymous>"}" does not return a stream. Double check you\'re not missing a return statement!`
);
}
return output$;
})
);
// Technically the `name` property on Function's are supposed to be read-only.
// While some JS runtimes allow it anyway (so this is useful in debugging)
// some actually throw an exception when you attempt to do so.
try {
Object.defineProperty(merger, 'name', {
value: '____MODULES_ROOT_EPIC',
Object.defineProperty(merger, "name", {
value: "____MODULES_ROOT_EPIC",
});
} catch (e) { }
} catch (e) {}
return merger;
}

Просмотреть файл

@ -21,6 +21,6 @@ export function getObservableExtension(): IExtension {
if (module.epics) {
epicManager.remove(module.epics);
}
}
},
};
}

Просмотреть файл

@ -1,10 +1,6 @@
{
"compilerOptions": {
"lib": [
"dom",
"es2015",
"es2016"
],
"lib": ["dom", "es2015", "es2016"],
"declaration": true,
"noUnusedLocals": true,
"noImplicitAny": false,
@ -14,6 +10,8 @@
"outDir": "lib"
},
"include": [
"src/**/*.ts", "../widgets-example/src/widgets/hacker-news/index.js", "../widgets-example/src/widgets/hacker-news/component/index.jsx"
"src/**/*.ts",
"../widgets-example/src/widgets/hacker-news/index.js",
"../widgets-example/src/widgets/hacker-news/component/index.jsx"
]
}
}

Просмотреть файл

@ -1,3 +1 @@
{
}
{}

Просмотреть файл

@ -1,39 +1,43 @@
let webpack = require("webpack");
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
let BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
module.exports = (env, argv) => {
let mode_env = argv.mode || 'development';
let mode_env = argv.mode || "development";
return {
mode: mode_env,
devtool: 'source-map',
devtool: "source-map",
entry: {
main: './lib/index'
main: "./lib/index",
},
output: {
library: "redux-dynamic-modules-observable",
libraryTarget: "umd",
filename: mode_env === "production" ? "redux-dynamic-modules-observable.min.js" : "redux-dynamic-modules-observable.js",
path: __dirname + "/dist/"
filename:
mode_env === "production"
? "redux-dynamic-modules-observable.min.js"
: "redux-dynamic-modules-observable.js",
path: __dirname + "/dist/",
},
externals: {
"react": "react",
"redux": "redux",
react: "react",
redux: "redux",
"react-redux": "react-redux",
"redux-saga": "redux-saga",
"redux-dynamic-modules": "redux-dynamic-modules",
"rxjs": "rxjs",
rxjs: "rxjs",
"rxjs/operators": "rxjs/operators",
"rxjs/observable": "rxjs/observable",
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
analyzerMode: "static",
reportFilename: `react-redux-module.stats.html`,
openAnalyzer: false
})
]
openAnalyzer: false,
}),
],
};
};
};

Просмотреть файл

@ -1,47 +1,50 @@
## Install
Run
Run
```
npm install redux-dynamic-modules-saga
```
or
or
```
yarn add redux-dynamic-modules-saga
```
## Usage
* Create a module with the following format
- Create a module with the following format
```typescript
export function getUsersModule(): ISagaModule<IUserState> {
return {
id: "users",
reducerMap: {
users: usersReducer
},
sagas: [userSagas]
// Actions to fire when this module is added/removed
// initialActions: [],
// finalActions: [],
}
return {
id: "users",
reducerMap: {
users: usersReducer,
},
sagas: [userSagas],
// Actions to fire when this module is added/removed
// initialActions: [],
// finalActions: [],
};
}
```
* Create a `ModuleStore`
- Create a `ModuleStore`
```typescript
import {configureStore, IModuleStore} from "redux-dynamic-modules";
import {getUsersModule} from "./usersModule";
import { configureStore, IModuleStore } from "redux-dynamic-modules";
import { getUsersModule } from "./usersModule";
const store: IModuleStore<IState> = configureStore(
/* initial state */
{},
/* initial state */
{},
/* extensions to include */
[getSagaExtension(/* saga context object */)],
/* extensions to include */
[getSagaExtension(/* saga context object */)],
getUsersModule(),
/* ...any additional modules */
getUsersModule()
/* ...any additional modules */
);
```

17072
packages/redux-dynamic-modules-saga/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -4,7 +4,9 @@ export interface ISagaWithArguments<T> {
saga: (argument?: T) => Iterator<any>;
argument?: T;
}
export type ISagaRegistration<T> = (() => Iterator<any>) | ISagaWithArguments<T>;
export type ISagaRegistration<T> =
| (() => Iterator<any>)
| ISagaWithArguments<T>;
export interface ISagaModule<T> extends IModule<T> {
sagas?: ISagaRegistration<any>[];

Просмотреть файл

@ -1,5 +1,8 @@
import { ISagaRegistration, ISagaWithArguments } from "./Contracts";
export function sagaEquals(a: ISagaRegistration<any>, b: ISagaRegistration<any>): boolean {
export function sagaEquals(
a: ISagaRegistration<any>,
b: ISagaRegistration<any>
): boolean {
if (typeof a === "function" && typeof b === "function") {
return a === b;
}
@ -12,7 +15,6 @@ export function sagaEquals(a: ISagaRegistration<any>, b: ISagaRegistration<any>)
const sagaA = a as () => Iterator<any>;
const sagaB = b as ISagaWithArguments<any>;
return sagaA === sagaB.saga && !sagaB.argument;
} else if (typeof b === "function") {
const sagaA = a as ISagaWithArguments<any>;
const sagaB = b as () => Iterator<any>;
@ -23,8 +25,8 @@ export function sagaEquals(a: ISagaRegistration<any>, b: ISagaRegistration<any>)
const sagaA = a as ISagaWithArguments<any>;
const sagaB = b as ISagaWithArguments<any>;
return sagaA.saga === sagaB.saga && (
sagaA.argument === sagaB.argument // TODO: This needs to be a deep equals
return (
sagaA.saga === sagaB.saga && sagaA.argument === sagaB.argument // TODO: This needs to be a deep equals
);
}
}

Просмотреть файл

@ -1,5 +1,10 @@
import { default as createSagaMiddleware, SagaMiddleware } from "redux-saga";
import { IExtension, IItemManager, getRefCountedManager, IModuleManager } from "redux-dynamic-modules";
import {
IExtension,
IItemManager,
getRefCountedManager,
IModuleManager,
} from "redux-dynamic-modules";
import { ISagaRegistration, ISagaModule } from "./Contracts";
import { getSagaManager } from "./SagaManager";
import { sagaEquals } from "./SagaComparer";
@ -8,7 +13,10 @@ import { sagaEquals } from "./SagaComparer";
* Get an extension that integrates saga with the store
* @param sagaContext The context to provide to the saga
*/
export function getSagaExtension<C>(sagaContext?: C, onError?: (error: Error) => void): IExtension {
export function getSagaExtension<C>(
sagaContext?: C,
onError?: (error: Error) => void
): IExtension {
let sagaMonitor = undefined;
//@ts-ignore
@ -17,15 +25,15 @@ export function getSagaExtension<C>(sagaContext?: C, onError?: (error: Error) =>
}
// Setup the saga middleware
let sagaMiddleware: SagaMiddleware<C> = createSagaMiddleware<any>(
{
context: sagaContext,
sagaMonitor,
onError
}
);
let sagaMiddleware: SagaMiddleware<C> = createSagaMiddleware<any>({
context: sagaContext,
sagaMonitor,
onError,
});
let _sagaManager: IItemManager<ISagaRegistration<any>> = getRefCountedManager(getSagaManager(sagaMiddleware), sagaEquals);
let _sagaManager: IItemManager<
ISagaRegistration<any>
> = getRefCountedManager(getSagaManager(sagaMiddleware), sagaEquals);
return {
middleware: [sagaMiddleware],
@ -50,6 +58,6 @@ export function getSagaExtension<C>(sagaContext?: C, onError?: (error: Error) =>
dispose: () => {
_sagaManager.dispose();
}
}
}
},
};
}

Просмотреть файл

@ -1,12 +1,14 @@
import { ISagaRegistration, ISagaWithArguments } from "./Contracts";
import { SagaMiddleware, Task } from "redux-saga";
import { sagaEquals as sagaEquals } from "./SagaComparer";
import { sagaEquals } from "./SagaComparer";
import { IItemManager, getMap } from "redux-dynamic-modules";
/**
* Creates saga items which can be used to start and stop sagas dynamically
*/
export function getSagaManager(sagaMiddleware: SagaMiddleware<any>): IItemManager<ISagaRegistration<any>> {
export function getSagaManager(
sagaMiddleware: SagaMiddleware<any>
): IItemManager<ISagaRegistration<any>> {
const tasks = getMap<ISagaRegistration<any>, Task>(sagaEquals);
return {
@ -35,11 +37,14 @@ export function getSagaManager(sagaMiddleware: SagaMiddleware<any>): IItemManage
dispose: () => {
// Cancel everything
tasks.keys.forEach(k => tasks.get(k).cancel());
}
},
};
}
function runSaga(sagaMiddleware: SagaMiddleware<any>, sagaRegistration: ISagaRegistration<any>): Task {
function runSaga(
sagaMiddleware: SagaMiddleware<any>,
sagaRegistration: ISagaRegistration<any>
): Task {
if (typeof sagaRegistration === "function") {
const saga = sagaRegistration as () => Iterator<any>;
return sagaMiddleware.run(saga);
@ -48,4 +53,3 @@ function runSaga(sagaMiddleware: SagaMiddleware<any>, sagaRegistration: ISagaReg
const argument = (sagaRegistration as ISagaWithArguments<any>).argument;
return sagaMiddleware.run(saga, argument);
}

Просмотреть файл

@ -1,43 +1,41 @@
import { sagaEquals } from "../SagaComparer";
it("Saga comparer tests", () => {
expect(sagaEquals(null, null)).toBe(true);
expect(sagaEquals(func1 as any, func1 as any)).toBe(true);
expect(sagaEquals(saga1, saga1)).toBe(true);
expect(sagaEquals(
{
saga: sagaWithArgs,
argument: "abc"
},
{
saga: sagaWithArgs,
argument: "abc"
})).toBe(true);
expect(
sagaEquals(
{
saga: sagaWithArgs,
argument: "abc",
},
{
saga: sagaWithArgs,
argument: "abc",
}
)
).toBe(true);
expect(sagaEquals(undefined, null)).toBe(false);
expect(sagaEquals(null, func1 as any)).toBe(false);
expect(sagaEquals(func1 as any, saga1)).toBe(false);
expect(sagaEquals(
{
saga: sagaWithArgs,
argument: "abc"
},
{
saga: sagaWithArgs,
argument: "pqr"
})).toBe(false);
expect(
sagaEquals(
{
saga: sagaWithArgs,
argument: "abc",
},
{
saga: sagaWithArgs,
argument: "pqr",
}
)
).toBe(false);
});
function func1() {
function func1() {}
}
function* saga1() {}
function* saga1() {
}
function* sagaWithArgs(name: string) {
}
function* sagaWithArgs(name: string) {}

Просмотреть файл

@ -16,12 +16,11 @@ describe("Saga extension tests", () => {
function getTestModule(): ISagaModule<{ a: string }> {
return {
id: "test-module",
sagas: [testSaga]
}
sagas: [testSaga],
};
}
let called = false;
function* testSaga(): SagaIterator {
called = true;
}

Просмотреть файл

@ -1,11 +1,11 @@
import { getSagaManager } from '../SagaManager';
import { getSagaManager } from "../SagaManager";
function getSagaMiddleware(callback) {
return {
run: () => {
return {
cancel: callback
}
}
cancel: callback,
};
},
};
}
@ -27,6 +27,4 @@ it("saga manager tests", () => {
expect(index).toBe(1);
});
function* saga1() {
}
function* saga1() {}

Просмотреть файл

@ -1,2 +1,2 @@
export * from "./Contracts";
export * from "./SagaExtension";
export * from "./SagaExtension";

Просмотреть файл

@ -1,10 +1,6 @@
{
"compilerOptions": {
"lib": [
"dom",
"es2015",
"es2016"
],
"lib": ["dom", "es2015", "es2016"],
"declaration": true,
"noUnusedLocals": true,
"noImplicitAny": false,
@ -13,10 +9,6 @@
"jsx": "react",
"outDir": "lib"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"src/__tests__/**/**.ts"
]
}
"include": ["src/**/*.ts"],
"exclude": ["src/__tests__/**/**.ts"]
}

Просмотреть файл

@ -1,3 +1 @@
{
}
{}

Просмотреть файл

@ -1,36 +1,40 @@
let webpack = require("webpack");
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
let BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
module.exports = (env, argv) => {
let mode_env = argv.mode || 'development';
let mode_env = argv.mode || "development";
return {
mode: mode_env,
devtool: 'source-map',
devtool: "source-map",
entry: {
main: './lib/index'
main: "./lib/index",
},
output: {
library: "redux-dynamic-modules-saga",
libraryTarget: "umd",
filename: mode_env === "production" ? "redux-dynamic-modules-saga.min.js" : "redux-dynamic-modules-saga.js",
path: __dirname + "/dist/"
filename:
mode_env === "production"
? "redux-dynamic-modules-saga.min.js"
: "redux-dynamic-modules-saga.js",
path: __dirname + "/dist/",
},
externals: {
"react": "react",
"redux": "redux",
react: "react",
redux: "redux",
"react-redux": "react-redux",
"redux-saga": "redux-saga",
"redux-dynamic-modules": "redux-dynamic-modules"
"redux-dynamic-modules": "redux-dynamic-modules",
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
analyzerMode: "static",
reportFilename: `react-redux-module.stats.html`,
openAnalyzer: false
})
]
openAnalyzer: false,
}),
],
};
};
};

17054
packages/redux-dynamic-modules-thunk/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -3,6 +3,6 @@ import { IExtension } from "redux-dynamic-modules";
export function getThunkExtension(): IExtension {
return {
middleware: [thunk]
}
}
middleware: [thunk],
};
}

Просмотреть файл

@ -1,10 +1,6 @@
{
"compilerOptions": {
"lib": [
"dom",
"es2015",
"es2016"
],
"lib": ["dom", "es2015", "es2016"],
"declaration": true,
"noUnusedLocals": true,
"noImplicitAny": false,
@ -13,7 +9,5 @@
"jsx": "react",
"outDir": "lib"
},
"include": [
"src/**/*.ts"
]
}
"include": ["src/**/*.ts"]
}

Просмотреть файл

@ -1,3 +1 @@
{
}
{}

Просмотреть файл

@ -1,36 +1,40 @@
let webpack = require("webpack");
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
let BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
module.exports = (env, argv) => {
let mode_env = argv.mode || 'development';
let mode_env = argv.mode || "development";
return {
mode: mode_env,
devtool: 'source-map',
devtool: "source-map",
entry: {
main: './lib/index'
main: "./lib/index",
},
output: {
library: "redux-dynamic-modules-thunk",
libraryTarget: "umd",
filename: mode_env === "production" ? "redux-dynamic-modules-thunk.min.js" : "redux-dynamic-modules-thunk.js",
path: __dirname + "/dist/"
filename:
mode_env === "production"
? "redux-dynamic-modules-thunk.min.js"
: "redux-dynamic-modules-thunk.js",
path: __dirname + "/dist/",
},
externals: {
"react": "react",
"redux": "redux",
react: "react",
redux: "redux",
"react-redux": "react-redux",
"redux-thunk": "redux-thunk",
"redux-dynamic-modules": "redux-dynamic-modules"
"redux-dynamic-modules": "redux-dynamic-modules",
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
analyzerMode: "static",
reportFilename: `react-redux-module.stats.html`,
openAnalyzer: false
})
]
openAnalyzer: false,
}),
],
};
};
};

17302
packages/redux-dynamic-modules/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,71 +1,71 @@
{
"name": "redux-dynamic-modules",
"version": "3.0.3",
"description": "Modularize the redux app by dynamically loading reducers, state and sagas",
"repository": {
"type": "github",
"url": "https://github.com/Microsoft/redux-dynamic-modules"
},
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"scripts": {
"prepublish": "npm run build:prod && npm run test",
"build": "npm run clean && tsc && webpack --mode development --display-modules --progress --display-error-details",
"build:prod": "npm run clean && tsc && webpack --mode development --display-error-details && webpack --mode production --display-modules --progress --display-error-details",
"clean": "rimraf lib/ dist/",
"test": "jest"
},
"keywords": [
"react",
"redux",
"module",
"dynamic",
"load"
],
"author": "Navneet Gupta (Microsoft), Alex Bettadapur (Microsoft)",
"license": "MIT",
"devDependencies": {
"@types/jest": "^23.3.1",
"@types/react": "^16.4.13",
"@types/react-redux": "^6.0.6",
"@types/redux": "^3.6.0",
"jest": "^23.5.0",
"react": "^16.4.2",
"react-redux": "^5.0.7",
"redux": "^4.0.0",
"rimraf": "^2.6.2",
"ts-jest": "^23.1.4",
"tslib": "^1.9.3",
"tslint": "^5.11.0",
"typescript": "^3.0.3",
"uglifyjs-webpack-plugin": "^1.3.0",
"webpack": "^4.17.1",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-cli": "^3.1.0"
},
"jest": {
"transform": {
"^.+\\.tsx?$": "ts-jest"
"name": "redux-dynamic-modules",
"version": "3.0.3",
"description": "Modularize the redux app by dynamically loading reducers, state and sagas",
"repository": {
"type": "github",
"url": "https://github.com/Microsoft/redux-dynamic-modules"
},
"verbose": false,
"testMatch": [
"**/src/__tests__/**/(*.)+(spec|test).ts?(x)"
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"scripts": {
"prepublish": "npm run build:prod && npm run test",
"build": "npm run clean && tsc && webpack --mode development --display-modules --progress --display-error-details",
"build:prod": "npm run clean && tsc && webpack --mode development --display-error-details && webpack --mode production --display-modules --progress --display-error-details",
"clean": "rimraf lib/ dist/",
"test": "jest"
},
"keywords": [
"react",
"redux",
"module",
"dynamic",
"load"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
]
},
"peerDependencies": {
"react": ">= 15.0.0",
"react-redux": ">= 5.0.0",
"redux": ">= 3.0.0"
},
"dependencies": {
"redux-dynamic-middlewares": "^1.0.0"
}
"author": "Navneet Gupta (Microsoft), Alex Bettadapur (Microsoft)",
"license": "MIT",
"devDependencies": {
"@types/jest": "^23.3.1",
"@types/react": "^16.4.13",
"@types/react-redux": "^6.0.6",
"@types/redux": "^3.6.0",
"jest": "^23.5.0",
"react": "^16.4.2",
"react-redux": "^5.0.7",
"redux": "^4.0.0",
"rimraf": "^2.6.2",
"ts-jest": "^23.1.4",
"tslib": "^1.9.3",
"tslint": "^5.11.0",
"typescript": "^3.0.3",
"uglifyjs-webpack-plugin": "^1.3.0",
"webpack": "^4.17.1",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-cli": "^3.1.0"
},
"jest": {
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"verbose": false,
"testMatch": [
"**/src/__tests__/**/(*.)+(spec|test).ts?(x)"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
]
},
"peerDependencies": {
"react": ">= 15.0.0",
"react-redux": ">= 5.0.0",
"redux": ">= 3.0.0"
},
"dependencies": {
"redux-dynamic-middlewares": "^1.0.0"
}
}

Просмотреть файл

@ -19,7 +19,6 @@ export interface IModule<State> {
*/
middlewares?: Middleware[];
/**
* These actions are dispatched immediately after adding the module in the store
*/
@ -50,19 +49,20 @@ export interface IModuleManager {
/**
* Add the given module to the store
*/
addModule: (module: IModule<any>) => IDynamicallyAddedModule
addModule: (module: IModule<any>) => IDynamicallyAddedModule;
/**
* Adds the given set of modules to the store
*/
addModules: (modules: IModule<any>[]) => IDynamicallyAddedModule;
}
export type IModuleStore<State> = Store<State> & IModuleManager & {
/**
* Remove all the modules from the store
*/
dispose: () => void;
};
export type IModuleStore<State> = Store<State> &
IModuleManager & {
/**
* Remove all the modules from the store
*/
dispose: () => void;
};
export interface IItemManager<T> {
getItems: () => T[];

Просмотреть файл

@ -4,15 +4,15 @@ import { IModule, IModuleStore, IDynamicallyAddedModule } from "./Contracts";
import { Provider } from "react-redux";
export interface IDynamicModuleLoaderProps<OriginalState, AdditionalState> {
/** Modules that need to be dynamically registerd */
modules: IModule<AdditionalState>[];
/** Modules that need to be dynamically registerd */
modules: IModule<AdditionalState>[];
/** Optional callback which returns a store instance. This would be called if no store could be loaded from the context. */
createStore?: () => IModuleStore<any>;
/** Optional callback which returns a store instance. This would be called if no store could be loaded from the context. */
createStore?: () => IModuleStore<any>;
}
export interface IDynamicModuleLoaderContext {
store: IModuleStore<any>;
store: IModuleStore<any>;
}
/**
@ -21,59 +21,61 @@ export interface IDynamicModuleLoaderContext {
* On unmount, they will be unregistered
*/
export class DynamicModuleLoader<
OriginalState,
AdditionalState
OriginalState,
AdditionalState
> extends React.Component<
IDynamicModuleLoaderProps<OriginalState, AdditionalState>
IDynamicModuleLoaderProps<OriginalState, AdditionalState>
> {
private _addedModules?: IDynamicallyAddedModule;
private _store: IModuleStore<any>;
private _providerInitializationNeeded: boolean;
private _addedModules?: IDynamicallyAddedModule;
private _store: IModuleStore<any>;
private _providerInitializationNeeded: boolean;
// @ts-ignore
private static contextTypes = {
store: PropTypes.object
};
// @ts-ignore
private static contextTypes = {
store: PropTypes.object,
};
constructor(
props: IDynamicModuleLoaderProps<OriginalState, AdditionalState>,
context: IDynamicModuleLoaderContext
) {
super(props, context);
const { modules, createStore } = props;
this._store = context.store;
this._providerInitializationNeeded = false;
constructor(
props: IDynamicModuleLoaderProps<OriginalState, AdditionalState>,
context: IDynamicModuleLoaderContext
) {
super(props, context);
const { modules, createStore } = props;
this._store = context.store;
this._providerInitializationNeeded = false;
if (!this._store) {
if (createStore) {
this._store = createStore();
this._providerInitializationNeeded = true;
} else {
throw new Error("Could not load store from React context.");
}
if (!this._store) {
if (createStore) {
this._store = createStore();
this._providerInitializationNeeded = true;
} else {
throw new Error("Could not load store from React context.");
}
}
this._addedModules = this._store.addModules(modules);
}
this._addedModules = this._store.addModules(modules);
}
/**
* Unregister sagas and reducers
*/
public componentWillUnmount(): void {
if (this._addedModules) {
this._addedModules.remove();
this._addedModules = undefined;
/**
* Unregister sagas and reducers
*/
public componentWillUnmount(): void {
if (this._addedModules) {
this._addedModules.remove();
this._addedModules = undefined;
}
}
}
/**
* Render a Redux provider
*/
public render(): React.ReactNode {
if (this._providerInitializationNeeded) {
return <Provider store={this._store}>{this.props.children}</Provider>;
} else {
return this.props.children;
/**
* Render a Redux provider
*/
public render(): React.ReactNode {
if (this._providerInitializationNeeded) {
return (
<Provider store={this._store}>{this.props.children}</Provider>
);
} else {
return this.props.children;
}
}
}
}

Просмотреть файл

@ -2,7 +2,7 @@
import { Middleware } from "redux";
import { IItemManager } from "../Contracts";
import { createDynamicMiddlewares } from 'redux-dynamic-middlewares'
import { createDynamicMiddlewares } from "redux-dynamic-middlewares";
export interface IDynamicMiddlewareManager extends IItemManager<Middleware> {
enhancer: Middleware;
@ -12,18 +12,20 @@ export const getMiddlewareManager = (): IDynamicMiddlewareManager => {
const add = (middlewares: Middleware[]) => {
dynamicMiddlewaresInstance.addMiddleware(...middlewares);
return middlewares;
}
};
const remove = (middlewares: Middleware[]) => {
middlewares.forEach(dynamicMiddlewaresInstance.removeMiddleware);
return middlewares;
}
};
return {
getItems: () => [],
enhancer: dynamicMiddlewaresInstance.enhancer,
add,
remove,
dispose: () => { dynamicMiddlewaresInstance.resetMiddlewares() }
}
}
dispose: () => {
dynamicMiddlewaresInstance.resetMiddlewares();
},
};
};

Просмотреть файл

@ -1,13 +1,26 @@
import { IModule, IItemManager, IExtension } from "../Contracts";
import { AnyAction, ReducersMapObject, Dispatch, Middleware, Reducer } from "redux";
import { getReducerManager, IReducerManager, getRefCountedReducerManager } from "./ReducerManager";
import {
AnyAction,
ReducersMapObject,
Dispatch,
Middleware,
Reducer,
} from "redux";
import {
getReducerManager,
IReducerManager,
getRefCountedReducerManager,
} from "./ReducerManager";
export interface IModuleManager<State> extends IItemManager<IModule<State>> {
setDispatch: (dispatch: Dispatch<AnyAction>) => void;
getReducer: (state: State, action: AnyAction) => State;
}
export function getModuleManager<State>(middlewareManager: IItemManager<Middleware>, extensions: IExtension[]): IModuleManager<State> {
export function getModuleManager<State>(
middlewareManager: IItemManager<Middleware>,
extensions: IExtension[]
): IModuleManager<State> {
let _dispatch = null;
let _reducerManager: IReducerManager<State>;
let _modules: IModule<any>[] = [];
@ -19,53 +32,61 @@ export function getModuleManager<State>(middlewareManager: IItemManager<Middlewa
}
if (!_dispatch) {
throw new Error("setDispatch should be called on ModuleManager before adding any modules.");
throw new Error(
"setDispatch should be called on ModuleManager before adding any modules."
);
}
actions.forEach(_dispatch);
}
};
const _addMiddlewares = (middlewares: Middleware[]) => {
if (!middlewares) {
return;
}
middlewareManager.add(middlewares);
}
};
const _removeMiddlewares = (middlewares: Middleware[]) => {
if (!middlewares) {
return;
}
middlewareManager.remove(middlewares);
}
};
const _addReducers = (reducerMap: ReducersMapObject<Reducer, AnyAction>) => {
const _addReducers = (
reducerMap: ReducersMapObject<Reducer, AnyAction>
) => {
if (!reducerMap) {
return;
}
if (!_reducerManager) {
_reducerManager = getRefCountedReducerManager(getReducerManager(reducerMap)) as any;
_reducerManager = getRefCountedReducerManager(
getReducerManager(reducerMap)
) as any;
} else {
for (const key in reducerMap) {
_reducerManager.add(key, reducerMap[key]);
}
}
}
};
const _removeReducers = (reducerMap: ReducersMapObject<Reducer, AnyAction>) => {
const _removeReducers = (
reducerMap: ReducersMapObject<Reducer, AnyAction>
) => {
if (!reducerMap || !_reducerManager) {
return;
}
for (const key in reducerMap) {
_reducerManager.remove(key);
}
}
};
// Create reduce function which redirects to _reducers.reduce
const _reduce = (s: State, a: AnyAction) => {
if (_reducerManager) {
return _reducerManager.reduce(s, a);
}
return (s || null);
return s || null;
};
const moduleManager = {
@ -93,10 +114,8 @@ export function getModuleManager<State>(middlewareManager: IItemManager<Middlewa
}
});
// add the sagas and dispatch actions at the end so all the reducers are registered
justAddedModules.forEach(module => {
// Let the extensions know we added a module
extensions.forEach(p => {
if (p.onModuleAdded) {
@ -105,8 +124,15 @@ export function getModuleManager<State>(middlewareManager: IItemManager<Middlewa
});
// Dispatch the initial actions
const moduleAddedAction = { type: "@@Internal/ModuleManager/ModuleAdded", payload: module.id };
_dispatchActions(module.initialActions ? [moduleAddedAction, ...module.initialActions] : [moduleAddedAction]);
const moduleAddedAction = {
type: "@@Internal/ModuleManager/ModuleAdded",
payload: module.id,
};
_dispatchActions(
module.initialActions
? [moduleAddedAction, ...module.initialActions]
: [moduleAddedAction]
);
});
},
remove: (modulesToRemove: IModule<any>[]) => {
@ -115,7 +141,6 @@ export function getModuleManager<State>(middlewareManager: IItemManager<Middlewa
}
modulesToRemove = modulesToRemove.filter(module => module);
modulesToRemove.forEach(module => {
if (_moduleIds.has(module.id)) {
_dispatchActions(module.finalActions);
@ -132,13 +157,18 @@ export function getModuleManager<State>(middlewareManager: IItemManager<Middlewa
_moduleIds.delete(module.id);
_modules = _modules.filter(m => m.id !== module.id);
_dispatchActions([{ type: "@@Internal/ModuleManager/ModuleRemoved", payload: module.id }]);
_dispatchActions([
{
type: "@@Internal/ModuleManager/ModuleRemoved",
payload: module.id,
},
]);
}
});
},
dispose: () => {
moduleManager.remove(_modules);
}
},
};
return moduleManager;
}

Просмотреть файл

@ -1,9 +1,4 @@
import {
combineReducers,
Reducer,
ReducersMapObject,
AnyAction
} from "redux";
import { combineReducers, Reducer, ReducersMapObject, AnyAction } from "redux";
import { getStringRefCounter } from "../Utils/RefCounter";
export interface IReducerManager<S> {
@ -16,7 +11,9 @@ export interface IReducerManager<S> {
/**
* Adds reference counting to reducer manager and adds/remove keys only when ref count is zero
*/
export function getRefCountedReducerManager<S>(manager: IReducerManager<S>): IReducerManager<S> {
export function getRefCountedReducerManager<S>(
manager: IReducerManager<S>
): IReducerManager<S> {
const reducerKeyRefCounter = getStringRefCounter();
for (const key in manager.getReducerMap()) {
reducerKeyRefCounter.add(key);
@ -38,7 +35,7 @@ export function getRefCountedReducerManager<S>(manager: IReducerManager<S>): IRe
if (reducerKeyRefCounter.getCount(key) === 0) {
manager.remove(key);
}
}
},
};
}
@ -52,12 +49,14 @@ export function getReducerManager<S extends {}>(
initialReducers: ReducersMapObject<S>
): IReducerManager<S> {
let combinedReducer = combineReducers(initialReducers);
const reducers: ReducersMapObject<S> = { ...initialReducers as object } as ReducersMapObject<S>;
const reducers: ReducersMapObject<S> = {
...(initialReducers as object),
} as ReducersMapObject<S>;
let keysToRemove = [];
const reduce = (state: S, action: AnyAction) => {
if (keysToRemove.length > 0) {
state = { ...state as any };
state = { ...(state as any) };
for (let key of keysToRemove) {
delete state[key];
}
@ -75,7 +74,6 @@ export function getReducerManager<S extends {}>(
getReducerMap: () => reducers,
reduce,
add: <ReducerState>(key: string, reducer: Reducer<ReducerState>) => {
if (!key || reducers[key]) {
return;
}
@ -101,4 +99,3 @@ function getCombinedReducer(reducerMap: ReducersMapObject<any>) {
}
return combineReducers(reducerMap);
}

Просмотреть файл

@ -4,13 +4,16 @@ import { IItemManager } from "../Contracts";
/**
* Enhances the given items with ref counting for add remove purposes
*/
export function getRefCountedManager<IType extends IItemManager<T>, T>(manager: IType, equals: (a: T, b: T) => boolean): IType {
export function getRefCountedManager<IType extends IItemManager<T>, T>(
manager: IType,
equals: (a: T, b: T) => boolean
): IType {
let refCounter = getObjectRefCounter<T>(equals);
const items = manager.getItems();
// Set initial ref counting
items.forEach(item => refCounter.add(item));
const ret: IType = { ...manager as object } as IType;
const ret: IType = { ...(manager as object) } as IType;
// Wrap add method
ret.add = (items: T[]) => {
@ -19,14 +22,15 @@ export function getRefCountedManager<IType extends IItemManager<T>, T>(manager:
}
const nonNullItems = items.filter(i => i);
const notAddedItems = nonNullItems.filter(i => refCounter.getCount(i) === 0);
const notAddedItems = nonNullItems.filter(
i => refCounter.getCount(i) === 0
);
manager.add(notAddedItems);
nonNullItems.forEach(refCounter.add);
};
// Wrap remove
ret.remove = (items: T[]) => {
if (!items) {
return;
}
@ -38,11 +42,11 @@ export function getRefCountedManager<IType extends IItemManager<T>, T>(manager:
}
}
});
}
};
ret.dispose = () => {
manager.dispose();
}
};
return ret;
}

Просмотреть файл

@ -1,54 +1,139 @@
import { applyMiddleware, compose, createStore as createReduxStore, DeepPartial, StoreEnhancer } from "redux";
import {
applyMiddleware,
compose,
createStore as createReduxStore,
DeepPartial,
StoreEnhancer,
} from "redux";
import { getMiddlewareManager } from ".";
import { IExtension, IModule, IModuleStore } from "./Contracts";
import { getModuleManager } from "./Managers/ModuleManager";
import { getRefCountedManager } from './Managers/RefCountedManager';
import { getRefCountedManager } from "./Managers/RefCountedManager";
/**
* Configure the module store
*/
export function createStore<S1>(initialState: DeepPartial<S1>, enhancers: StoreEnhancer[], extensions: IExtension[], reduxModule: IModule<S1>): IModuleStore<S1>;
export function createStore<S1, S2>(initialState: DeepPartial<S1 & S2>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>): IModuleStore<S1 & S2>;
export function createStore<S1, S2, S3>(initialState: DeepPartial<S1 & S2 & S3>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>): IModuleStore<S1 & S2 & S3>;
export function createStore<S1, S2, S3, S4>(initialState: DeepPartial<S1 & S2 & S3 & S4>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>, m4: IModule<S4>): IModuleStore<S1 & S2 & S3 & S4>;
export function createStore<S1, S2, S3, S4, S5>(initialState: DeepPartial<S1 & S2 & S3 & S4 & S5>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>, m4: IModule<S4>, m5: IModule<S5>): IModuleStore<S1 & S2 & S3 & S4 & S5>;
export function createStore<S1, S2, S3, S4, S5, S6>(initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>, m4: IModule<S4>, m5: IModule<S5>, m6: IModule<S6>): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6>;
export function createStore<S1, S2, S3, S4, S5, S6, S7>(initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6 & S7>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>, m4: IModule<S4>, m5: IModule<S5>, m6: IModule<S6>, m7: IModule<S7>): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6 & S7>;
export function createStore<S1, S2, S3, S4, S5, S6, S7, S8>(initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>, m4: IModule<S4>, m5: IModule<S5>, m6: IModule<S6>, m7: IModule<S7>, m8: IModule<S8>): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8>;
export function createStore<State>(initialState: DeepPartial<State>, enhancers: StoreEnhancer[], extensions: IExtension[], ...initialModules: IModule<any>[]): IModuleStore<State>;
export function createStore<State>(initialState: DeepPartial<State>, enhancers: StoreEnhancer[], extensions: IExtension[], ...initialModules: IModule<any>[]): IModuleStore<State> {
export function createStore<S1>(
initialState: DeepPartial<S1>,
enhancers: StoreEnhancer[],
extensions: IExtension[],
reduxModule: IModule<S1>
): IModuleStore<S1>;
export function createStore<S1, S2>(
initialState: DeepPartial<S1 & S2>,
enhancers: StoreEnhancer[],
extensions: IExtension[],
m1: IModule<S1>,
m2: IModule<S2>
): IModuleStore<S1 & S2>;
export function createStore<S1, S2, S3>(
initialState: DeepPartial<S1 & S2 & S3>,
enhancers: StoreEnhancer[],
extensions: IExtension[],
m1: IModule<S1>,
m2: IModule<S2>,
m3: IModule<S3>
): IModuleStore<S1 & S2 & S3>;
export function createStore<S1, S2, S3, S4>(
initialState: DeepPartial<S1 & S2 & S3 & S4>,
enhancers: StoreEnhancer[],
extensions: IExtension[],
m1: IModule<S1>,
m2: IModule<S2>,
m3: IModule<S3>,
m4: IModule<S4>
): IModuleStore<S1 & S2 & S3 & S4>;
export function createStore<S1, S2, S3, S4, S5>(
initialState: DeepPartial<S1 & S2 & S3 & S4 & S5>,
enhancers: StoreEnhancer[],
extensions: IExtension[],
m1: IModule<S1>,
m2: IModule<S2>,
m3: IModule<S3>,
m4: IModule<S4>,
m5: IModule<S5>
): IModuleStore<S1 & S2 & S3 & S4 & S5>;
export function createStore<S1, S2, S3, S4, S5, S6>(
initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6>,
enhancers: StoreEnhancer[],
extensions: IExtension[],
m1: IModule<S1>,
m2: IModule<S2>,
m3: IModule<S3>,
m4: IModule<S4>,
m5: IModule<S5>,
m6: IModule<S6>
): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6>;
export function createStore<S1, S2, S3, S4, S5, S6, S7>(
initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6 & S7>,
enhancers: StoreEnhancer[],
extensions: IExtension[],
m1: IModule<S1>,
m2: IModule<S2>,
m3: IModule<S3>,
m4: IModule<S4>,
m5: IModule<S5>,
m6: IModule<S6>,
m7: IModule<S7>
): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6 & S7>;
export function createStore<S1, S2, S3, S4, S5, S6, S7, S8>(
initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8>,
enhancers: StoreEnhancer[],
extensions: IExtension[],
m1: IModule<S1>,
m2: IModule<S2>,
m3: IModule<S3>,
m4: IModule<S4>,
m5: IModule<S5>,
m6: IModule<S6>,
m7: IModule<S7>,
m8: IModule<S8>
): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8>;
export function createStore<State>(
initialState: DeepPartial<State>,
enhancers: StoreEnhancer[],
extensions: IExtension[],
...initialModules: IModule<any>[]
): IModuleStore<State>;
export function createStore<State>(
initialState: DeepPartial<State>,
enhancers: StoreEnhancer[],
extensions: IExtension[],
...initialModules: IModule<any>[]
): IModuleStore<State> {
if (!extensions) {
extensions = [];
}
const extensionMiddleware = extensions.reduce(
(mw, p) => {
if (p.middleware) {
mw.push(...p.middleware)
}
const extensionMiddleware = extensions.reduce((mw, p) => {
if (p.middleware) {
mw.push(...p.middleware);
}
return mw;
},
[]
);
return mw;
}, []);
let composeEnhancers = compose;
//@ts-ignore
if (process.env.NODE_ENV === "development") {
composeEnhancers = window["__REDUX_DEVTOOLS_EXTENSION_COMPOSE__"] || compose;
composeEnhancers =
window["__REDUX_DEVTOOLS_EXTENSION_COMPOSE__"] || compose;
}
const middlewareManager = getRefCountedManager(getMiddlewareManager(), (a, b) => a === b);
const middlewareManager = getRefCountedManager(
getMiddlewareManager(),
(a, b) => a === b
);
const enhancer = composeEnhancers(
...enhancers,
applyMiddleware(
...extensionMiddleware,
middlewareManager.enhancer));
applyMiddleware(...extensionMiddleware, middlewareManager.enhancer)
);
const modules = getRefCountedManager(
getModuleManager<State>(middlewareManager, extensions),
(a: IModule<any>, b: IModule<any>) => a.id === b.id);
(a: IModule<any>, b: IModule<any>) => a.id === b.id
);
// Create store
const store: IModuleStore<State> = createReduxStore<State, any, {}, {}>(
@ -64,9 +149,9 @@ export function createStore<State>(initialState: DeepPartial<State>, enhancers:
return {
remove: () => {
modules.remove(modulesToBeAdded);
}
},
};
}
};
const addModule = (moduleToBeAdded: IModule<any>) => {
return addModules([moduleToBeAdded]);
@ -76,7 +161,7 @@ export function createStore<State>(initialState: DeepPartial<State>, enhancers:
if (p.onModuleManagerCreated) {
p.onModuleManagerCreated({
addModule,
addModules
addModules,
});
}
});
@ -95,7 +180,6 @@ export function createStore<State>(initialState: DeepPartial<State>, enhancers:
});
};
store.addModules(initialModules);
return store as IModuleStore<State>;

Просмотреть файл

@ -1,4 +1,3 @@
export interface IMap<K, V> {
keys: K[];
get(key: K): V;
@ -10,7 +9,9 @@ export interface IMap<K, V> {
* We will use it where we can not use the default Map as the Map class do not allow custom compare function
* @param equals Optional, a comparer to use
*/
export function getMap<K, V>(equals: (key1: K, key2: K) => boolean): IMap<K, V> {
export function getMap<K, V>(
equals: (key1: K, key2: K) => boolean
): IMap<K, V> {
const keys: K[] = [];
const values: { [key: number]: V } = {};
@ -61,6 +62,6 @@ export function getMap<K, V>(equals: (key1: K, key2: K) => boolean): IMap<K, V>
const value = values[index];
delete values[index];
return value;
}
}
},
};
}

Просмотреть файл

@ -1,4 +1,3 @@
export interface IRefCounter<T> {
/**
* Gets refeerence count for given item
@ -16,7 +15,9 @@ export interface IRefCounter<T> {
}
/** Ref counts given object */
export function getObjectRefCounter<T>(equals: (a: T, b: T) => boolean): IRefCounter<T> {
export function getObjectRefCounter<T>(
equals: (a: T, b: T) => boolean
): IRefCounter<T> {
if (!equals) {
equals = (a, b) => a === b;
}
@ -72,8 +73,8 @@ export function getObjectRefCounter<T>(equals: (a: T, b: T) => boolean): IRefCou
counts[index] = counts[index] - 1;
return false;
}
}
},
};
}
/**
@ -82,7 +83,6 @@ export function getObjectRefCounter<T>(equals: (a: T, b: T) => boolean): IRefCou
export function getStringRefCounter(): IRefCounter<string> {
const values: { [key: string]: number } = {};
return {
/**
* Returns current ref count for the key
*/
@ -126,6 +126,6 @@ export function getStringRefCounter(): IRefCounter<string> {
values[key]--;
return false;
}
}
},
};
}

Просмотреть файл

@ -11,14 +11,14 @@ describe("Store with extensions", () => {
onModuleAdded: jest.fn(),
onModuleManagerCreated: jest.fn(),
onModuleRemoved: jest.fn(),
dispose: jest.fn()
}
dispose: jest.fn(),
};
});
it("Registers additional middleware", () => {
const middlewareFunction = jest.fn();
const middleware: Middleware = (api) => (next) => (action) => {
const middleware: Middleware = api => next => action => {
middlewareFunction();
};
testExtension.middleware = [middleware];
@ -46,4 +46,4 @@ describe("Store with extensions", () => {
expect(testExtension.onModuleRemoved).toHaveBeenCalled();
});
});
});

Просмотреть файл

@ -6,60 +6,103 @@ it("module manager tests", () => {
const moduleManager = getModuleManager(middlewareManager, []);
let actionsDispatched = [];
moduleManager.setDispatch((action) => {
moduleManager.setDispatch(action => {
// Push the action type to array so we can track it
actionsDispatched.push(action.type);
return action.payload || null;
});
const reducer = (state) => state || null;
const reducer = state => state || null;
const module1 = {
id: "module1",
initialActions: [{ type: "initial1" }, { type: "initial11" }],
reducerMap: { "duplicateReducer": reducer, "key11": reducer },
reducerMap: { duplicateReducer: reducer, key11: reducer },
finalActions: [{ type: "final1" }, { type: "final11" }],
sagas: [saga1]
sagas: [saga1],
};
const module2 = {
id: "module2",
initialActions: [{ type: "initial2" }, { type: "initial21" }],
reducerMap: { "duplicateReducer": reducer, "key2": reducer }, // Keep one reducer duplicated to ensure ref counting of reducer
reducerMap: { duplicateReducer: reducer, key2: reducer }, // Keep one reducer duplicated to ensure ref counting of reducer
finalActions: [{ type: "final2" }, { type: "final21" }],
sagas: [saga1] //Keep the same saga to validate saga ref counting
sagas: [saga1], //Keep the same saga to validate saga ref counting
};
// Add first module
moduleManager.add([module1]);
// Test initial actions are dispatched for module1
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11"]);
expect(actionsDispatched).toEqual([
"@@Internal/ModuleManager/ModuleAdded",
"initial1",
"initial11",
]);
// Add second module
moduleManager.add([module2]);
// Test initial actions are dispatched for module2
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11", "@@Internal/ModuleManager/ModuleAdded", "initial2", "initial21"]);
expect(actionsDispatched).toEqual([
"@@Internal/ModuleManager/ModuleAdded",
"initial1",
"initial11",
"@@Internal/ModuleManager/ModuleAdded",
"initial2",
"initial21",
]);
// Remove Module1
moduleManager.remove([module1]);
// Test final actions are dispatched for module1
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11", "@@Internal/ModuleManager/ModuleAdded", "initial2", "initial21", "final1", "final11", "@@Internal/ModuleManager/ModuleRemoved"]);
expect(actionsDispatched).toEqual([
"@@Internal/ModuleManager/ModuleAdded",
"initial1",
"initial11",
"@@Internal/ModuleManager/ModuleAdded",
"initial2",
"initial21",
"final1",
"final11",
"@@Internal/ModuleManager/ModuleRemoved",
]);
// Remove Module1 again
moduleManager.remove([module1]);
// Test no additional actions are dispatched
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11", "@@Internal/ModuleManager/ModuleAdded", "initial2", "initial21", "final1", "final11", "@@Internal/ModuleManager/ModuleRemoved"]);
expect(actionsDispatched).toEqual([
"@@Internal/ModuleManager/ModuleAdded",
"initial1",
"initial11",
"@@Internal/ModuleManager/ModuleAdded",
"initial2",
"initial21",
"final1",
"final11",
"@@Internal/ModuleManager/ModuleRemoved",
]);
// Remove Module2
moduleManager.remove([module2]);
// Test no additional actions are dispatched
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11", "@@Internal/ModuleManager/ModuleAdded", "initial2", "initial21", "final1", "final11", "@@Internal/ModuleManager/ModuleRemoved", "final2", "final21", "@@Internal/ModuleManager/ModuleRemoved"]);
expect(actionsDispatched).toEqual([
"@@Internal/ModuleManager/ModuleAdded",
"initial1",
"initial11",
"@@Internal/ModuleManager/ModuleAdded",
"initial2",
"initial21",
"final1",
"final11",
"@@Internal/ModuleManager/ModuleRemoved",
"final2",
"final21",
"@@Internal/ModuleManager/ModuleRemoved",
]);
});
it("Dispose disposes all modules", () => {
@ -67,28 +110,33 @@ it("Dispose disposes all modules", () => {
const moduleManager = getModuleManager(middlewareManager, []);
let actionsDispatched = [];
moduleManager.setDispatch((action) => {
moduleManager.setDispatch(action => {
// Push the action type to array so we can track it
actionsDispatched.push(action.type);
return action.payload || null;
});
const reducer = (state) => state || null;
const reducer = state => state || null;
const module1 = {
id: "module1",
initialActions: [{ type: "initial1" }, { type: "initial11" }],
reducerMap: { "duplicateReducer": reducer, "key11": reducer },
reducerMap: { duplicateReducer: reducer, key11: reducer },
finalActions: [{ type: "final1" }, { type: "final11" }],
sagas: [saga1]
sagas: [saga1],
};
// Add first module
moduleManager.add([module1]);
moduleManager.dispose();
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11", "final1", "final11", "@@Internal/ModuleManager/ModuleRemoved"]);
})
expect(actionsDispatched).toEqual([
"@@Internal/ModuleManager/ModuleAdded",
"initial1",
"initial11",
"final1",
"final11",
"@@Internal/ModuleManager/ModuleRemoved",
]);
});
export function* saga1() {
}
export function* saga1() {}

Просмотреть файл

@ -1,20 +1,25 @@
import { getReducerManager, getRefCountedReducerManager } from "../../Managers/ReducerManager";
import {
getReducerManager,
getRefCountedReducerManager,
} from "../../Managers/ReducerManager";
interface ITestState {
name: string;
age: number
};
age: number;
}
const reduce = (state: any, action: any) => state;
it("reducer manager tests", () => {
const reducerManager = getReducerManager<ITestState>({
name: reduce,
age: reduce
age: reduce,
});
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "age"]);
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
"name",
"age",
]);
reducerManager.remove("age");
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name"]);
@ -26,24 +31,38 @@ it("reducer manager tests", () => {
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name"]);
reducerManager.add("city", reduce);
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "city"]);
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
"name",
"city",
]);
});
it("ref counting reducers", () => {
const reducerManager = getRefCountedReducerManager(getReducerManager<ITestState>({
name: reduce,
age: reduce
}));
const reducerManager = getRefCountedReducerManager(
getReducerManager<ITestState>({
name: reduce,
age: reduce,
})
);
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "age"]);
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
"name",
"age",
]);
// Increment the ref count
reducerManager.add("age", reduce);
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "age"]);
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
"name",
"age",
]);
// Decrement the ref count
reducerManager.remove("age");
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "age"]);
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
"name",
"age",
]);
// This time it should be removed
reducerManager.remove("age");
@ -53,5 +72,8 @@ it("ref counting reducers", () => {
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name"]);
reducerManager.add("city", reduce);
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "city"]);
});
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
"name",
"city",
]);
});

Просмотреть файл

@ -6,7 +6,7 @@ it("ref counted manager tests", () => {
getItems: () => Array.from(items.keys()),
add: (s: string[]) => s.length > 0 && s.forEach(s1 => items.add(s1)),
remove: (s: string[]) => s.forEach(s1 => items.delete(s1)),
dispose: () => { }
dispose: () => {},
};
const refCounter = getRefCountedManager(manager, (a, b) => a === b);
@ -32,4 +32,4 @@ it("ref counted manager tests", () => {
refCounter.remove(["c"]);
expect(refCounter.getItems()).toEqual(["b"]);
});
});

Просмотреть файл

@ -11,4 +11,4 @@ it("comparable map tests", () => {
expect(map.remove(1)).toBe(2);
expect(map.remove(1)).toBe(undefined);
expect(map.remove(2)).toBe(3);
});
});

Просмотреть файл

@ -1,4 +1,7 @@
import { getStringRefCounter, getObjectRefCounter } from "../../Utils/RefCounter";
import {
getStringRefCounter,
getObjectRefCounter,
} from "../../Utils/RefCounter";
it("tests string ref counter", () => {
const refCounter = getStringRefCounter();
expect(refCounter.getCount("foobar")).toBe(0);
@ -25,7 +28,6 @@ it("tests string ref counter", () => {
expect(refCounter.getCount("a")).toBe(0);
});
it("tests object ref counter", () => {
const refCounter = getObjectRefCounter<Function>((a, b) => a === b);
expect(refCounter.getCount(foobar)).toBe(0);
@ -46,11 +48,6 @@ it("tests object ref counter", () => {
expect(refCounter.getCount(a)).toBe(0);
});
function foobar() {
}
function a() {
}
function foobar() {}
function a() {}

Просмотреть файл

@ -1,10 +1,6 @@
{
"compilerOptions": {
"lib": [
"dom",
"es2015",
"es2016"
],
"lib": ["dom", "es2015", "es2016"],
"declaration": true,
"noUnusedLocals": true,
"noImplicitAny": false,
@ -14,10 +10,6 @@
"rootDir": "src",
"outDir": "lib"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"src/__tests__/**/*.ts"
]
}
"include": ["src/**/*.ts"],
"exclude": ["src/__tests__/**/*.ts"]
}

Просмотреть файл

@ -1,3 +1 @@
{
}
{}

Просмотреть файл

@ -1,35 +1,39 @@
let webpack = require("webpack");
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
let BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
module.exports = (env, argv) => {
let mode_env = argv.mode || 'development';
let mode_env = argv.mode || "development";
return {
devtool: 'source-map',
devtool: "source-map",
entry: {
main: './lib/index'
main: "./lib/index",
},
output: {
library: "redux-dynamic-modules",
libraryTarget: "umd",
filename: mode_env === "production" ? "redux-dynamic-modules.min.js" : "redux-dynamic-modules.js",
path: __dirname + "/dist/"
filename:
mode_env === "production"
? "redux-dynamic-modules.min.js"
: "redux-dynamic-modules.js",
path: __dirname + "/dist/",
},
externals: {
"prop-types": "prop-types",
"react": "react",
"redux": "redux",
react: "react",
redux: "redux",
"react-redux": "react-redux",
"redux-saga": "redux-saga"
"redux-saga": "redux-saga",
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
analyzerMode: "static",
reportFilename: `react-redux-module.stats.html`,
openAnalyzer: false
})
]
openAnalyzer: false,
}),
],
};
};
};

Просмотреть файл

@ -4,7 +4,7 @@ This project is a modified version of [Redux Todo App](https://github.com/reduxj
This project template was originally built with [Create React App](https://github.com/facebookincubator/create-react-app), which provides a simple way to start React projects with no build configuration needed.
Projects built with Create-React-App include support for ES6 syntax, as well as several unofficial / not-yet-final forms of Javascript syntax such as Class Properties and JSX. See the list of [language features and polyfills supported by Create-React-App](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#supported-language-features-and-polyfills) for more information.
Projects built with Create-React-App include support for ES6 syntax, as well as several unofficial / not-yet-final forms of Javascript syntax such as Class Properties and JSX. See the list of [language features and polyfills supported by Create-React-App](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#supported-language-features-and-polyfills) for more information.
## Available Scripts
@ -35,4 +35,3 @@ If you arent satisfied with the build tool and configuration choices, you can
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.

26032
packages/todo-example/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,22 +1,22 @@
{
"name": "redux-dynamic-modules-todos-example",
"version": "3.0.3",
"private": true,
"devDependencies": {
"react-scripts": "^1.1.4"
},
"dependencies": {
"prop-types": "^15.6.1",
"react": "^16.3.1",
"react-dom": "^16.3.1",
"react-redux": "^5.0.7",
"redux": "^3.5.2",
"redux-dynamic-modules": "^3.0.3",
"redux-saga": "^0.16.0"
},
"scripts": {
"start": "react-scripts start",
"build:dev": "react-scripts build",
"eject": "react-scripts eject"
}
"name": "redux-dynamic-modules-todos-example",
"version": "3.0.3",
"private": true,
"devDependencies": {
"react-scripts": "^1.1.4"
},
"dependencies": {
"prop-types": "^15.6.1",
"react": "^16.3.1",
"react-dom": "^16.3.1",
"react-redux": "^5.0.7",
"redux": "^3.5.2",
"redux-dynamic-modules": "^3.0.3",
"redux-saga": "^0.16.0"
},
"scripts": {
"start": "react-scripts start",
"build:dev": "react-scripts build",
"eject": "react-scripts eject"
}
}

Просмотреть файл

@ -1,16 +1,24 @@
import React from 'react'
import RootView from "../containers/root/rootView"
import React from "react";
import RootView from "../containers/root/rootView";
const App = () => (<div>
<RootView />
const App = () => (
<div>
<div>This app contains two modules</div>
<div>1) Todo</div>
<div>2) Shopping list</div>
<RootView />
<div>
<div>This app contains two modules</div>
<div>1) Todo</div>
<div>2) Shopping list</div>
<div>By clicking on each button above the modules are loaded and their views are displayed.</div>
<div>When you switch between them the previous module is unloaded and its state is cleared.</div>
<div>
By clicking on each button above the modules are loaded and
their views are displayed.
</div>
<div>
When you switch between them the previous module is unloaded and
its state is cleared.
</div>
</div>
</div>
</div>)
);
export default App
export default App;

Просмотреть файл

@ -1,17 +1,15 @@
import React from 'react'
import FilterLink from '../../containers/root/FilterLink'
import { VisibilityFilters } from '../../modules/root/actions'
import React from "react";
import FilterLink from "../../containers/root/FilterLink";
import { VisibilityFilters } from "../../modules/root/actions";
const Header = () => (
<div>
<span>Show: </span>
<FilterLink filter={VisibilityFilters.SHOW_TOOD}>
Todo View
</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_SHOPPING_LIST}>
Shopping List
</FilterLink>
</div>
);
export default Header
<div>
<span>Show: </span>
<FilterLink filter={VisibilityFilters.SHOW_TOOD}>Todo View</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_SHOPPING_LIST}>
Shopping List
</FilterLink>
</div>
);
export default Header;

Просмотреть файл

@ -1,22 +1,21 @@
import React from 'react'
import PropTypes from 'prop-types'
import React from "react";
import PropTypes from "prop-types";
const Link = ({ active, children, onClick }) => (
<button
onClick={onClick}
disabled={active}
style={{
marginLeft: '4px',
}}
>
{children}
onClick={onClick}
disabled={active}
style={{
marginLeft: "4px",
}}>
{children}
</button>
)
);
Link.propTypes = {
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired
}
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired,
};
export default Link
export default Link;

Просмотреть файл

@ -1,20 +1,16 @@
import React from 'react'
import FilterLink from '../../containers/shoppinglist/FilterItemLink'
import { VisibilityFilters } from '../../modules/shoppinglist/actions'
import React from "react";
import FilterLink from "../../containers/shoppinglist/FilterItemLink";
import { VisibilityFilters } from "../../modules/shoppinglist/actions";
const Footer = () => (
<div>
<div>
<span>Show: </span>
<FilterLink filter={VisibilityFilters.SHOW_ALL}>
All
</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>
Active
</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_ALL}>All</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>
Completed
Completed
</FilterLink>
</div>
)
</div>
);
export default Footer
export default Footer;

Просмотреть файл

@ -1,21 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
import React from "react";
import PropTypes from "prop-types";
const Item = ({ onClick, completed, text }) => (
<li
onClick={onClick}
style={{
textDecoration: completed ? 'line-through' : 'none'
}}
>
{text}
</li>
)
<li
onClick={onClick}
style={{
textDecoration: completed ? "line-through" : "none",
}}>
{text}
</li>
);
Item.propTypes = {
onClick: PropTypes.func.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired
}
onClick: PropTypes.func.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired,
};
export default Item
export default Item;

Просмотреть файл

@ -1,26 +1,24 @@
import React from 'react'
import PropTypes from 'prop-types'
import Item from './Item'
import React from "react";
import PropTypes from "prop-types";
import Item from "./Item";
const ItemList = ({ todos, toggleItem }) => (
<ul>
{todos.map(todo =>
<Item
key={todo.id}
{...todo}
onClick={() => toggleItem(todo.id)}
/>
)}
</ul>
)
<ul>
{todos.map(todo => (
<Item key={todo.id} {...todo} onClick={() => toggleItem(todo.id)} />
))}
</ul>
);
ItemList.propTypes = {
todos: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired
}).isRequired).isRequired,
toggleItem: PropTypes.func.isRequired
}
todos: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired,
}).isRequired
).isRequired,
toggleItem: PropTypes.func.isRequired,
};
export default ItemList
export default ItemList;

Просмотреть файл

@ -1,22 +1,21 @@
import React from 'react'
import PropTypes from 'prop-types'
import React from "react";
import PropTypes from "prop-types";
const Link = ({ active, children, onClick }) => (
<button
onClick={onClick}
disabled={active}
style={{
marginLeft: '4px',
}}
>
{children}
onClick={onClick}
disabled={active}
style={{
marginLeft: "4px",
}}>
{children}
</button>
)
);
Link.propTypes = {
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired
}
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired,
};
export default Link
export default Link;

Просмотреть файл

@ -1,20 +1,16 @@
import React from 'react'
import FilterLink from '../../containers/todo/FilterLink'
import { VisibilityFilters } from '../../modules/todo/actions'
import React from "react";
import FilterLink from "../../containers/todo/FilterLink";
import { VisibilityFilters } from "../../modules/todo/actions";
const Footer = () => (
<div>
<span>Show: </span>
<FilterLink filter={VisibilityFilters.SHOW_ALL}>
All
</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>
Active
</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>
Completed
</FilterLink>
</div>
)
<div>
<span>Show: </span>
<FilterLink filter={VisibilityFilters.SHOW_ALL}>All</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>
Completed
</FilterLink>
</div>
);
export default Footer
export default Footer;

Просмотреть файл

@ -1,22 +1,21 @@
import React from 'react'
import PropTypes from 'prop-types'
import React from "react";
import PropTypes from "prop-types";
const Link = ({ active, children, onClick }) => (
<button
onClick={onClick}
disabled={active}
style={{
marginLeft: '4px',
}}
>
{children}
onClick={onClick}
disabled={active}
style={{
marginLeft: "4px",
}}>
{children}
</button>
)
);
Link.propTypes = {
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired
}
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired,
};
export default Link
export default Link;

Просмотреть файл

@ -1,21 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'
import React from "react";
import PropTypes from "prop-types";
const Todo = ({ onClick, completed, text }) => (
<li
onClick={onClick}
style={{
textDecoration: completed ? 'line-through' : 'none'
}}
>
{text}
</li>
)
<li
onClick={onClick}
style={{
textDecoration: completed ? "line-through" : "none",
}}>
{text}
</li>
);
Todo.propTypes = {
onClick: PropTypes.func.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired
}
onClick: PropTypes.func.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired,
};
export default Todo
export default Todo;

Просмотреть файл

@ -1,26 +1,24 @@
import React from 'react'
import PropTypes from 'prop-types'
import Todo from './Todo'
import React from "react";
import PropTypes from "prop-types";
import Todo from "./Todo";
const TodoList = ({ todos, toggleTodo }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => toggleTodo(todo.id)}
/>
)}
</ul>
)
<ul>
{todos.map(todo => (
<Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} />
))}
</ul>
);
TodoList.propTypes = {
todos: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired
}).isRequired).isRequired,
toggleTodo: PropTypes.func.isRequired
}
todos: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
completed: PropTypes.bool.isRequired,
text: PropTypes.string.isRequired,
}).isRequired
).isRequired,
toggleTodo: PropTypes.func.isRequired,
};
export default TodoList
export default TodoList;

Просмотреть файл

@ -1,16 +1,16 @@
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../../modules/root/actions'
import Link from '../../components/root/Link'
import { connect } from "react-redux";
import { setVisibilityFilter } from "../../modules/root/actions";
import Link from "../../components/root/Link";
const mapStateToProps = (state, ownProps) => ({
active: ownProps.filter === state.root.visibilityFilter
})
active: ownProps.filter === state.root.visibilityFilter,
});
const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Link)
mapStateToProps,
mapDispatchToProps
)(Link);

Просмотреть файл

@ -1,33 +1,27 @@
import React from 'react'
import PropTypes from 'prop-types'
import {
connect
} from 'react-redux'
import {
VisibilityFilters
} from '../../modules/root/actions'
import TodoView from '../todo/TodoView';
import ShoppingListView from '../shoppinglist/ShoppingListView';
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { VisibilityFilters } from "../../modules/root/actions";
import TodoView from "../todo/TodoView";
import ShoppingListView from "../shoppinglist/ShoppingListView";
const mapStateToProps = (state) => {
const mapStateToProps = state => {
return {
visibilityFilter: state.root
}
}
const Views = ({visibilityFilter}) => {
if (visibilityFilter === VisibilityFilters.SHOW_TOOD) {
return <TodoView / >
}
if (visibilityFilter === VisibilityFilters.SHOW_SHOPPING_LIST) {
return <ShoppingListView / >
}
return null;
}
Views.propTypes = {
visibilityFilter: PropTypes.string.isRequired
visibilityFilter: state.root,
};
};
export default connect(
mapStateToProps
)(Views)
const Views = ({ visibilityFilter }) => {
if (visibilityFilter === VisibilityFilters.SHOW_TOOD) {
return <TodoView />;
}
if (visibilityFilter === VisibilityFilters.SHOW_SHOPPING_LIST) {
return <ShoppingListView />;
}
return null;
};
Views.propTypes = {
visibilityFilter: PropTypes.string.isRequired,
};
export default connect(mapStateToProps)(Views);

Просмотреть файл

@ -1,14 +1,14 @@
import React from 'react';
import { DynamicModuleLoader } from 'redux-dynamic-modules'
import { getRootModule } from '../../modules/root/rootModule';
import Header from '../../components/root/Header';
import Views from "./Views"
import React from "react";
import { DynamicModuleLoader } from "redux-dynamic-modules";
import { getRootModule } from "../../modules/root/rootModule";
import Header from "../../components/root/Header";
import Views from "./Views";
const Rootview = () => (
<DynamicModuleLoader modules={[getRootModule()]}>
<Header />
<Views />
</DynamicModuleLoader>
);
export default Rootview
<DynamicModuleLoader modules={[getRootModule()]}>
<Header />
<Views />
</DynamicModuleLoader>
);
export default Rootview;

Просмотреть файл

@ -1,27 +1,26 @@
import React from 'react'
import { connect } from 'react-redux'
import { addItem } from '../../modules/shoppinglist/actions'
import React from "react";
import { connect } from "react-redux";
import { addItem } from "../../modules/shoppinglist/actions";
const AddItem = ({ dispatch }) => {
let input
let input;
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addItem(input.value))
input.value = ''
}}>
<input ref={node => input = node} />
<button type="submit">
Add Item
</button>
</form>
</div>
)
}
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
if (!input.value.trim()) {
return;
}
dispatch(addItem(input.value));
input.value = "";
}}>
<input ref={node => (input = node)} />
<button type="submit">Add Item</button>
</form>
</div>
);
};
export default connect()(AddItem)
export default connect()(AddItem);

Просмотреть файл

@ -1,16 +1,16 @@
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../../modules/shoppinglist/actions'
import Link from '../../components/shoppinglist/Link'
import { connect } from "react-redux";
import { setVisibilityFilter } from "../../modules/shoppinglist/actions";
import Link from "../../components/shoppinglist/Link";
const mapStateToProps = (state, ownProps) => ({
active: ownProps.filter === state.shoppingList.visibilityFilter
})
active: ownProps.filter === state.shoppingList.visibilityFilter,
});
const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Link)
mapStateToProps,
mapDispatchToProps
)(Link);

Просмотреть файл

@ -1,17 +1,17 @@
import AddItem from './AddItem';
import React from 'react';
import { DynamicModuleLoader } from 'redux-dynamic-modules';
import VisibleItemsList from './VisibleItemsList';
import Footer from '../../components/shoppinglist/Footer';
import { getShoppingListModule } from '../../modules/shoppinglist/shoppingListModule';
import AddItem from "./AddItem";
import React from "react";
import { DynamicModuleLoader } from "redux-dynamic-modules";
import VisibleItemsList from "./VisibleItemsList";
import Footer from "../../components/shoppinglist/Footer";
import { getShoppingListModule } from "../../modules/shoppinglist/shoppingListModule";
const ShoppingListView = () => (
<DynamicModuleLoader modules={[getShoppingListModule()]}>
<DynamicModuleLoader modules={[getShoppingListModule()]}>
<div>Shopping list view</div>
<AddItem />
<VisibleItemsList />
<Footer />
</DynamicModuleLoader>
);
</DynamicModuleLoader>
);
export default ShoppingListView
export default ShoppingListView;

Просмотреть файл

@ -1,30 +1,33 @@
import { connect } from 'react-redux'
import { toggleItem } from '../../modules/shoppinglist/actions'
import ItemList from '../../components/shoppinglist/ItemList'
import { VisibilityFilters } from '../../modules/shoppinglist/actions'
import { connect } from "react-redux";
import { toggleItem } from "../../modules/shoppinglist/actions";
import ItemList from "../../components/shoppinglist/ItemList";
import { VisibilityFilters } from "../../modules/shoppinglist/actions";
const getVisibleItems = (items, filter) => {
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return items
case VisibilityFilters.SHOW_COMPLETED:
return items.filter(t => t.completed)
case VisibilityFilters.SHOW_ACTIVE:
return items.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + filter)
}
}
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return items;
case VisibilityFilters.SHOW_COMPLETED:
return items.filter(t => t.completed);
case VisibilityFilters.SHOW_ACTIVE:
return items.filter(t => !t.completed);
default:
throw new Error("Unknown filter: " + filter);
}
};
const mapStateToProps = state => ({
todos: getVisibleItems(state.shoppingList.items, state.shoppingList.visibilityFilter)
})
todos: getVisibleItems(
state.shoppingList.items,
state.shoppingList.visibilityFilter
),
});
const mapDispatchToProps = dispatch => ({
toggleItem: id => dispatch(toggleItem(id))
})
toggleItem: id => dispatch(toggleItem(id)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ItemList)
mapStateToProps,
mapDispatchToProps
)(ItemList);

Просмотреть файл

@ -1,27 +1,26 @@
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../../modules/todo/actions'
import React from "react";
import { connect } from "react-redux";
import { addTodo } from "../../modules/todo/actions";
const AddTodo = ({ dispatch }) => {
let input
let input;
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}}>
<input ref={node => input = node} />
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
if (!input.value.trim()) {
return;
}
dispatch(addTodo(input.value));
input.value = "";
}}>
<input ref={node => (input = node)} />
<button type="submit">Add Todo</button>
</form>
</div>
);
};
export default connect()(AddTodo)
export default connect()(AddTodo);

Просмотреть файл

@ -1,16 +1,16 @@
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../../modules/todo/actions'
import Link from '../../components/todo/Link'
import { connect } from "react-redux";
import { setVisibilityFilter } from "../../modules/todo/actions";
import Link from "../../components/todo/Link";
const mapStateToProps = (state, ownProps) => ({
active: ownProps.filter === state.todo.visibilityFilter
})
active: ownProps.filter === state.todo.visibilityFilter,
});
const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Link)
mapStateToProps,
mapDispatchToProps
)(Link);

Просмотреть файл

@ -1,17 +1,17 @@
import AddTodo from './AddTodo';
import React from 'react';
import { DynamicModuleLoader } from 'redux-dynamic-modules';
import VisibleTodoList from './VisibleTodoList';
import Footer from '../../components/todo/Footer';
import { getTodoModule } from '../../modules/todo/todoModule';
import AddTodo from "./AddTodo";
import React from "react";
import { DynamicModuleLoader } from "redux-dynamic-modules";
import VisibleTodoList from "./VisibleTodoList";
import Footer from "../../components/todo/Footer";
import { getTodoModule } from "../../modules/todo/todoModule";
const TodoView = () => (
<DynamicModuleLoader modules={[getTodoModule()]}>
<div>Todo View</div>
<AddTodo />
<VisibleTodoList />
<Footer />
</DynamicModuleLoader>
)
export default TodoView
<DynamicModuleLoader modules={[getTodoModule()]}>
<div>Todo View</div>
<AddTodo />
<VisibleTodoList />
<Footer />
</DynamicModuleLoader>
);
export default TodoView;

Просмотреть файл

@ -1,30 +1,30 @@
import { connect } from 'react-redux'
import { toggleTodo } from '../../modules/todo/actions'
import TodoList from '../../components/todo/TodoList'
import { VisibilityFilters } from '../../modules/todo/actions'
import { connect } from "react-redux";
import { toggleTodo } from "../../modules/todo/actions";
import TodoList from "../../components/todo/TodoList";
import { VisibilityFilters } from "../../modules/todo/actions";
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return todos
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter(t => t.completed)
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + filter)
}
}
switch (filter) {
case VisibilityFilters.SHOW_ALL:
return todos;
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter(t => t.completed);
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter(t => !t.completed);
default:
throw new Error("Unknown filter: " + filter);
}
};
const mapStateToProps = state => ({
todos: getVisibleTodos(state.todo.todos, state.todo.visibilityFilter)
})
todos: getVisibleTodos(state.todo.todos, state.todo.visibilityFilter),
});
const mapDispatchToProps = dispatch => ({
toggleTodo: id => dispatch(toggleTodo(id))
})
toggleTodo: id => dispatch(toggleTodo(id)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
mapStateToProps,
mapDispatchToProps
)(TodoList);

Просмотреть файл

@ -1,14 +1,14 @@
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux-dynamic-modules'
import { Provider } from 'react-redux'
import App from './components/App'
import React from "react";
import { render } from "react-dom";
import { createStore } from "redux-dynamic-modules";
import { Provider } from "react-redux";
import App from "./components/App";
const store = createStore({}, [], []);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);

Просмотреть файл

@ -1,11 +1,11 @@
export const setVisibilityFilter = text => {
debugger;
return {
type: 'SHOW_VIEW',
text
}
}
debugger;
return {
type: "SHOW_VIEW",
text,
};
};
export const VisibilityFilters = {
SHOW_TOOD: 'SHOW_TODO',
SHOW_SHOPPING_LIST: 'SHOW_SHOPPING_LIST'
}
SHOW_TOOD: "SHOW_TODO",
SHOW_SHOPPING_LIST: "SHOW_SHOPPING_LIST",
};

Просмотреть файл

@ -1,11 +1,11 @@
const rootReducer = (state = "", action) => {
switch (action.type) {
case 'SHOW_VIEW':
debugger;
return action.text;
default:
return state;
}
}
switch (action.type) {
case "SHOW_VIEW":
debugger;
return action.text;
default:
return state;
}
};
export default rootReducer;
export default rootReducer;

Просмотреть файл

@ -1,9 +1,9 @@
import rootReducer from "./reducers"
import rootReducer from "./reducers";
export function getRootModule() {
return {
id: "root",
reducerMap: {
root: rootReducer
}
}
}
root: rootReducer,
},
};
}

Просмотреть файл

@ -1,22 +1,22 @@
let nextItemId = 0
let nextItemId = 0;
export const addItem = text => ({
type: 'ADD_ITEM',
id: nextItemId++,
text
})
type: "ADD_ITEM",
id: nextItemId++,
text,
});
export const setVisibilityFilter = filter => ({
type: 'SET_VISIBILITY_FILTER_ITEMS',
filter
})
type: "SET_VISIBILITY_FILTER_ITEMS",
filter,
});
export const toggleItem = id => ({
type: 'TOGGLE_ITEM',
id
})
type: "TOGGLE_ITEM",
id,
});
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL_ITEMS',
SHOW_COMPLETED: 'SHOW_COMPLETED_ITEMS',
SHOW_ACTIVE: 'SHOW_ACTIVE_ITEMS'
}
SHOW_ALL: "SHOW_ALL_ITEMS",
SHOW_COMPLETED: "SHOW_COMPLETED_ITEMS",
SHOW_ACTIVE: "SHOW_ACTIVE_ITEMS",
};

Просмотреть файл

@ -1,8 +1,8 @@
import { combineReducers } from 'redux'
import items from './items'
import visibilityFilter from './visibilityFilter'
import { combineReducers } from "redux";
import items from "./items";
import visibilityFilter from "./visibilityFilter";
export default combineReducers({
items,
visibilityFilter
})
items,
visibilityFilter,
});

Просмотреть файл

@ -1,23 +1,23 @@
const items = (state = [], action) => {
switch (action.type) {
case 'ADD_ITEM':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_ITEM':
return state.map(item =>
(item.id === action.id)
? {...item, completed: !item.completed}
: item
)
default:
return state
}
}
switch (action.type) {
case "ADD_ITEM":
return [
...state,
{
id: action.id,
text: action.text,
completed: false,
},
];
case "TOGGLE_ITEM":
return state.map(item =>
item.id === action.id
? { ...item, completed: !item.completed }
: item
);
default:
return state;
}
};
export default items
export default items;

Просмотреть файл

@ -1,12 +1,12 @@
import { VisibilityFilters } from '../actions'
import { VisibilityFilters } from "../actions";
const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER_ITEMS':
return action.filter
default:
return state
}
}
switch (action.type) {
case "SET_VISIBILITY_FILTER_ITEMS":
return action.filter;
default:
return state;
}
};
export default visibilityFilter
export default visibilityFilter;

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше