UNPKG

subapp-redux

Version:

Electrode subapp redux support

146 lines (143 loc) 5.56 kB
import { configureStore, combineReducers } from "@reduxjs/toolkit"; // // - stores can be shared between subapps with reduxShareStore flag // - if it's true, then a common global store is used // - if it's a string, then it's use to name a store for sharing. // - otherwise subapp gets its own private store // - state sharing is made possible through named reducers // - each subapp must provide named reducers for states // - other subapps can then provide the same reducer under the same name // - all the reducers are then merged into a single object and then combined // - initial state handling follow these rules // - the first subapp that loads and initializes wins and all subapps load // after will use the initial state from it // - a top level initializer can be specified to do this // let persistenStoreContainer = { namedStores: {} }; function initContainer(storeContainer) { storeContainer = storeContainer || persistenStoreContainer; if (!storeContainer.namedStores) { storeContainer.namedStores = {}; } return storeContainer; } function setStoreContainer(storeContainer) { persistenStoreContainer = storeContainer; initContainer(storeContainer); } function clearSharedStore() { persistenStoreContainer.namedStores = {}; } function getSharedStore(name, storeContainer) { storeContainer = initContainer(storeContainer); return name && storeContainer.namedStores[name === true ? "_" : name] || {}; } function setSharedStore(name, contents, storeContainer) { storeContainer = initContainer(storeContainer); storeContainer.namedStores[name === true ? "_" : name] = contents; } const assert = (flag, msg) => { if (!flag) { throw new Error(msg); } }; const reducerNamesSym = "- reducer owner names -"; const originalReplaceReducerSym = "- original replace reducer -"; const newReducerContainer = () => ({ [reducerNamesSym]: [] }); const WHEN_SHARED_MSG = "When using reduxShareStore to share stores"; const errReducersMustBeObject = `${WHEN_SHARED_MSG}, reduxReducers must be an object of named reducers.`; const addSharedReducer = (info, container, reducers) => { assert(typeof reducers === "object", errReducersMustBeObject); const names = container[reducerNamesSym]; if (!container.hasOwnProperty(info.name)) { names.push(info.name); } container[info.name] = reducers; const merged = names.reduce((m, n) => Object.assign(m, container[n]), {}); return merged; }; const combineSharedReducers = (info, container, reducers) => { return combineReducers(addSharedReducer(info, container, reducers)); }; function replaceReducer(newReducers, info, storeContainer) { let { store, reducerContainer } = getSharedStore(info.reduxShareStore, storeContainer); const reducer = combineSharedReducers(info, reducerContainer, newReducers); return store[originalReplaceReducerSym](reducer); } function createSharedStore(initialState, info, storeContainer) { const sharedStoreName = info.reduxShareStore; if (sharedStoreName) { assert(info._genReduxCreateStore || !info.reduxCreateStore, `${WHEN_SHARED_MSG}, you cannot have reduxCreateStore`); let { store, reducerContainer } = getSharedStore(sharedStoreName, storeContainer); if (store) { // TODO: redux doesn't have a way to set initial state // after store's created? What can we do about this? replaceReducer(info.reduxReducers, info, storeContainer); } else { reducerContainer = newReducerContainer(); store = configureStore({ reducer: combineSharedReducers(info, reducerContainer, info.reduxReducers), preloadedState: initialState, enhancers: info.reduxEnhancer, middleware: info.middleware }); store[originalReplaceReducerSym] = store.replaceReducer; // // TODO: better handling of a replaceReducer that takes extra params // // Since this is share store, reducers must be replaced // as object of named reducers also. // // Maybe create a proxy store object, one for each sub-app // // NOTE: It's only on SSR that we need to share store within the // request and replaceReducer doesn't make sense for SSR, but it's // taking a storeContainer here anyways. // store.replaceReducer = (reducers, info2, storeContainer2) => { return replaceReducer(reducers, info2, storeContainer2); }; } setSharedStore(sharedStoreName, { store, reducerContainer }, storeContainer); return store; } // call user provided reduxCreateStore if (info.reduxCreateStore && !info._genReduxCreateStore) { // TODO: given the complexities of dealing with and maintaining store // allowing user reduxCreateStore is not a good idea. Consider for removal. return info.reduxCreateStore(initialState); } let reducer; const reducerType = typeof info.reduxReducers; if (reducerType === "function") { reducer = info.reduxReducers; } else if (reducerType === "object") { reducer = combineReducers(info.reduxReducers); } else { reducer = x => x; } return configureStore({ reducer, preloadedState: initialState, enhancers: info.reduxEnhancer, middleware: info.middleware }); } function getReduxCreateStore(info) { return (initialState, storeContainer) => createSharedStore(initialState, info, storeContainer); } export { setStoreContainer, getReduxCreateStore, createSharedStore, getSharedStore, setSharedStore, clearSharedStore }; //# sourceMappingURL=shared.js.map