subapp-redux
Version:
Electrode subapp redux support
146 lines (143 loc) • 5.56 kB
JavaScript
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