1 | import type {
|
2 | Reducer,
|
3 | ReducersMapObject,
|
4 | Middleware,
|
5 | Action,
|
6 | StoreEnhancer,
|
7 | Store,
|
8 | UnknownAction,
|
9 | } from 'redux'
|
10 | import {
|
11 | applyMiddleware,
|
12 | createStore,
|
13 | compose,
|
14 | combineReducers,
|
15 | isPlainObject,
|
16 | } from 'redux'
|
17 | import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension'
|
18 | import { composeWithDevTools } from './devtoolsExtension'
|
19 |
|
20 | import type {
|
21 | ThunkMiddlewareFor,
|
22 | GetDefaultMiddleware,
|
23 | } from './getDefaultMiddleware'
|
24 | import { buildGetDefaultMiddleware } from './getDefaultMiddleware'
|
25 | import type {
|
26 | ExtractDispatchExtensions,
|
27 | ExtractStoreExtensions,
|
28 | ExtractStateExtensions,
|
29 | UnknownIfNonSpecific,
|
30 | } from './tsHelpers'
|
31 | import type { Tuple } from './utils'
|
32 | import type { GetDefaultEnhancers } from './getDefaultEnhancers'
|
33 | import { buildGetDefaultEnhancers } from './getDefaultEnhancers'
|
34 |
|
35 | const IS_PRODUCTION = process.env.NODE_ENV === 'production'
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | export interface ConfigureStoreOptions<
|
43 | S = any,
|
44 | A extends Action = UnknownAction,
|
45 | M extends Tuple<Middlewares<S>> = Tuple<Middlewares<S>>,
|
46 | E extends Tuple<Enhancers> = Tuple<Enhancers>,
|
47 | P = S,
|
48 | > {
|
49 | |
50 |
|
51 |
|
52 |
|
53 | reducer: Reducer<S, A, P> | ReducersMapObject<S, A, P>
|
54 |
|
55 | |
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | middleware?: (getDefaultMiddleware: GetDefaultMiddleware<S>) => M
|
63 |
|
64 | |
65 |
|
66 |
|
67 |
|
68 |
|
69 | devTools?: boolean | DevToolsOptions
|
70 |
|
71 | |
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 | preloadedState?: P
|
81 |
|
82 | |
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | enhancers?: (getDefaultEnhancers: GetDefaultEnhancers<M>) => E
|
91 | }
|
92 |
|
93 | export type Middlewares<S> = ReadonlyArray<Middleware<{}, S>>
|
94 |
|
95 | type Enhancers = ReadonlyArray<StoreEnhancer>
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 | export type EnhancedStore<
|
104 | S = any,
|
105 | A extends Action = UnknownAction,
|
106 | E extends Enhancers = Enhancers,
|
107 | > = ExtractStoreExtensions<E> &
|
108 | Store<S, A, UnknownIfNonSpecific<ExtractStateExtensions<E>>>
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 | export function configureStore<
|
119 | S = any,
|
120 | A extends Action = UnknownAction,
|
121 | M extends Tuple<Middlewares<S>> = Tuple<[ThunkMiddlewareFor<S>]>,
|
122 | E extends Tuple<Enhancers> = Tuple<
|
123 | [StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>, StoreEnhancer]
|
124 | >,
|
125 | P = S,
|
126 | >(options: ConfigureStoreOptions<S, A, M, E, P>): EnhancedStore<S, A, E> {
|
127 | const getDefaultMiddleware = buildGetDefaultMiddleware<S>()
|
128 |
|
129 | const {
|
130 | reducer = undefined,
|
131 | middleware,
|
132 | devTools = true,
|
133 | preloadedState = undefined,
|
134 | enhancers = undefined,
|
135 | } = options || {}
|
136 |
|
137 | let rootReducer: Reducer<S, A, P>
|
138 |
|
139 | if (typeof reducer === 'function') {
|
140 | rootReducer = reducer
|
141 | } else if (isPlainObject(reducer)) {
|
142 | rootReducer = combineReducers(reducer) as unknown as Reducer<S, A, P>
|
143 | } else {
|
144 | throw new Error(
|
145 | '`reducer` is a required argument, and must be a function or an object of functions that can be passed to combineReducers',
|
146 | )
|
147 | }
|
148 |
|
149 | if (!IS_PRODUCTION && middleware && typeof middleware !== 'function') {
|
150 | throw new Error('`middleware` field must be a callback')
|
151 | }
|
152 |
|
153 | let finalMiddleware: Tuple<Middlewares<S>>
|
154 | if (typeof middleware === 'function') {
|
155 | finalMiddleware = middleware(getDefaultMiddleware)
|
156 |
|
157 | if (!IS_PRODUCTION && !Array.isArray(finalMiddleware)) {
|
158 | throw new Error(
|
159 | 'when using a middleware builder function, an array of middleware must be returned',
|
160 | )
|
161 | }
|
162 | } else {
|
163 | finalMiddleware = getDefaultMiddleware()
|
164 | }
|
165 | if (
|
166 | !IS_PRODUCTION &&
|
167 | finalMiddleware.some((item: any) => typeof item !== 'function')
|
168 | ) {
|
169 | throw new Error(
|
170 | 'each middleware provided to configureStore must be a function',
|
171 | )
|
172 | }
|
173 |
|
174 | let finalCompose = compose
|
175 |
|
176 | if (devTools) {
|
177 | finalCompose = composeWithDevTools({
|
178 |
|
179 | trace: !IS_PRODUCTION,
|
180 | ...(typeof devTools === 'object' && devTools),
|
181 | })
|
182 | }
|
183 |
|
184 | const middlewareEnhancer = applyMiddleware(...finalMiddleware)
|
185 |
|
186 | const getDefaultEnhancers = buildGetDefaultEnhancers<M>(middlewareEnhancer)
|
187 |
|
188 | if (!IS_PRODUCTION && enhancers && typeof enhancers !== 'function') {
|
189 | throw new Error('`enhancers` field must be a callback')
|
190 | }
|
191 |
|
192 | let storeEnhancers =
|
193 | typeof enhancers === 'function'
|
194 | ? enhancers(getDefaultEnhancers)
|
195 | : getDefaultEnhancers()
|
196 |
|
197 | if (!IS_PRODUCTION && !Array.isArray(storeEnhancers)) {
|
198 | throw new Error('`enhancers` callback must return an array')
|
199 | }
|
200 | if (
|
201 | !IS_PRODUCTION &&
|
202 | storeEnhancers.some((item: any) => typeof item !== 'function')
|
203 | ) {
|
204 | throw new Error(
|
205 | 'each enhancer provided to configureStore must be a function',
|
206 | )
|
207 | }
|
208 | if (
|
209 | !IS_PRODUCTION &&
|
210 | finalMiddleware.length &&
|
211 | !storeEnhancers.includes(middlewareEnhancer)
|
212 | ) {
|
213 | console.error(
|
214 | 'middlewares were provided, but middleware enhancer was not included in final enhancers - make sure to call `getDefaultEnhancers`',
|
215 | )
|
216 | }
|
217 |
|
218 | const composedEnhancer: StoreEnhancer<any> = finalCompose(...storeEnhancers)
|
219 |
|
220 | return createStore(rootReducer, preloadedState as P, composedEnhancer)
|
221 | }
|