vite-uni-dev-tool 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +16 -0
  2. package/dev/components/Code/index.vue +227 -0
  3. package/dev/components/Connection/index.vue +11 -21
  4. package/dev/components/ConsoleList/Code.vue +1 -1
  5. package/dev/components/ConsoleList/ConsoleItem.vue +38 -12
  6. package/dev/components/ConsoleList/RunJSInput.vue +59 -0
  7. package/dev/components/ConsoleList/index.vue +24 -8
  8. package/dev/components/DevTool/index.vue +10 -9
  9. package/dev/components/DevToolButton/index.vue +10 -10
  10. package/dev/components/DevToolTitle/index.vue +21 -0
  11. package/dev/components/DevToolWindow/index.vue +62 -5
  12. package/dev/components/FilterInput/index.vue +1 -1
  13. package/dev/components/JsonPretty/components/CheckController/index.vue +22 -5
  14. package/dev/components/JsonPretty/components/TreeNode/index.vue +16 -15
  15. package/dev/components/JsonPretty/index.vue +77 -75
  16. package/dev/components/JsonPretty/type.ts +2 -0
  17. package/dev/components/NetworkList/NetworkDetail.vue +1 -1
  18. package/dev/components/RunJS/index.vue +128 -0
  19. package/dev/components/SettingList/index.vue +10 -20
  20. package/dev/components/UniEvent/UniEventItem.vue +124 -0
  21. package/dev/components/UniEvent/index.vue +94 -0
  22. package/dev/components/UploadList/UploadDetail.vue +1 -1
  23. package/dev/components/VirtualList/index.vue +112 -0
  24. package/dev/components/WebSocket/WebSocketList.vue +1 -1
  25. package/dev/const.ts +101 -28
  26. package/dev/core.ts +12 -3
  27. package/dev/devConsole/index.ts +21 -4
  28. package/dev/devEvent/index.ts +122 -8
  29. package/dev/devEventBus/index.ts +94 -0
  30. package/dev/devIntercept/index.ts +61 -18
  31. package/dev/devRunJS/index.ts +170 -0
  32. package/dev/devStore/index.ts +83 -0
  33. package/dev/index.d.ts +3 -2
  34. package/dev/index.js +1 -1
  35. package/dev/plugins/uniDevTool/uniDevTool.d.ts +2 -1
  36. package/dev/plugins/uniDevTool/uniDevTool.d.ts.map +1 -1
  37. package/dev/plugins/uniDevTool/uniDevTool.js +36 -38
  38. package/dev/plugins/uniGlobalComponents/uniGlobalComponents.d.ts +2 -1
  39. package/dev/plugins/uniGlobalComponents/uniGlobalComponents.d.ts.map +1 -1
  40. package/dev/plugins/uniGlobalComponents/uniGlobalComponents.js +7 -9
  41. package/dev/plugins/utils/index.d.ts +10 -2
  42. package/dev/plugins/utils/index.d.ts.map +1 -1
  43. package/dev/plugins/utils/index.js +1 -1
  44. package/dev/type.ts +58 -1
  45. package/dev/utils/index.ts +10 -1
  46. package/dev/utils/language.ts +53 -0
  47. package/dev/utils/object.ts +64 -1
  48. package/dev/utils/string.ts +5 -5
  49. package/package.json +2 -2
package/dev/const.ts CHANGED
@@ -10,86 +10,159 @@ export const EVENT_DEV_BUTTON = 'event-dev-button';
10
10
  */
11
11
  export const EVENT_DEV_WINDOW = 'event-dev-window';
12
12
 
13
- /** app 发出的信息 */
13
+ /**
14
+ * app 发出的信息
15
+ */
14
16
  export const DEV_APP_MESSAGE = 'dev-app-message';
15
17
 
16
- /** 调试弹窗发出的消息 */
18
+ /**
19
+ * 调试弹窗发出的消息
20
+ */
17
21
  export const DEV_WINDOW_MESSAGE = 'dev-window-message';
18
22
 
19
- /** 缓存调试按钮信息 */
23
+ /**
24
+ * 缓存调试按钮信息
25
+ */
20
26
  export const DEV_BUTTON_INFO = 'dev-button-info';
21
27
 
