@u-devtools/core 0.1.6 → 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.
@@ -0,0 +1,158 @@
1
+ import { Transport } from '../transport';
2
+ import type { RpcMessage } from '../index';
3
+
4
+ /**
5
+ * Transport based on WebSocket
6
+ * Used for communication between client and server when HMR is unavailable
7
+ * (e.g., for remote debugging or production builds)
8
+ */
9
+ export class WebSocketTransport extends Transport {
10
+ private ws: WebSocket | null = null;
11
+
12
+ // Reconnection
13
+ private reconnectAttempts = 0;
14
+ private maxReconnectAttempts = 5;
15
+ private reconnectDelay = 1000;
16
+ private reconnectTimer: ReturnType<typeof setTimeout> | null = null; // For cleanup
17
+ private isManualClose = false;
18
+
19
+ // Message queue (buffer)
20
+ private messageQueue: string[] = [];
21
+
22
+ constructor(private url: string) {
23
+ super();
24
+ this.connect();
25
+ }
26
+
27
+ private connect() {
28
+ // Clear old socket if exists
29
+ if (this.ws) {
30
+ this.ws.close();
31
+ this.ws = null;
32
+ }
33
+
34
+ try {
35
+ this.ws = new WebSocket(this.url);
36
+
37
+ this.ws.onopen = () => {
38
+ this.reconnectAttempts = 0;
39
+ console.log('[WebSocketTransport] Connected');
40
+ this.flushQueue(); // Send queued messages
41
+ };
42
+
43
+ this.ws.onmessage = (e) => {
44
+ try {
45
+ const data = JSON.parse(e.data);
46
+ // Call base class method for message routing
47
+ this.handleMessage(data);
48
+ } catch (err) {
49
+ console.error('[WebSocketTransport] Failed to parse message:', err);
50
+ }
51
+ };
52
+
53
+ this.ws.onerror = (error) => {
54
+ // Connection errors often lead to onclose, so just log here
55
+ console.error('[WebSocketTransport] Socket error:', error);
56
+ };
57
+
58
+ this.ws.onclose = (e) => {
59
+ if (this.isManualClose) return;
60
+
61
+ console.warn(`[WebSocketTransport] Closed (code: ${e.code}). Attempting reconnect...`);
62
+ this.scheduleReconnect();
63
+ };
64
+ } catch (err) {
65
+ console.error('[WebSocketTransport] Failed to create socket:', err);
66
+ this.scheduleReconnect();
67
+ }
68
+ }
69
+
70
+ private scheduleReconnect() {
71
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
72
+
73
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
74
+ this.reconnectAttempts++;
75
+ const delay = this.reconnectDelay * this.reconnectAttempts; // Exponential backoff (simple)
76
+
77
+ console.log(`[WebSocketTransport] Reconnecting in ${delay}ms...`);
78
+
79
+ this.reconnectTimer = setTimeout(() => {
80
+ this.connect();
81
+ }, delay);
82
+ } else {
83
+ console.error('[WebSocketTransport] Max reconnect attempts reached');
84
+ }
85
+ }
86
+
87
+ protected sendMessage(type: string, data: unknown): void {
88
+ // Improvement 3: Form message more safely.
89
+ // Assume Base Transport passes payload that needs to be wrapped,
90
+ // OR Base Transport already passes a ready structure.
91
+ // Better to standardize: Transport should pass a ready object for sending.
92
+
93
+ // Let's form the packet here, as in original, but safer:
94
+ const rawData = data as any;
95
+
96
+ const message: RpcMessage = {
97
+ id: rawData.id || '', // Fallback
98
+ type: type as 'request' | 'event' | 'response', // Cast type safely
99
+ method: rawData.method,
100
+ payload: type === 'request' ? rawData.payload : rawData,
101
+ error: rawData.error, // Don't forget errors
102
+ };
103
+
104
+ const serialized = JSON.stringify(message);
105
+
106
+ if (this.isConnected()) {
107
+ this.ws!.send(serialized);
108
+ } else {
109
+ // Improvement 2: Buffering
110
+ this.messageQueue.push(serialized);
111
+ }
112
+ }
113
+
114
+ private flushQueue() {
115
+ if (!this.isConnected()) return;
116
+
117
+ while (this.messageQueue.length > 0) {
118
+ const msg = this.messageQueue.shift();
119
+ if (msg) this.ws!.send(msg);
120
+ }
121
+ }
122
+
123
+ // Subscription methods not needed, as WS is a "pipe".
124
+ // Base Transport class manages listener map itself,
125
+ // and handleMessage distributes data.
126
+ protected subscribe(_type: string, _handler: (data: unknown) => void): () => void {
127
+ return () => {};
128
+ }
129
+
130
+ protected unsubscribe(_type: string, _handler: (data: unknown) => void): void {
131
+ // No-op
132
+ }
133
+
134
+ override dispose() {
135
+ this.isManualClose = true;
136
+
137
+ // Improvement 1: Clear timer
138
+ if (this.reconnectTimer) {
139
+ clearTimeout(this.reconnectTimer);
140
+ this.reconnectTimer = null;
141
+ }
142
+
143
+ if (this.ws) {
144
+ this.ws.close();
145
+ this.ws = null;
146
+ }
147
+
148
+ this.messageQueue = []; // Clear memory
149
+ super.dispose();
150
+ }
151
+
152
+ /**
153
+ * Check connection status
154
+ */
155
+ isConnected(): boolean {
156
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
157
+ }
158
+ }
@@ -1,10 +1,36 @@
1
- import { defineConfig, type PluginOption } from 'vite';
1
+ import { defineConfig, type Plugin, type PluginOption } from 'vite';
2
2
  import vue from '@vitejs/plugin-vue';
