-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #255 from core-ds/feat/client-event-bus
feat(*): add client-event-bus package
- Loading branch information
Showing
20 changed files
with
546 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@alfalab/client-event-bus': major | ||
--- | ||
|
||
Добавлена библиотека для обмена данными через общую шину |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
build | ||
.turbo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
module.exports = { | ||
root: true, | ||
extends: ['custom/common'], | ||
parserOptions: { | ||
tsconfigRootDir: __dirname, | ||
project: [ | ||
'./tsconfig.eslint.json', | ||
], | ||
}, | ||
overrides: [ | ||
{ | ||
files: ['**/__tests__/**/*.{ts,tsx}'], | ||
rules: { | ||
'import/no-extraneous-dependencies': 'off', | ||
}, | ||
} | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
build | ||
coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
src | ||
build/tsconfig.tsbuildinfo | ||
__tests__ | ||
.turbo | ||
coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
@alfalab/client-event-bus | ||
|
||
Библиотека позволяет обмениваться данными в приложениях с модульной архитектурой (module federation), используя событийную модель браузера. | ||
Это особенно актуально для приложений на базе `Webpack Module Federation`, обеспечивая взаимодействие без создания жестких зависимостей. | ||
|
||
По сути представляет собой [`EventEmitter`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) с добавлением методов для | ||
получения последнего отправленного события уже после того, как это событие произошло. | ||
|
||
Для того чтобы добавить типизацию событий на проекте, не трогая основной пакет, можно сделать следующее: | ||
|
||
`constants/event-bus.ts` - заводим ключ, по которому создается шина данных | ||
```ts | ||
// constants/event-bus.ts | ||
export const BUS_KEY = 'my-first-bus'; | ||
``` | ||
|
||
`types/event-bus.ts` - определяем типы событий | ||
```ts | ||
// types/event-bus.ts | ||
type EventType = 'busValueFirst' | 'busValueSecond' | ||
type EventPayload = string | null | ||
|
||
export type EventTypes = Record<EventType, EventPayload>; | ||
``` | ||
|
||
`types/event-types.d.ts` - добавляем файл для типизации функций `getEventBus` и `createBus` | ||
```ts | ||
// types/event-types.d.ts | ||
import type { AbstractAppEventBus } from '@alfalab/client-event-bus'; | ||
|
||
import { BUS_KEY } from '~/constants/event-bus'; | ||
import type { EventTypes } from '~types/event-bus' | ||
|
||
export declare type EventBus = AbstractAppEventBus<EventTypes>; | ||
|
||
declare module '@alfalab/client-event-bus' { | ||
export declare function getEventBus(busKey: typeof BUS_KEY): EventBus; | ||
} | ||
|
||
declare module '@alfalab/client-event-bus' { | ||
export declare function createBus( | ||
key: typeof BUS_KEY, | ||
params?: EventBusParams, | ||
): EventBus; | ||
} | ||
``` | ||
|
||
## Рекомендации использования | ||
|
||
Для именования событий предлагается следующие договоренности: | ||
|
||
- Имя события начинается с названия вашего проекта, исключая префикс вашей системы/направления (`corp-`, `ufr-` и так далее) и суффикс `-ui`. То есть если ваш проект называется `ufr-cards-ui` событие должно начинаться с `cards_`, `corp-sales-ui` это соответственно `sales_`. | ||
- Название события пишется в `camelCase`. | ||
|
||
## Возвращаемое значение | ||
|
||
Возвращает `EventBus`, со следующими методами: | ||
|
||
- `addEventListener(eventName: string, eventHandler: (event: CustomEvent) => void, options?: AddEventListenerOptions)` - [стандартная функция](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) добавления подписки на событие | ||
- `removeEventListener(eventName: string, eventHandler: (event: CustomEvent) => void, options?: EventListenerOptions)` - [стандартная функция](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener) удаления подписки на событие | ||
- `getLastEventDetail(eventName: string)` - Функция, которая возвращает последнее событие заданного типа. Если событие еще не происходило - возвращает `undefined` | ||
- `addEventListenerAndGetLast(eventName: string, eventHandler: (event: CustomEvent) => void, options?: AddEventListenerOptions)` - объединяет в себе `addEventListener` и `getLastEvent`. Подписывает на событие и возвращает последнее событие этого типа | ||
|
||
## Использование в react | ||
Если вам нужно использовать значение из event-bus в react коде - вы можете использовать хук `useEventBusValue`: | ||
```tsx | ||
import { useEventBusValue } from '@alfalab/client-event-bus'; | ||
|
||
const MyComponent = () => { | ||
const currentOrganizationId = useEventBusValue('shared_currentOrganizationId'); | ||
|
||
return ( | ||
<div> | ||
ID текущей организации: {currentOrganizationId} | ||
</div> | ||
) | ||
} | ||
``` | ||
|
||
Хук всегда будет возвращать последнее значение из eventBus. При изменениях значения будет происходить ререндер. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ | ||
module.exports = { | ||
preset: 'ts-jest', | ||
testEnvironment: 'jsdom', | ||
testPathIgnorePatterns: [ | ||
'/node_modules/', | ||
'/build/', | ||
], | ||
collectCoverageFrom: [ | ||
'src/**/*.{ts,tsx}', | ||
'!src/**/*.d.ts', | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
{ | ||
"name": "@alfalab/client-event-bus", | ||
"version": "1.0.0", | ||
"main": "./build/index.js", | ||
"module": "./build/esm/index.js", | ||
"typings": "./build/index.d.ts", | ||
"license": "MPL-2.0", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/core-ds/arui-scripts.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/core-ds/arui-scripts/issues" | ||
}, | ||
"homepage": "https://github.com/core-ds/arui-scripts/tree/master/packages/client-event-bus#readme", | ||
"scripts": { | ||
"build:commonjs": "tsc --project tsconfig.json", | ||
"build:esm": "tsc --project tsconfig.esm.json", | ||
"build": "yarn build:commonjs && yarn build:esm", | ||
"test": "jest", | ||
"lint:scripts": "eslint \"**/*.{js,jsx,ts,tsx}\" --ext .js,.jsx,.ts,.tsx", | ||
"lint": "yarn lint:scripts", | ||
"lint:fix": "yarn lint:scripts --fix", | ||
"format": "prettier --write $INIT_CWD/{config,src}/**/*.{ts,tsx,js,jsx,css}" | ||
}, | ||
"peerDependencies": { | ||
"react": ">16.18.0" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^23.3.14", | ||
"eslint": "^8.20.0", | ||
"eslint-config-custom": "workspace:*", | ||
"jest": "28.1.3", | ||
"prettier": "^2.7.1", | ||
"react": "18.2.0", | ||
"ts-jest": "28.0.8", | ||
"typescript": "4.9.5" | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
packages/client-event-bus/src/__tests__/implementation.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { EventBus, createBus } from '../implementation'; | ||
Check warning on line 1 in packages/client-event-bus/src/__tests__/implementation.test.ts GitHub Actions / build (16.x)
|
||
|
||
type EventList = { | ||
testEvent: { message: string }; | ||
}; | ||
|
||
describe('EventBus', () => { | ||
let eventBus: EventBus<EventList>; | ||
|
||
beforeEach(() => { | ||
eventBus = new EventBus<EventList>({ debugMode: true }); | ||
}); | ||
|
||
it('should create EventBus with default parameters', () => { | ||
expect(eventBus).toBeDefined(); | ||
expect(eventBus).toHaveProperty('targetNode'); | ||
expect(eventBus).toHaveProperty('debugMode', true); | ||
}); | ||
|
||
it('should dispatch an event and capture it', () => { | ||
const handler = jest.fn(); | ||
|
||
eventBus.addEventListener<'testEvent', EventList['testEvent']>('testEvent', handler); | ||
eventBus.dispatchEvent('testEvent', { message: 'Test Message' }); | ||
|
||
expect(handler).toHaveBeenCalled(); | ||
expect(handler).toHaveBeenCalledWith( | ||
expect.objectContaining({ detail: { message: 'Test Message' } }), | ||
); | ||
expect(eventBus.getLastEventDetail('testEvent')).toEqual({ message: 'Test Message' }); | ||
}); | ||
|
||
it('should return last event detail', () => { | ||
eventBus.dispatchEvent('testEvent', { message: 'Test Message' }); | ||
|
||
const detail = eventBus.getLastEventDetail('testEvent'); | ||
|
||
expect(detail).toEqual({ message: 'Test Message' }); | ||
}); | ||
|
||
it('should add and remove event listeners', () => { | ||
const handler = jest.fn(); | ||
|
||
eventBus.addEventListener('testEvent', handler); | ||
eventBus.dispatchEvent('testEvent', { message: 'Hello!' }); | ||
|
||
expect(handler).toHaveBeenCalled(); | ||
|
||
eventBus.removeEventListener('testEvent', handler); | ||
eventBus.dispatchEvent('testEvent', { message: 'Goodbye!' }); | ||
|
||
expect(handler).not.toHaveBeenCalledTimes(2); | ||
}); | ||
}); | ||
|
||
describe('createBus', () => { | ||
it('should create and return the same EventBus instance for the same key', () => { | ||
const eventBus1 = createBus('eventBus'); | ||
const eventBus2 = createBus('eventBus'); | ||
|
||
expect(eventBus1).toBe(eventBus2); | ||
}); | ||
|
||
it('should create different EventBus instances for different keys', () => { | ||
const eventBus1 = createBus('eventBus1'); | ||
const eventBus2 = createBus('eventBus2'); | ||
|
||
expect(eventBus1).not.toBe(eventBus2); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// CustomEvent не поддерживается нормально в ie, поэтому полифилим его прям тут | ||
function isNativeCustomEventAvailable() { | ||
try { | ||
const p = new global.CustomEvent('cat', { detail: { foo: 'bar' } }); | ||
|
||
return p.type === 'cat' && p.detail.foo === 'bar'; | ||
} catch (e) { | ||
// just ignore it | ||
} | ||
|
||
return false; | ||
} | ||
|
||
function CustomEventPolyfill<T>(type: string, params: CustomEventInit<T>) { | ||
const e = document.createEvent('CustomEvent'); | ||
|
||
if (params) { | ||
e.initCustomEvent(type, Boolean(params.bubbles), Boolean(params.cancelable), params.detail); | ||
} else { | ||
e.initCustomEvent(type, false, false, undefined); | ||
} | ||
|
||
return e; | ||
} | ||
|
||
export const CustomEvent = ( | ||
isNativeCustomEventAvailable() ? global.CustomEvent : CustomEventPolyfill | ||
) as unknown as typeof global.CustomEvent; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/** | ||
* Возвращает event-bus для конкретной системы. Если event-bus в текущем контексте не доступен - вернет undefined | ||
* @param busKey ключ конкретной системы | ||
*/ | ||
/* eslint-disable no-underscore-dangle */ | ||
export function getEventBus(busKey: string) { | ||
if (window.__alfa_event_buses) { | ||
return window.__alfa_event_buses[busKey]; | ||
} | ||
|
||
return null; | ||
} | ||
/* eslint-enable no-underscore-dangle */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { AbstractAppEventBus, AbstractKnownEventTypes } from './types/abstract-types'; | ||
import { CustomEvent } from './custom-event'; | ||
|
||
export class EventBus<KnownEventTypes extends AbstractKnownEventTypes> | ||
implements AbstractAppEventBus<KnownEventTypes> | ||
{ | ||
constructor({ targetNode = document, debugMode = false }: EventBusParams = {}) { | ||
this.debugMode = debugMode; | ||
this.targetNode = targetNode; | ||
} | ||
|
||
private targetNode: Node; | ||
|
||
private debugMode: boolean; | ||
|
||
private lastEventValues = {} as Record<keyof KnownEventTypes, unknown>; | ||
|
||
dispatchEvent< | ||
EventName extends keyof KnownEventTypes, | ||
PayloadType extends KnownEventTypes[EventName], | ||
>(eventName: EventName, detail?: PayloadType): void { | ||
this.targetNode.dispatchEvent(new CustomEvent(eventName as string, { detail })); | ||
this.lastEventValues[eventName] = detail; | ||
|
||
if (this.debugMode) { | ||
// eslint-disable-next-line no-console | ||
console.debug(`Event bus, dispatchEvent: ${eventName.toString()}`, detail); | ||
} | ||
} | ||
|
||
getLastEventDetail< | ||
EventName extends keyof KnownEventTypes, | ||
PayloadType extends KnownEventTypes[EventName], | ||
>(eventName: EventName): PayloadType | undefined { | ||
return this.lastEventValues[eventName] as PayloadType | undefined; | ||
} | ||
|
||
addEventListener< | ||
EventName extends keyof KnownEventTypes, | ||
PayloadType extends KnownEventTypes[EventName], | ||
>( | ||
eventName: EventName, | ||
eventHandler: (event: CustomEvent<PayloadType>) => void, | ||
options?: boolean | AddEventListenerOptions, | ||
): void { | ||
this.targetNode.addEventListener( | ||
eventName as string, | ||
eventHandler as EventListener, | ||
options, | ||
); | ||
} | ||
|
||
addEventListenerAndGetLast< | ||
EventName extends keyof KnownEventTypes, | ||
PayloadType extends KnownEventTypes[EventName], | ||
>( | ||
eventName: EventName, | ||
eventHandler: (event: CustomEvent<PayloadType>) => void, | ||
options?: boolean | AddEventListenerOptions, | ||
): PayloadType | undefined { | ||
this.addEventListener(eventName, eventHandler, options); | ||
|
||
return this.getLastEventDetail(eventName); | ||
} | ||
|
||
removeEventListener< | ||
EventName extends keyof KnownEventTypes, | ||
PayloadType extends KnownEventTypes[EventName], | ||
>( | ||
eventName: EventName, | ||
eventHandler: (event: CustomEvent<PayloadType>) => void, | ||
options?: EventListenerOptions | boolean, | ||
): void { | ||
this.targetNode.removeEventListener( | ||
eventName as string, | ||
eventHandler as unknown as EventListener, | ||
options, | ||
); | ||
} | ||
} | ||
|
||
/* eslint-disable no-underscore-dangle */ | ||
export function createBus( | ||
key: string, | ||
params: EventBusParams = {}, | ||
): EventBus<AbstractKnownEventTypes> { | ||
if (!window.__alfa_event_buses) { | ||
window.__alfa_event_buses = {}; | ||
} | ||
if (!window.__alfa_event_buses[key]) { | ||
window.__alfa_event_buses[key] = new EventBus(params); | ||
} | ||
|
||
return window.__alfa_event_buses[key] as EventBus<AbstractKnownEventTypes>; | ||
} | ||
|
||
/* eslint-enable no-underscore-dangle */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import './types/types'; | ||
|
||
export { getEventBus } from './get-event-bus'; | ||
export { createBus, EventBus } from './implementation'; | ||
export { useEventBusValue } from './use-event-bus-value'; | ||
|
||
export type { AbstractAppEventBus, AbstractKnownEventTypes } from './types/abstract-types'; |
Oops, something went wrong.