22
- /** 缓存调试弹窗信息 */
28
+ /**
29
+ * 缓存调试弹窗信息
30
+ */
23
31
  export const DEV_WINDOW_INFO = ' dev-window-info';
24
32
 
25
33
  export const DEV_IS_DESTROY = 'dev-is-destroy';
26
34
 
27
- /** 清空console */
35
+ /**
36
+ * 清空console
37
+ */
28
38
  export const DEV_CONSOLE_CLEAR = 'dev-console-clear';
29
39
 
30
- /** 清空network */
40
+ /**
41
+ * 清空network
42
+ */
31
43
  export const DEV_NETWORK_CLEAR = 'dev-network-clear';
32
44
 
33
- /** 清空websocket */
45
+ /**
46
+ * 清空websocket
47
+ */
34
48
  export const DEV_WEBSOCKET_CLEAR = 'dev-websocket-clear';
35
49
 
36
- /** 清空upload */
50
+ /**
51
+ * 清空upload
52
+ */
37
53
  export const DEV_UPLOAD_CLEAR = 'dev-upload-clear';
38
54
 
39
- /** 清空storage */
55
+ /**
56
+ * 清空storage
57
+ */
40
58
  export const DEV_STORAGE_CLEAR = 'dev-storage-clear';
41
59
 
42
- /** 刷新storage */
60
+ /**
61
+ * 刷新storage
62
+ */
43
63
  export const DEV_STORAGE_REFRESH = 'dev-storage-refresh';
44
64
 
45
- /** 移除storage */
65
+ /**
66
+ * 移除storage
67
+ */
46
68
  export const DEV_STORAGE_REMOVE = 'dev-storage-remove';
47
69
 
48
- /** 新增storage */
70
+ /**
71
+ * 新增storage
72
+ */
49
73
  export const DEV_STORAGE_ADD = 'dev-storage-add';
50
74
 
51
- /** 更新storage */
75
+ /**
76
+ * 更新storage
77
+ */
52
78
  export const DEV_STORAGE_UPDATE = 'dev-storage-update';
53
79
 
54
- /** 页面跳转 */
80
+ /**
81
+ * 页面跳转
82
+ */
55
83
  export const DEV_PAGE_JUMP = 'dev-page-jump';
56
84
 
57
- /** 隐藏调试按钮 */
85
+ /**
86
+ * 隐藏调试按钮
87
+ */
58
88
  export const DEV_BUTTON_SHOW_OR_HIDE = 'dev-button-show-or-hide';
59
89
 
60
- /** 重启调试器 */
90
+ /**
91
+ * 重启调试器
92
+ */
61
93
  export const DEV_RESTART_DEBUGGER = 'dev-restart-debugger';
62
94
 
63
- /** 重启app */
95
+ /**
96
+ * 重启app
97
+ */
64
98
  export const DEV_RESTART_APP = 'dev-restart-app';
65
99
 
66
- /** 导出日志 */
100
+ /**
101
+ * 导出日志
102
+ */
67
103
  export const DEV_EXPORT_LOG = 'dev-export-log';
68
104
 
69
- /** 改变vuex数据 */
105
+ /**
106
+ * 改变vuex数据
107
+ */
70
108
  export const DEV_VUEX_CHANGE = 'dev-vuex-change';
71
109
 
72
- /** 改变pinia */
110
+ /**
111
+ * 改变pinia
112
+ */
73
113
  export const DEV_PINIA_CHANGE = 'dev-pinia-change';
74
114
 
75
- /** 清除日志缓存 */
115
+ /**
116
+ * 清除日志缓存
117
+ */
76
118
  export const DEV_LOG_CACHE_CLEAR = 'dev-log-cache-clear';
77
119
 
78
- /** 销毁调试工具 */
120
+ /**
121
+ * 销毁调试工具
122
+ */
79
123
  export const DEV_DESTROY = 'dev-destroy';
80
124
 
81
- /** 显示调试弹窗 */
125
+ /**
126
+ * 显示调试弹窗
127
+ */
82
128
  export const DEV_WINDOW_OPEN = 'dev-window-open';
83
129
 
84
- /** 显示调试弹窗 */
130
+ /**
131
+ * 显示调试弹窗
132
+ */
85
133
  export const DEV_WINDOW_CLOSE = 'dev-window-close';
86
134
 
