Skip to content

Commit a30cdc6

Browse files
Lots of changes...
- Implement event bus on the StoreManager level & store - Replace "hooks" with the event bus - Added @on decorator for stores, so we can define listeners for stores nicely - Testability - Implemented a bunch of stuff to make stores properly testable - When testing, test modules will be transformed before-hand(kind of a hacky solution for now) so we can use meta - More work on store scopes - During tests, scoping is ended correctly, needs more work/thinking about how to handle it during regular usage
1 parent c84e877 commit a30cdc6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1664
-290
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
/dist/
55
/docs/node_modules/
66
/docs/docs/.vitepress/dist/
7+
/.vitest-result.json

build.js

+27-11
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ import dts from 'vite-plugin-dts';
88

99
const __dirname = fileURLToPath(new URL('.', import.meta.url));
1010

11+
/**
12+
* @type {import('vite').UserConfig}
13+
*/
1114
const commonConfig = {
1215
plugins : [
1316
/*dts({
1417
tsConfigFilePath : "./tsconfig.json",
1518
insertTypesEntry : true,
1619
}),*/
17-
// vue(),
20+
vue(),
1821
],
1922
resolve : {
2023
alias : {
@@ -32,15 +35,25 @@ const commonConfig = {
3235
"typescript",
3336
"fs-jetpack",
3437
'path',
38+
'@vue/devtools-api',
39+
'@vue/compat',
40+
'@vue/compiler-dom',
41+
'@vue/compiler-sfc',
42+
'@vue/runtime-core',
3543
],
3644
output : {
37-
globals : {
38-
vue : 'Vue',
39-
klona : 'klona',
40-
'typescript' : "ts",
41-
'fs-jetpack' : "jetpack",
42-
'path' : 'path',
43-
},
45+
/*globals : {
46+
'@vue/devtools-api' : 'vue_devtools_api',
47+
'@vue/compat' : 'vue_compat',
48+
'@vue/compiler-dom' : 'vue_compiler_dom',
49+
'@vue/compiler-sfc' : 'vue_compiler_sfc',
50+
'@vue/runtime-core' : 'vue_runtime_core',
51+
'vue' : 'Vue',
52+
'klona' : 'klona',
53+
'typescript' : "ts",
54+
'fs-jetpack' : "jetpack",
55+
'path' : 'path',
56+
},*/
4457
},
4558
},
4659
},
@@ -88,6 +101,9 @@ const libConfigs = {
88101
function forLib(key) {
89102
const config = {...commonConfig};
90103

104+
if (config.plugins?.length) {
105+
config.plugins = config.plugins.concat(libConfigs[key].plugins ?? []);
106+
}
91107
config.root = __dirname;
92108
config.build.lib = libConfigs[key].lib;
93109
config.build.outDir = libConfigs[key].outDir;
@@ -104,9 +120,9 @@ for (let lib in libConfigs) {
104120
console.log('Done with', lib);
105121
}
106122

107-
execSync(`tsc --declaration --emitDeclarationOnly --project ./tsconfig.lib.json`);
108-
execSync(`tsc --declaration --project ./tsconfig.vite-plugin.json`);
109-
execSync(`cp src/VitePlugin/package.json dist/VitePlugin/package.json`);
123+
execSync(`yarn run tsc:types:lib`, {stdio : 'inherit'});
124+
execSync(`yarn run tsc:types:vite-pkg`, {stdio : 'inherit'});
125+
execSync(`cp src/VitePlugin/package.json dist/VitePlugin/package.json`, {stdio : 'inherit'});
110126

111127
fs.writeFileSync('./dist/index.d.ts', `\n
112128
export * from './Lib';

package.json

+8-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
"build": "tsc && vite build",
3131
"dist": "node build.js",
3232
"preview": "vite preview",
33-
"test": "vitest run --no-threads"
33+
"test": "vitest run --no-threads",
34+
"webstorm-integration": "vitest --watch --reporter=dot --reporter=json --outputFile=.vitest-result.json",
35+
"tsc:types:lib": "tsc --declaration --emitDeclarationOnly --project ./tsconfig.lib.json",
36+
"tsc:types:vite-pkg": "tsc --declaration --project ./tsconfig.vite-plugin.json"
3437
},
3538
"license": "MIT",
3639
"sideEffects": false,
@@ -84,17 +87,19 @@
8487
],
8588
"dependencies": {
8689
"@idevelopthings/reflect-extensions": "^0.0.3",
90+
"@rollup/plugin-typescript": "^9.0.1",
91+
"@rollup/pluginutils": "^5.0.1",
8792
"@vue/devtools-api": "^6.4.4",
8893
"fs-jetpack": "^5.0.0",
8994
"klona": "^2.0.5",
9095
"lodash.get": "^4.4.2",
9196
"lodash.set": "^4.3.2",
9297
"lodash.throttle": "^4.1.1",
9398
"lodash.uniq": "^4.5.0",
94-
"vue": "^3.2.40",
95-
"@rollup/pluginutils": "^5.0.1"
99+
"vue": "^3.2.40"
96100
},
97101
"devDependencies": {
102+
"@types/estree": "^1.0.0",
98103
"@types/lodash.get": "^4.4.7",
99104
"@types/lodash.set": "^4.3.7",
100105
"@types/lodash.throttle": "^4.1.7",

src/Common/LifeCycle.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//export const LifeCycleEvents = ['BeforeAll', 'OnInit', 'OnDispose', 'AfterAll'];
2-
31
export enum LifeCycleEvent {
42
BeforeAll = 'BeforeAll',
53
OnInit = 'OnInit',
@@ -10,3 +8,17 @@ export enum LifeCycleEvent {
108
export function isLifeCycleEvent(name: string): boolean {
119
return Object.values(LifeCycleEvent).includes(name as LifeCycleEvent);
1210
}
11+
12+
export function lifeCycleEventName<T extends LifeCycleEvent>(name: T): `@${T}` {
13+
return `@${name}`;
14+
}
15+
16+
export type LifeCycleEventsMap =
17+
| { [K in LifeCycleEvent as `@${K}`]: { store: any } };
18+
19+
export type StoreLifeCycleEventsMap =
20+
| { [K in LifeCycleEvent as `${string}.@${K}`]: { store: any } };
21+
22+
export interface LifeCycleEvents extends LifeCycleEventsMap, StoreLifeCycleEventsMap {
23+
}
24+

src/DevTools/DevTools.ts

+2
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ export class DevtoolsInstance {
320320
}
321321

322322
public actionSetup(action: StoreAction<any, any>): void {
323+
if (!this.api) return;
324+
323325
this.currentAction = action;
324326
const id = this.currentGroupId++;
325327

src/Lib/Decorators.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* This decorator is used to mark a Store as a store used in a test file
3+
*/
4+
export const TestStore = (target: { new(...args): any }) => {};
5+
6+
/**
7+
* Marks a getter as computed
8+
*/
9+
export const Computed = (target: any, propertyKey: string) => {};
10+
11+
/**
12+
* Marks an action as an event handler for this event
13+
*/
14+
export const BeforeAll = (target: any, propertyKey: string) => {};
15+
16+
/**
17+
* Marks an action as an event handler for this event
18+
*/
19+
export const OnInit = (target: any, propertyKey: string) => {};
20+
21+
/**
22+
* Marks an action as an event handler for this event
23+
*/
24+
export const OnDispose = (target: any, propertyKey: string) => {};
25+
26+
/**
27+
* Marks an action as an event handler for this event
28+
*/
29+
export const AfterAll = (target: any, propertyKey: string) => {};
30+
31+
/**
32+
* Marks an action as an event handler for the specified event
33+
*/
34+
export const On = (eventName: string) => {
35+
return (target: any, propertyKey: string) => {
36+
37+
}
38+
};

src/Lib/EventBus/EventBus.ts

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// An event handler can take an optional event argument
2+
// and should not return a value
3+
export type Handler<T = unknown> = (event: T) => void;
4+
5+
// An array of all currently registered event handlers for a type
6+
export type EventHandlerList<T = unknown> = Array<Handler<T>>;
7+
//export type WildCardEventHandlerList<T = { [key: string]: any }> = Array<WildcardHandler<T>>;
8+
9+
// A map of event types and their corresponding event handlers.
10+
export type EventHandlerMap<Events extends { [key: string]: any }> = Map<keyof Events, EventHandlerList<Events[keyof Events]>>;
11+
12+
export interface Emitter<Events extends { [key: string]: any }> {
13+
all: EventHandlerMap<Events>;
14+
15+
on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void;
16+
17+
off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>): void;
18+
19+
emit<Key extends keyof Events>(type: Key, event?: Events[Key]): void;
20+
21+
}
22+
23+
/**
24+
* Credits to mit
25+
*/
26+
export default function eventBus<Events extends { [key: string]: any }>(
27+
all?: EventHandlerMap<Events>
28+
): Emitter<Events> {
29+
30+
all = all || new Map();
31+
32+
return {
33+
34+
/**
35+
* A Map of event names to registered handler functions.
36+
*/
37+
all,
38+
39+
/**
40+
* Register an event handler for the given type.
41+
* @param {string|symbol} type Type of event to listen for
42+
* @param {Function} handler Function to call in response to given event
43+
*/
44+
on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>) {
45+
const handlers: Array<Handler<Events[Key]>> | undefined = all!.get(type);
46+
if (handlers) {
47+
handlers.push(handler);
48+
} else {
49+
all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
50+
}
51+
},
52+
53+
/**
54+
* Remove an event handler for the given type.
55+
* If `handler` is omitted, all handlers of the given type are removed.
56+
* @param {string|symbol} type Type of event to unregister `handler` from
57+
* @param {Function} [handler] Handler function to remove
58+
*/
59+
off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>) {
60+
const handlers: Array<Handler<Events[Key]>> | undefined = all!.get(type);
61+
if (handlers) {
62+
if (handler) {
63+
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
64+
} else {
65+
all!.set(type, []);
66+
}
67+
68+
if (handlers.length === 0) {
69+
all!.delete(type);
70+
}
71+
}
72+
},
73+
74+
/**
75+
* Invoke all handlers for the given type.
76+
*
77+
* @param {string|symbol} type The event type to invoke
78+
* @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
79+
*/
80+
emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) {
81+
let handlers = all!.get(type) as EventHandlerList<Events[keyof Events]> | undefined;
82+
if (!handlers) {
83+
return;
84+
}
85+
86+
handlers.slice().map((handler) => {
87+
handler(evt!);
88+
});
89+
}
90+
};
91+
}

src/Lib/EventBus/StoreEventBus.ts

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//const GlobalEventBus = new StoreEventBus();
2+
//
3+
//export const EventBusPlugin: Plugin = {
4+
// install(app: App, ...options: any[]) {
5+
// app.config.globalProperties.$events = GlobalEventBus;
6+
// app.config.globalProperties.$emitEvent = GlobalEventBus.emit.bind(GlobalEventBus);
7+
// }
8+
//};
9+
//export function useEvent<Key extends keyof StoreEventsMap>(type: Key, handler: Handler<StoreEventsMap[Key]>): void;
10+
//export function useEvent(type: "*", handler: WildcardHandler<StoreEventsMap>): void;
11+
//export function useEvent(type, handler): void {
12+
// const scope = getCurrentScope();
13+
// if (!scope) {
14+
// throw new Error('No scope found. Are you using the plugin outside of a Vue component/setup?');
15+
// }
16+
//
17+
// GlobalEventBus.on(type, handler);
18+
//
19+
// onScopeDispose(() => {
20+
// GlobalEventBus.off(type, handler);
21+
// console.log('disposed event listener for', type);
22+
// });
23+
//}
24+
//
25+
//export function emitEvent<Key extends keyof StoreEventsMap>(type: Key, data: StoreEventsMap[Key]): void;
26+
//export function emitEvent(type, data): void {
27+
// GlobalEventBus.emit(type, data);
28+
//}
29+
//export {useEvent, emitEvent};
30+
31+
import {getCurrentInstance, onBeforeUnmount} from "vue";
32+
import eventBus, {Emitter} from "./EventBus";
33+
import {StoreEventsMap} from "./StoreEventsMap";
34+
35+
export type EventKey = keyof StoreEventsMap | string;
36+
37+
export class StoreEventBus {
38+
39+
protected bus: Emitter<StoreEventsMap> = eventBus<StoreEventsMap>();
40+
41+
/**
42+
* Dispatch an event to the main event bus
43+
*/
44+
public $dispatch(type: EventKey, evt?: StoreEventsMap[EventKey]) {
45+
this.bus.emit(type, evt);
46+
}
47+
48+
public getAllEvents() {
49+
const events = [];
50+
51+
this.bus.all.forEach((handlers, type) => {
52+
events.push({type, handlers});
53+
});
54+
55+
return events;
56+
}
57+
58+
public hasEventHandler(type: EventKey) {
59+
return this.bus.all.has(type);
60+
}
61+
62+
public removeAllListeners() {
63+
this.bus.all.clear();
64+
}
65+
66+
/**
67+
* By default, when an event is used in a component, and the component is unmounted, the event listener will be removed.
68+
* You can set detached to true to disable this.
69+
*/
70+
public $on(
71+
type: EventKey,
72+
handler: (event: StoreEventsMap[EventKey]) => void,
73+
detached: boolean = false
74+
): () => void {
75+
76+
const offFn = () => this.bus.off(type, handler);
77+
78+
if (!detached && getCurrentInstance()) {
79+
onBeforeUnmount(() => offFn());
80+
}
81+
82+
this.bus.on(type, handler);
83+
84+
return offFn;
85+
}
86+
87+
public $off(type: EventKey, handler: (event: StoreEventsMap[EventKey]) => void) {
88+
this.bus.off(type, handler);
89+
}
90+
91+
}

src/Lib/EventBus/StoreEventsMap.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {LifeCycleEvents} from "../../Common/LifeCycle";
2+
3+
export interface StoreEventsMap extends LifeCycleEvents {
4+
[key: string]: any;
5+
}

0 commit comments

Comments
 (0)