From 515122ee99900d7f5dd16f9faf1b1eb1e58de438 Mon Sep 17 00:00:00 2001 From: unadlib Date: Tue, 3 Sep 2024 01:31:59 +0800 Subject: [PATCH] fix(reactant-module): fix plugin order issue --- .../reactant-module/src/core/createStore.ts | 22 +++++- .../reactant-module/src/core/handlePlugin.ts | 67 ++++++++++++++++++- packages/reactant-module/src/interfaces.ts | 3 +- .../test/core/handlePlugin.test.ts | 16 +++-- 4 files changed, 97 insertions(+), 11 deletions(-) diff --git a/packages/reactant-module/src/core/createStore.ts b/packages/reactant-module/src/core/createStore.ts index ccd097c8d..4a80e30da 100644 --- a/packages/reactant-module/src/core/createStore.ts +++ b/packages/reactant-module/src/core/createStore.ts @@ -55,7 +55,12 @@ import type { ThisService, } from '../interfaces'; import { getComposeEnhancers, getStageName, isEqual, perform } from '../utils'; -import { handlePlugin } from './handlePlugin'; +import { + assignPlugin, + pushPlugin, + mergePluginHooks, + generatePluginHooks, +} from './handlePlugin'; import { type Signal, signal } from './signal'; interface CreateStoreOptions { @@ -98,6 +103,9 @@ export function createStore({ const enableInspector = devOptions.enableInspector ?? false; const strict = devOptions.strict ?? false; + const _pushPluginHooks = generatePluginHooks(); + const _assignPluginHooks = generatePluginHooks(); + dynamicModules.forEach((module, key) => { try { const services = container!.getAll(key); @@ -116,9 +124,11 @@ export function createStore({ } } const multipleInjectMap = getMetadata(METADATA_KEY.multiple); + // #region sort ServiceIdentifiers // it's just a workaround for the issue of `ServiceIdentifiers` order. // InversifyJS issue: https://github.com/inversify/InversifyJS/issues/1578 const allServiceIdentifiers = Array.from(ServiceIdentifiers); + const allServiceIdentifierKeys = Array.from(ServiceIdentifiers.keys()); allServiceIdentifiers.sort(([a], [b]) => { let aDeps = []; try { @@ -142,6 +152,7 @@ export function createStore({ allServiceIdentifiers.forEach(([ServiceIdentifier, value]) => { ServiceIdentifiers.set(ServiceIdentifier, value); }); + // #endregion for (const [ServiceIdentifier] of ServiceIdentifiers) { // `Service` should be bound before `createStore`. const isMultipleInjection = multipleInjectMap.has(ServiceIdentifier); @@ -153,7 +164,12 @@ export function createStore({ const services: IService[] = container.getAll(ServiceIdentifier); loadedModules.add(ServiceIdentifier); services.forEach((service, index) => { - handlePlugin(service, pluginHooks); + const indexPlugin = allServiceIdentifierKeys.indexOf(ServiceIdentifier); + if (indexPlugin === -1) { + pushPlugin(service, _pushPluginHooks, indexPlugin); + } else { + assignPlugin(service, _assignPluginHooks, indexPlugin); + } const className = (ServiceIdentifier as Function).name; let identifier: string | undefined = typeof ServiceIdentifier === 'string' @@ -428,6 +444,8 @@ export function createStore({ }); } } + // #region keep plugin instance order + mergePluginHooks(pluginHooks, _assignPluginHooks, _pushPluginHooks); if (typeof store === 'undefined') { // load reducers and create store for Redux const reducer = isExistReducer diff --git a/packages/reactant-module/src/core/handlePlugin.ts b/packages/reactant-module/src/core/handlePlugin.ts index 2e73f81f3..4cf83f86e 100644 --- a/packages/reactant-module/src/core/handlePlugin.ts +++ b/packages/reactant-module/src/core/handlePlugin.ts @@ -1,8 +1,73 @@ +/* eslint-disable no-param-reassign */ import { ReducersMapObject, Reducer, PreloadedState } from 'redux'; +import { type FunctionComponent } from 'react'; import { PluginModule } from './plugin'; import { PluginHooks, HandlePlugin } from '../interfaces'; -export const handlePlugin: HandlePlugin = ( +export const generatePluginHooks = (): PluginHooks => ({ + middleware: [], + beforeCombineRootReducers: [], + afterCombineRootReducers: [], + enhancer: [], + preloadedStateHandler: [], + afterCreateStore: [], + provider: [] as FunctionComponent[], +}); + +export const mergePluginHooks = ( + pluginHooks: PluginHooks, + _assignPluginHooks: PluginHooks, + _pushPluginHooks: PluginHooks +) => { + Object.keys(pluginHooks).forEach((key) => { + const assignPluginHooks = _assignPluginHooks[ + key as keyof PluginHooks + ].filter(Boolean) as any[]; + const pushPluginHooks = _pushPluginHooks[key as keyof PluginHooks] as any[]; + pluginHooks[key as keyof PluginHooks].push( + ...assignPluginHooks, + ...pushPluginHooks + ); + }); +}; + +export const assignPlugin: HandlePlugin = ( + service: any, + pluginHooks: PluginHooks, + index: number +) => { + if (service instanceof PluginModule) { + if (typeof service.beforeCombineRootReducers === 'function') { + pluginHooks.beforeCombineRootReducers[index] = ( + reducers: ReducersMapObject + ) => service.beforeCombineRootReducers!(reducers); + } + if (typeof service.afterCombineRootReducers === 'function') { + pluginHooks.afterCombineRootReducers[index] = (rootReducer: Reducer) => + service.afterCombineRootReducers!(rootReducer); + } + if (typeof service.preloadedStateHandler === 'function') { + pluginHooks.preloadedStateHandler[index] = ( + preloadedState: PreloadedState + ) => service.preloadedStateHandler!(preloadedState); + } + if (typeof service.enhancer === 'function') { + pluginHooks.enhancer[index] = service.enhancer.bind(service); + } + if (typeof service.middleware === 'function') { + pluginHooks.middleware[index] = service.middleware.bind(service); + } + if (typeof service.afterCreateStore === 'function') { + pluginHooks.afterCreateStore[index] = + service.afterCreateStore.bind(service); + } + if (typeof service.provider === 'function') { + pluginHooks.provider[index] = service.provider.bind(service); + } + } +}; + +export const pushPlugin: HandlePlugin = ( service: any, pluginHooks: PluginHooks ) => { diff --git a/packages/reactant-module/src/interfaces.ts b/packages/reactant-module/src/interfaces.ts index 2adc4f54a..c81374661 100644 --- a/packages/reactant-module/src/interfaces.ts +++ b/packages/reactant-module/src/interfaces.ts @@ -144,7 +144,8 @@ export type PluginHooks = Collection; export type HandlePlugin = ( service: T, - pluginHooks: PluginHooks + pluginHooks: PluginHooks, + index: number ) => void; export type Subscribe = ( diff --git a/packages/reactant-module/test/core/handlePlugin.test.ts b/packages/reactant-module/test/core/handlePlugin.test.ts index aa5171054..6bf6b956d 100644 --- a/packages/reactant-module/test/core/handlePlugin.test.ts +++ b/packages/reactant-module/test/core/handlePlugin.test.ts @@ -1,8 +1,8 @@ /* eslint-disable lines-between-class-members */ -import { handlePlugin, PluginModule, Store } from '../..'; +import { pushPlugin, PluginModule, Store } from '../..'; test('base `handlePlugin` with invalid service', () => { - for (const item of [ + const arr = [ {}, [], function () { @@ -11,7 +11,8 @@ test('base `handlePlugin` with invalid service', () => { () => { // }, - ]) { + ]; + for (const item of arr) { const pluginHooks = { middleware: [], beforeCombineRootReducers: [], @@ -21,7 +22,7 @@ test('base `handlePlugin` with invalid service', () => { afterCreateStore: [], provider: [], }; - handlePlugin(item, pluginHooks); + pushPlugin(item, pluginHooks, arr.indexOf(item)); Object.entries(pluginHooks).forEach(([_, hooks]) => { expect(hooks.length).toBe(0); }); @@ -29,7 +30,7 @@ test('base `handlePlugin` with invalid service', () => { }); test('base `handlePlugin` with valid service', () => { - for (const item of [ + const arr = [ { service: new (class extends PluginModule { middleware = () => () => () => null; @@ -50,7 +51,8 @@ test('base `handlePlugin` with valid service', () => { provider: 1, }, }, - ]) { + ]; + for (const item of arr) { const pluginHooks = { middleware: [], beforeCombineRootReducers: [], @@ -60,7 +62,7 @@ test('base `handlePlugin` with valid service', () => { afterCreateStore: [], provider: [], }; - handlePlugin(item.service, pluginHooks); + pushPlugin(item.service, pluginHooks, arr.indexOf(item)); Object.entries(pluginHooks).forEach(([key, hooks]) => { expect(hooks.length).toBe((item.length as any)[key]); });