87
- /** 刷新路由列表 */
135
+ /**
136
+ * 刷新路由列表
137
+ */
88
138
  export const DEV_ROUTE_REFRESH = 'dev-route-refresh';
89
139
 
90
- /** 获取 dev option */
140
+ /**
141
+ * 获取 dev option
142
+ */
91
143
  export const DEV_OPTION = 'dev-option';
92
144
 
93
- export const DEV_OPTION_GET = 'dev-option-get';
145
+ /**
146
+ * 获取options
147
+ */
148
+ export const DEV_OPTION_GET = 'dev-option-get';
149
+
150
+ /**
151
+ * 发送 options
152
+ */
153
+ export const DEV_OPTION_SEND = 'dev-option-send';
154
+
155
+ /**
156
+ * 清空事件
157
+ */
158
+ export const DEV_UNI_EVENT_CLEAR = 'dev-uni-event-clear';
159
+
160
+ /**
161
+ * 运行js
162
+ */
163
+ export const DEV_RUN_JS = 'dev-uni-run-js';
94
164
 
95
- export const DEV_OPTION_SEND = 'dev-option-send';
165
+ /**
166
+ * 清空运行的js
167
+ */
168
+ export const DEV_RUN_JS_CLEAR = 'dev-uni-run-js-clear';
package/dev/core.ts CHANGED
@@ -1,4 +1,3 @@
1
- // import DevToolComponent from './components/DevTool/index.vue';
2
1
  import { DevStore } from './devStore';
3
2
  import { DevEvent } from './devEvent';
4
3
  import { DevIntercept } from './devIntercept';
@@ -6,6 +5,7 @@ import { DevConsole } from './devConsole';
6
5
 
7
6
  import type { DevTool } from './type';
8
7
 