3
3
  import dts from 'vite-plugin-dts';
4
- import { resolve } from 'node:path';
5
- import { cleanTimestampFiles } from './clean-timestamp-plugin';
4
+ import { join, resolve } from 'node:path';
5
+ import { readdirSync, unlinkSync } from 'node:fs';
6
6
 
7
- interface ConfigOptions {
7
+ function cleanTimestampFiles(dir: string): Plugin {
8
+ return {
9
+ name: 'clean-timestamp-files',
10
+ buildStart() {
11
+ try {
12
+ const files = readdirSync(dir);
13
+ files.forEach((file) => {
14
+ if (file.includes('.timestamp-') && file.endsWith('.mjs')) {
15
+ try {
16
+ unlinkSync(join(dir, file));
17
+ } catch (_e) {
18
+ // Ignore deletion errors
19
+ }
20
+ }
21
+ });
22
+ } catch (_e) {
23
+ // Ignore directory read errors
24
+ }
25
+ },
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Configuration options for createViteConfig
31
+ * @internal
32
+ */
33
+ export interface ConfigOptions {
8
34
  entry: string | Record<string, string>;
9
35
  name: string;
10
36
  dir: string;
@@ -24,6 +50,40 @@ interface ConfigOptions {
24
50
  cssCodeSplit?: boolean;
25
51
  }
26
52
 
53
+ /**
54
+ * Creates a Vite configuration optimized for building DevTools packages.
55
+ *
56
+ * This function generates a Vite config with proper TypeScript declaration file generation,
57
+ * preserving JSDoc comments in .d.ts files for better IDE autocomplete.
58
+ *
59
+ * @param options - Configuration options for the Vite build
60
+ * @param options.entry - Entry point(s) for the library (string or record of entry points)
61
+ * @param options.name - Library name (used for UMD builds)
62
+ * @param options.dir - Package directory (usually __dirname)
63
+ * @param options.external - Array of module IDs to externalize
64
+ * @param options.clearScreen - Whether to clear screen on build (default: false)
65
+ * @param options.useVue - Whether to include Vue plugin (default: true)
66
+ * @param options.formats - Output formats: 'es' and/or 'cjs' (default: ['es', 'cjs'])
67
+ * @param options.fileName - Custom file naming function or string
68
+ * @param options.dtsOptions - Options for TypeScript declaration file generation
69
+ * @param options.additionalPlugins - Additional Vite plugins to include
70
+ * @param options.resolveAlias - Path aliases for module resolution
71
+ * @param options.cssCodeSplit - Whether to enable CSS code splitting
72
+ * @returns Vite configuration object
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * import { createViteConfig } from '@u-devtools/core/vite.config.base';
77
+ *
78
+ * export default createViteConfig({
79
+ * name: 'MyPackage',
80
+ * entry: 'src/index.ts',
81
+ * dir: __dirname,
82
+ * });
83
+ * ```
84
+ *
85
+ * @public
86
+ */
27
87
  export function createViteConfig({
28
88
  entry,
29
89
  name,
@@ -51,6 +111,10 @@ export function createViteConfig({
51
111
  exclude: dtsOptions.exclude,
52
112
  copyDtsFiles: dtsOptions.copyDtsFiles,
53
113
  tsconfigPath: resolve(dir, 'tsconfig.json'),
114
+ outDir: resolve(dir, 'dist'),
115
+ compilerOptions: {
116
+ removeComments: false, // Explicitly preserve JSDoc comments
117
+ },
54
118
  })
55
119
  );
56
120
 
@@ -72,19 +136,22 @@ export function createViteConfig({
72
136
  return defineConfig({
73
137
  clearScreen,
74
138
  plugins,
75
- // ВАЖНО: Это предотвращает замену import.meta.hot на false при сборке
76
- // Теперь код в dist будет содержать проверку if (import.meta.hot)
77
- // и HMR будет работать даже в собранной версии
139
+ // IMPORTANT: This prevents replacing import.meta.hot with false during build
140
+ // Now code in dist will contain check if (import.meta.hot)
141
+ // and HMR will work even in built version
78
142
  define: {
79
143
  'import.meta.hot': 'import.meta.hot',
80
144
  },
81
- resolve: resolveAlias
82
- ? {
83
- alias: Object.fromEntries(
84
- Object.entries(resolveAlias).map(([key, value]) => [key, resolve(dir, value)])
85
- ),
86
- }
87
- : undefined,
145
+ resolve: {
146
+ extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json', '.vue'],
147
+ ...(resolveAlias
148
+ ? {
149
+ alias: Object.fromEntries(
150
+ Object.entries(resolveAlias).map(([key, value]) => [key, resolve(dir, value)])
151
+ ),
152
+ }
153
+ : {}),
154
+ },
88
155
  build: {
89
156
  lib: {
90
157
  entry: entryResolved,
@@ -1,28 +0,0 @@
1
- import type { Plugin } from 'vite';
2
- import { readdirSync, unlinkSync } from 'node:fs';
3
- import { join } from 'node:path';
4
-
5
- /**
6
- * Плагин для очистки временных timestamp файлов, создаваемых Vite
7
- */
8
- export function cleanTimestampFiles(dir: string): Plugin {
9
- return {
10
- name: 'clean-timestamp-files',
11
- buildStart() {
12
- try {
13
- const files = readdirSync(dir);
14
- files.forEach((file) => {
15
- if (file.includes('.timestamp-') && file.endsWith('.mjs')) {
16
- try {
17
- unlinkSync(join(dir, file));
18
- } catch (_e) {
19
- // Игнорируем ошибки удаления
20
- }
21
- }
22
- });
23
- } catch (_e) {
24
- // Игнорируем ошибки чтения директории
25
- }
26
- },
27
- };
28
- }