@u-devtools/core 0.1.5 → 0.2.0
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.
- package/LICENSE +1 -2
- package/README.md +119 -0
- package/dist/index.cjs.js +46 -1
- package/dist/index.d.ts +871 -64
- package/dist/index.es.js +3113 -56
- package/dist/vite/vite.config.base.cjs.js +1 -0
- package/dist/vite/vite.config.base.d.ts +59 -0
- package/dist/vite/vite.config.base.js +91 -0
- package/dist/vite.config.base.d.ts +42 -0
- package/package.json +26 -18
- package/src/bridge-app.ts +198 -51
- package/src/control.ts +23 -52
- package/src/event-bus.ts +126 -0
- package/src/index.ts +476 -44
- package/src/schemas/rpc.ts +36 -0
- package/src/schemas/settings.ts +125 -0
- package/src/transport.ts +172 -0
- package/src/transports/broadcast-transport.ts +174 -0
- package/src/transports/hmr-transport.ts +82 -0
- package/src/transports/websocket-transport.ts +158 -0
- package/vite/vite.config.base.ts +81 -14
- package/vite/clean-timestamp-plugin.ts +0 -28
package/src/control.ts
CHANGED
|
@@ -2,38 +2,38 @@ import type { AppBridge } from './bridge-app';
|
|
|
2
2
|
|
|
3
3
|
export interface OverlayContext {
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Open the main DevTools window
|
|
6
6
|
*/
|
|
7
7
|
open: () => void;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Close the main DevTools window
|
|
11
11
|
*/
|
|
12
12
|
close: () => void;
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Toggle window state
|
|
16
16
|
*/
|
|
17
17
|
toggle: () => void;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Current state
|
|
21
21
|
*/
|
|
22
22
|
isOpen: boolean;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Switch to plugin by name
|
|
26
26
|
*/
|
|
27
27
|
switchPlugin: (pluginName: string) => void;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* Switch tab within plugin by tab name
|
|
31
31
|
*/
|
|
32
32
|
switchTab: (pluginName: string, tabName: string) => void;
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* Create a temporary bridge for sending messages.
|
|
36
|
+
* Useful if you don't have access to the plugin's global bridge in this scope.
|
|
37
37
|
*/
|
|
38
38
|
createBridge: (namespace: string) => AppBridge;
|
|
39
39
|
}
|
|
@@ -41,12 +41,12 @@ export interface OverlayContext {
|
|
|
41
41
|
export interface OverlayMenuItem {
|
|
42
42
|
id: string;
|
|
43
43
|
label: string;
|
|
44
|
-
icon?: string; //
|
|
45
|
-
iconSvg?: string; // SVG
|
|
46
|
-
iconUrl?: string; // URL
|
|
44
|
+
icon?: string; // Icon name (Heroicons) - for backward compatibility
|
|
45
|
+
iconSvg?: string; // SVG as text
|
|
46
|
+
iconUrl?: string; // URL to icon
|
|
47
47
|
order?: number;
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* Event handlers (receive context)
|
|
50
50
|
*/
|
|
51
51
|
onClick?: (ctx: OverlayContext, event: MouseEvent) => void;
|
|
52
52
|
onDoubleClick?: (ctx: OverlayContext, event: MouseEvent) => void;
|
|
@@ -61,72 +61,43 @@ export interface OverlayMenuItem {
|
|
|
61
61
|
onBlur?: (ctx: OverlayContext, event: FocusEvent) => void;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export const OVERLAY_EVENT = 'u-devtools:register-menu-item';
|
|
65
|
-
|
|
66
64
|
declare global {
|
|
67
65
|
interface Window {
|
|
68
66
|
__UDEVTOOLS_MENU_ITEMS__?: OverlayMenuItem[];
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
/**
|
|
73
|
-
* Функция для регистрации кнопки в оверлее (вызывается из app.ts плагина)
|
|
74
|
-
*/
|
|
75
|
-
export function registerMenuItem(item: OverlayMenuItem) {
|
|
76
|
-
if (typeof window === 'undefined') return;
|
|
77
|
-
|
|
78
|
-
// Инициализируем глобальный массив, если его нет
|
|
79
|
-
if (!window.__UDEVTOOLS_MENU_ITEMS__) {
|
|
80
|
-
window.__UDEVTOOLS_MENU_ITEMS__ = [];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Сохраняем элемент в глобальный массив (для случаев, когда overlay еще не загружен)
|
|
84
|
-
const existingIdx = window.__UDEVTOOLS_MENU_ITEMS__.findIndex((i) => i.id === item.id);
|
|
85
|
-
if (existingIdx !== -1) {
|
|
86
|
-
window.__UDEVTOOLS_MENU_ITEMS__[existingIdx] = item;
|
|
87
|
-
} else {
|
|
88
|
-
window.__UDEVTOOLS_MENU_ITEMS__.push(item);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Отправляем событие, которое поймает Vue-приложение оверлея (для обратной совместимости)
|
|
92
|
-
window.dispatchEvent(
|
|
93
|
-
new CustomEvent(OVERLAY_EVENT, {
|
|
94
|
-
detail: item,
|
|
95
|
-
})
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
70
|
export class DevToolsControl {
|
|
100
71
|
private channel: BroadcastChannel;
|
|
101
72
|
|
|
102
73
|
constructor() {
|
|
103
|
-
//
|
|
74
|
+
// Single channel for state management
|
|
104
75
|
this.channel = new BroadcastChannel('u-devtools:control');
|
|
105
76
|
}
|
|
106
77
|
|
|
107
78
|
/**
|
|
108
|
-
*
|
|
79
|
+
* Open DevTools
|
|
109
80
|
*/
|
|
110
81
|
open() {
|
|
111
82
|
this.channel.postMessage({ action: 'open' });
|
|
112
83
|
}
|
|
113
84
|
|
|
114
85
|
/**
|
|
115
|
-
*
|
|
86
|
+
* Close DevTools
|
|
116
87
|
*/
|
|
117
88
|
close() {
|
|
118
89
|
this.channel.postMessage({ action: 'close' });
|
|
119
90
|
}
|
|
120
91
|
|
|
121
92
|
/**
|
|
122
|
-
*
|
|
93
|
+
* Toggle state
|
|
123
94
|
*/
|
|
124
95
|
toggle() {
|
|
125
96
|
this.channel.postMessage({ action: 'toggle' });
|
|
126
97
|
}
|
|
127
98
|
|
|
128
99
|
/**
|
|
129
|
-
*
|
|
100
|
+
* Get current state (asynchronously)
|
|
130
101
|
*/
|
|
131
102
|
isOpen(): Promise<boolean> {
|
|
132
103
|
return new Promise((resolve) => {
|
|
@@ -138,10 +109,10 @@ export class DevToolsControl {
|
|
|
138
109
|
};
|
|
139
110
|
|
|
140
111
|
this.channel.addEventListener('message', handler);
|
|
141
|
-
//
|
|
112
|
+
// Request state
|
|
142
113
|
this.channel.postMessage({ action: 'get-state' });
|
|
143
114
|
|
|
144
|
-
//
|
|
115
|
+
// Timeout in case DevTools is not loaded
|
|
145
116
|
setTimeout(() => {
|
|
146
117
|
this.channel.removeEventListener('message', handler);
|
|
147
118
|
resolve(false);
|
|
@@ -150,7 +121,7 @@ export class DevToolsControl {
|
|
|
150
121
|
}
|
|
151
122
|
|
|
152
123
|
/**
|
|
153
|
-
*
|
|
124
|
+
* Subscribe to state changes
|
|
154
125
|
*/
|
|
155
126
|
onStateChange(cb: (isOpen: boolean) => void) {
|
|
156
127
|
const handler = (e: MessageEvent) => {
|
|
@@ -163,14 +134,14 @@ export class DevToolsControl {
|
|
|
163
134
|
}
|
|
164
135
|
|
|
165
136
|
/**
|
|
166
|
-
*
|
|
137
|
+
* Switch to plugin by name
|
|
167
138
|
*/
|
|
168
139
|
switchPlugin(pluginName: string) {
|
|
169
140
|
this.channel.postMessage({ action: 'switch-plugin', pluginName });
|
|
170
141
|
}
|
|
171
142
|
|
|
172
143
|
/**
|
|
173
|
-
*
|
|
144
|
+
* Switch tab within plugin by tab name
|
|
174
145
|
*/
|
|
175
146
|
switchTab(pluginName: string, tabName: string) {
|
|
176
147
|
this.channel.postMessage({ action: 'switch-tab', pluginName, tabName });
|
|
@@ -181,5 +152,5 @@ export class DevToolsControl {
|
|
|
181
152
|
}
|
|
182
153
|
}
|
|
183
154
|
|
|
184
|
-
//
|
|
155
|
+
// Export singleton for convenience
|
|
185
156
|
export const devtools = new DevToolsControl();
|
package/src/event-bus.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed Event Bus for inter-plugin communication
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* EventBus events interface.
|
|
7
|
+
* Plugins can extend this interface through module augmentation.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // In your plugin
|
|
12
|
+
* declare module '@u-devtools/core' {
|
|
13
|
+
* interface BusEvents {
|
|
14
|
+
* 'my-plugin:custom-event': { data: string };
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export interface BusEvents {
|
|
20
|
+
'plugin:mounted': { name: string };
|
|
21
|
+
'plugin:unmounted': { name: string };
|
|
22
|
+
navigate: { path: string };
|
|
23
|
+
'settings:changed': { key: string; value: unknown };
|
|
24
|
+
'storage:changed': { key: string; value: unknown };
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Typed Event Emitter for inter-plugin communication.
|
|
30
|
+
* Provides type-safe event emission and subscription.
|
|
31
|
+
*
|
|
32
|
+
* @template T - Type definition for events and their data
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { TypedEventBus, type BusEvents } from '@u-devtools/core';
|
|
37
|
+
*
|
|
38
|
+
* // Create event bus instance
|
|
39
|
+
* const bus = new TypedEventBus<BusEvents>();
|
|
40
|
+
*
|
|
41
|
+
* // Emit events
|
|
42
|
+
* bus.emit('plugin:mounted', { name: 'my-plugin' });
|
|
43
|
+
* bus.emit('navigate', { path: '/settings' });
|
|
44
|
+
* bus.emit('settings:changed', { key: 'theme', value: 'dark' });
|
|
45
|
+
*
|
|
46
|
+
* // Subscribe to events
|
|
47
|
+
* const unsubscribe1 = bus.on('plugin:mounted', ({ name }) => {
|
|
48
|
+
* console.log(`Plugin ${name} was mounted`);
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* const unsubscribe2 = bus.on('navigate', ({ path }) => {
|
|
52
|
+
* console.log(`Navigating to: ${path}`);
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* // Unsubscribe
|
|
56
|
+
* unsubscribe1();
|
|
57
|
+
* unsubscribe2();
|
|
58
|
+
*
|
|
59
|
+
* // Or use off method (handler must be the same function reference)
|
|
60
|
+
* const handler = ({ name }: { name: string }) => {
|
|
61
|
+
* console.log(`Plugin ${name} was mounted`);
|
|
62
|
+
* };
|
|
63
|
+
* bus.on('plugin:mounted', handler);
|
|
64
|
+
* bus.off('plugin:mounted', handler);
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export class TypedEventBus<T extends Record<string, unknown> = BusEvents> {
|
|
68
|
+
private listeners = new Map<keyof T, Set<(data: T[keyof T]) => void>>();
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Emit an event
|
|
72
|
+
*/
|
|
73
|
+
emit<K extends keyof T>(event: K, data: T[K]): void {
|
|
74
|
+
const handlers = this.listeners.get(event);
|
|
75
|
+
if (handlers) {
|
|
76
|
+
handlers.forEach((fn) => {
|
|
77
|
+
fn(data);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Subscribe to an event
|
|
84
|
+
*/
|
|
85
|
+
on<K extends keyof T>(event: K, handler: (data: T[K]) => void): () => void {
|
|
86
|
+
if (!this.listeners.has(event)) {
|
|
87
|
+
this.listeners.set(event, new Set());
|
|
88
|
+
}
|
|
89
|
+
const handlers = this.listeners.get(event);
|
|
90
|
+
if (handlers) {
|
|
91
|
+
handlers.add(handler as (data: T[keyof T]) => void);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Return unsubscribe function
|
|
95
|
+
return () => {
|
|
96
|
+
this.off(event, handler);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Unsubscribe from an event
|
|
102
|
+
*/
|
|
103
|
+
off<K extends keyof T>(event: K, handler: (data: T[K]) => void): void {
|
|
104
|
+
const handlers = this.listeners.get(event);
|
|
105
|
+
if (handlers) {
|
|
106
|
+
handlers.delete(handler as (data: T[keyof T]) => void);
|
|
107
|
+
if (handlers.size === 0) {
|
|
108
|
+
this.listeners.delete(event);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Clear all subscriptions
|
|
115
|
+
*/
|
|
116
|
+
clear(): void {
|
|
117
|
+
this.listeners.clear();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get the number of subscribers for an event
|
|
122
|
+
*/
|
|
123
|
+
listenerCount<K extends keyof T>(event: K): number {
|
|
124
|
+
return this.listeners.get(event)?.size || 0;
|
|
125
|
+
}
|
|
126
|
+
}
|