8
+ import { EventBus } from './devEventBus';
9
9
  /**
10
10
  * 备份原生方法
11
11
  *
@@ -26,12 +26,21 @@ const backup = {
26
26
  reject: Promise.reject,
27
27
  connectSocket: uni?.connectSocket,
28
28
  uploadFile: uni?.uploadFile,
29
+ $on: uni.$on,
30
+ $once: uni.$once,
31
+ $emit: uni.$emit,
32
+ $off: uni.$off,
29
33
  __log__: (uni as any).__log__,
30
34
  };
31
35
 
36
+ const eventBus = new EventBus();
37
+
32
38
  const store = new DevStore();
33
39
 
34
- const event = new DevEvent(store);
40
+ const event = new DevEvent({
41
+ store,
42
+ eventBus,
43
+ });
35
44
 
36
45
  const console = new DevConsole(event);
37
46
 
@@ -93,7 +102,6 @@ function interceptPiniaStore(context: any) {
93
102
  uni.__dev__console = console;
94
103
 
95
104
  export {
96
- // DevToolComponent,
97
105
  backup,
98
106
  console,
99
107
  initDevTool,
@@ -106,4 +114,5 @@ export {
106
114
  getDevToolOptions,
107
115
  interceptVuexStorage,
108
116
  interceptPiniaStore,
117
+ eventBus,
109
118
  };
@@ -3,8 +3,9 @@ import type { DevTool } from '../type';
3
3
  import {
4
4
  getCurrentDate,
5
5
  getCurrentPagePath,
6
+ transformValueToView,
6
7
  isArray,
7
- serializeCircular,
8
+ parseValue,
8
9
  } from '../utils/index';
9
10
 
10
11
  /**
@@ -58,7 +59,13 @@ export class DevConsole {
58
59
  const stackList = new Error()?.stack?.split('\n');
59
60
  const stack = stackList?.slice(3)?.[0];
60
61
 
61
- let argList = args?.map((item) => serializeCircular(item));
62
+ let argList = args?.map((item) => {
63
+ return {
64
+ type: transformValueToView(item),
65
+ // 处理 item 循环引用问题
66
+ value: parseValue(item),
67
+ };
68
+ });
62
69
 
63
70
  if (isArray(args) && args.length > 0) {
64
71
  this.event.updateConsoleList([
@@ -153,7 +160,12 @@ export class DevConsole {
153
160
  type: 'log',
154
161
  position,
155
162
  time,
156
- args: [`${label ?? '[DevTool timeEnd error]'}: ${duration}ms`],
163
+ args: [
164
+ {
165
+ type: 'string',
166
+ value: `${label ?? '[DevTool timeEnd error]'}: ${duration}ms`,
167
+ },
168
+ ],
157
169
  stack,
158
170
  },
159
171
  ]);
@@ -196,7 +208,12 @@ export class DevConsole {
196
208
  type: 'log',
197
209
  position: getCurrentPagePath(),
198
210
  time: getCurrentDate(),
199
- args: [`${label}: ${this.countMap.get(label)}`],
211
+ args: [
212
+ {
213
+ type: 'string',
214
+ value: `${label}: ${this.countMap.get(label)}`,
215
+ },
216
+ ],
200
217
  stack,
201
218
  },
202
219
  ]);
@@ -28,6 +28,8 @@ import {
28
28
  DEV_WINDOW_CLOSE,
29
29
  DEV_OPTION_GET,
30
30
  DEV_OPTION_SEND,
31
+ DEV_UNI_EVENT_CLEAR,
32
+ DEV_RUN_JS,
31
33
  } from '../const';
32
34
  import { DevStore } from '../devStore';
33
35
  import type { DevTool } from '../type';
@@ -38,7 +40,12 @@ import {
38
40
  saveTxtFileApp,
39
41
  saveTextFileH5,
40
42
  saveTextFileMicro,
43
+ getCurrentDate,
44
+ isNil,
45
+ transformValueToView,
46
+ parseValue,
41
47
  } from '../utils/index';
48
+ import type { EventBus } from '../devEventBus';
42
49
 
43
50
  /**
44
51
  * 事件中心
@@ -49,8 +56,11 @@ import {
49
56
  export class DevEvent {
50
57
  private store: DevStore;
51
58
 
52
- constructor(store: DevStore) {
59
+ private eventBus: EventBus;
60
+
61
+ constructor({ store, eventBus }: { store: DevStore; eventBus: EventBus }) {
53
62
  this.store = store;
63
+ this.eventBus = eventBus;
54
64
  this.acceptMessage();
55
65
  }
56
66
 
@@ -67,7 +77,7 @@ export class DevEvent {
67
77
  data.size = size;
68
78
  data.sizeFormat = sizeFormat;
69
79
 
70
- uni.$emit(DEV_APP_MESSAGE, data);
80
+ this.eventBus.emit(DEV_APP_MESSAGE, data);
71
81
 
72
82
  if (size > this.store.cacheMaxSize) {
73
83
  this.store?.clearDevCache();
@@ -136,8 +146,12 @@ export class DevEvent {
136
146
 
137
147
  this.resetInterceptSwitchTab();
138
148
 
149
+ this.resetUniEvent();
150
+
139
151
  this.store.setDevToolDestroy(true);
140
152
 
153
+ this.eventBus.clear();
154
+
141
155
  console.warn('[DevTool] 调试器已销毁');
142
156
  }
143
157
 
@@ -163,7 +177,7 @@ export class DevEvent {
163
177
  setTimeout(async () => {
164
178
  this.postMessage();
165
179
  }, 100);
166
- uni.$emit(EVENT_DEV_WINDOW, true);
180
+ this.eventBus.emit(EVENT_DEV_WINDOW, true);
167
181
  }
168
182
 
169
183
  /**
@@ -172,7 +186,7 @@ export class DevEvent {
172
186
  * @memberof DevEvent
173
187
  */
174
188
  closeDevToolWindow() {
175
- uni.$emit(EVENT_DEV_WINDOW, false);
189
+ this.eventBus.emit(EVENT_DEV_WINDOW, false);
176
190
  }
177
191
 
178
192
  /**
@@ -183,7 +197,7 @@ export class DevEvent {
183
197
  showDevToolButton() {
184
198
  const isDestroy = uni.getStorageSync(DEV_IS_DESTROY) ?? false;
185
199
  if (isDestroy) return;
186
- uni.$emit(EVENT_DEV_BUTTON, true);
200
+ this.eventBus.emit(EVENT_DEV_BUTTON, true);
187
201
  uni.setStorageSync(EVENT_DEV_BUTTON, true);
188
202
  this.store.setDevToolVisible(true);
189
203
  }
@@ -196,7 +210,7 @@ export class DevEvent {
196
210
  hideDevToolButton() {
197
211
  const isDestroy = uni.getStorageSync(DEV_IS_DESTROY) ?? false;
198
212
  if (isDestroy) return;
199
- uni.$emit(EVENT_DEV_BUTTON, false);
213
+ this.eventBus.emit(EVENT_DEV_BUTTON, false);
200
214
  uni.setStorageSync(EVENT_DEV_BUTTON, false);
201
215
  this.store.setDevToolVisible(false);
202
216
  }
@@ -281,7 +295,7 @@ export class DevEvent {
281
295
  sendDevToolOption() {
282
296
  const options = this.store.getDevToolOptions();
283
297
 
284
- uni.$emit(DEV_OPTION_SEND, options);
298
+ this.eventBus.emit(DEV_OPTION_SEND, options);
285
299
  }
286
300
 
287
301
  /**
@@ -290,7 +304,7 @@ export class DevEvent {
290
304
  * @memberof DevEvent
291
305
  */
292
306
  acceptMessage() {
293
- uni.$on(
307
+ this.eventBus.on(
294
308
  DEV_WINDOW_MESSAGE,
295
309
  (data: {
296
310
  type: string;
@@ -306,6 +320,7 @@ export class DevEvent {
306
320
  exportWindow: boolean;
307
321
  exportDevice: boolean;
308
322
  exportSystem: boolean;
323
+ code: string;
309
324
  [key: string]: any;
310
325
  };
311
326
  }) => {
@@ -407,6 +422,14 @@ export class DevEvent {
407
422
  else if (data.type === DEV_OPTION_GET) {
408
423
  this.sendDevToolOption();
409
424
  }
425
+ // 清空事件
426
+ else if (data.type === DEV_UNI_EVENT_CLEAR) {
427
+ this.uniEventClear();
428
+ }
429
+ // 运行 js
430
+ else if (data.type === DEV_RUN_JS) {
431
+ this.devRunJS(data.data.code);
432
+ }
410
433
  },
411
434
  );
412
435
  }
@@ -662,4 +685,95 @@ export class DevEvent {
662
685
  getDevToolDestroy() {
663
686
  return this.store.getDevToolDestroy();
664
687
  }
688
+
689
+ updateUniEventCount(type: DevTool.EventCountKey) {
690
+ this.store.updateUniEventCount(type);
691
+ this.postMessage();
692
+ }
693
+ updateUniEventList(eventList: DevTool.EventItem[]) {
694
+ this.store.updateUniEventList(eventList);
695
+ this.postMessage();
696
+ }
697
+ uniEventClear() {
698
+ this.store.uniEventClear();
699
+ }
700
+
701
+ resetUniEvent() {
702
+ uni.$on = backup.$on;
703
+ uni.$once = backup.$once;
704
+ uni.$emit = backup.$emit;
705
+ uni.$off = backup.$off;
706
+ }
707
+
708
+ execute(code: any): Promise<any> {
709
+ const evalFunction = '_e_v_a_l_'.replace(
710
+ /_/g,
711
+ '',
712
+ ) as keyof typeof globalThis;
713
+
714
+ if (!globalThis?.[evalFunction]) {
715
+ return Promise.reject(
716
+ new Error('[DevTool] DevRunJS 当前环境不支持执行js'),
717
+ );
718
+ }
719
+
720
+ return Promise.resolve(globalThis?.[evalFunction](code));
721
+ }
722
+
723
+ /**
724
+ * 运行js
725
+ *
726
+ * @param {string} code
727
+ * @memberof DevEvent
728
+ */
729
+ devRunJS(code: string) {
730
+ const start = Date.now();
731
+ const consoleItem: DevTool.ConsoleItem = {
732
+ type: 'log',
733
+ args: [
734
+ {
735
+ type: 'string',
736
+ value: code,
737
+ },
738
+ ],
739
+ position: 'dev/devEvent/index.ts',
740
+ time: getCurrentDate(),
741
+ stack: 'http://usr/devRunJS',
742
+ mode: 'input',
743
+ };
744
+ console.log(code);
745
+ this.store.updateConsoleList([{ ...consoleItem }]);
746
+
747
+ this.postMessageFn();
748
+
749
+ this.execute(code)
750
+ .then((res) => {
751
+ consoleItem.args = [
752
+ {
753
+ type: transformValueToView(res),
754
+ value: parseValue(res),
755
+ },
756
+ ];
757
+ consoleItem.position = `dev/devEvent/index.ts ${Date.now() - start}ms`;
758
+ consoleItem.time = getCurrentDate();
759
+ consoleItem.mode = 'output';
760
+ })
761
+ .catch((err: Error) => {
762
+ console.error(err);
763
+ consoleItem.args = [
764
+ {
765
+ type: 'string',
766
+ value: err.message,
767
+ },
768
+ ];
769
+ consoleItem.position = `dev/devEvent/index.ts ${Date.now() - start}ms`;
770
+ consoleItem.time = getCurrentDate();
771
+ consoleItem.type = 'error';
772
+ consoleItem.mode = 'output';
773
+ })
774
+ .finally(() => {
775
+ this.store.updateConsoleList([{ ...consoleItem }]);
776
+ this.postMessageFn();
777
+ });
778
+ }
665
779
  }
@@ -0,0 +1,94 @@
1
+ type EventHandler<T = any> = (payload: T) => void;
2
+
3
+ export class EventBus {
4
+ // 使用 Map 存储事件及其对应的回调函数数组
5
+ private events: Map<string, EventHandler[]> = new Map();
6
+
7
+ /**
8
+ * 订阅事件
9
+ * @param eventName 事件名称
10
+ * @param handler 事件处理函数
11
+ * @returns 返回一个取消订阅的函数
12
+ */
13
+ on<T = any>(eventName: string, handler: EventHandler<T>): () => void {
14
+ if (!this.events.has(eventName)) {
15
+ this.events.set(eventName, []);
16
+ }
17
+ const handlers = this.events.get(eventName)!;
18
+ handlers.push(handler as EventHandler);
19
+
20
+ // 返回取消订阅的函数
21
+ return () => {
22
+ this.off(eventName, handler);
23
+ };
24
+ }
25
+
26
+ /**
27
+ * 发布事件
28
+ * @param eventName 事件名称
29
+ * @param payload 事件携带的数据
30
+ */
31
+ emit<T = any>(eventName: string, payload: T): void {
32
+ if (this.events.has(eventName)) {
33
+ const handlers = [...this.events.get(eventName)!];
34
+ handlers.forEach((handler) => handler(payload));
35
+ }
36
+ }
37
+
38
+ /**
39
+ * 取消订阅事件
40
+ * @param eventName 事件名称
41
+ * @param handler 要取消的事件处理函数
42
+ */
43
+ off<T = any>(eventName: string, handler: EventHandler<T>): void {
44
+ if (this.events.has(eventName)) {
45
+ const handlers = this.events.get(eventName)!;
46
+ this.events.set(
47
+ eventName,
48
+ handlers.filter((h) => h !== handler),
49
+ );
50
+ }
51
+ }
52
+
53
+ /**
54
+ * 只订阅一次事件,触发后自动取消订阅
55
+ * @param eventName 事件名称
56
+ * @param handler 事件处理函数
57
+ */
58
+ once<T = any>(eventName: string, handler: EventHandler<T>): void {
59
+ const wrapper = (payload: T) => {
60
+ handler(payload);
61
+ this.off(eventName, wrapper);
62
+ };
63
+ this.on(eventName, wrapper);
64
+ }
65
+
66
+ /**
67
+ * 清除指定事件的所有订阅者,如果未提供事件名称,则清除所有事件
68
+ * @param eventName 可选的事件名称
69
+ */
70
+ clear(eventName?: string): void {
71
+ if (eventName) {
72
+ this.events.delete(eventName);
73
+ } else {
74
+ this.events.clear();
75
+ }
76
+ }
77
+
78
+ /**
79
+ * 获取指定事件的订阅者数量,如果未提供事件名称,则返回所有事件的订阅者总数
80
+ * @param eventName 可选的事件名称
81
+ * @returns 订阅者数量
82
+ */
83
+ count(eventName?: string): number {
84
+ if (eventName) {
85
+ return this.events.has(eventName)
86
+ ? this.events.get(eventName)!.length
87
+ : 0;
88
+ }
89
+ return Array.from(this.events.values()).reduce(
90
+ (sum, handlers) => sum + handlers.length,
91
+ 0,
92
+ );
93
+ }
94
+ }