@buenojs/bueno 0.8.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/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HMR Client Script
|
|
3
|
+
*
|
|
4
|
+
* This script is injected into HTML pages to enable Hot Module Replacement.
|
|
5
|
+
* It handles WebSocket communication, module updates, and framework-specific HMR.
|
|
6
|
+
*
|
|
7
|
+
* The script is kept minimal (< 5KB) for fast injection.
|
|
8
|
+
*
|
|
9
|
+
* @module frontend/hmr-client
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Client script as a string for injection into HTML
|
|
13
|
+
export const HMR_CLIENT_SCRIPT = `
|
|
14
|
+
(function() {
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
// ============= Configuration =============
|
|
18
|
+
const HMR_CONFIG = {
|
|
19
|
+
reconnectInterval: 1000,
|
|
20
|
+
maxReconnectAttempts: 10,
|
|
21
|
+
heartbeatInterval: 30000,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// ============= State =============
|
|
25
|
+
let ws = null;
|
|
26
|
+
let clientId = null;
|
|
27
|
+
let reconnectAttempts = 0;
|
|
28
|
+
let reconnectTimer = null;
|
|
29
|
+
let heartbeatTimer = null;
|
|
30
|
+
let subscribedFiles = new Set();
|
|
31
|
+
let moduleCache = new Map();
|
|
32
|
+
let isConnecting = false;
|
|
33
|
+
|
|
34
|
+
// ============= Framework Detection =============
|
|
35
|
+
const framework = detectFramework();
|
|
36
|
+
|
|
37
|
+
function detectFramework() {
|
|
38
|
+
if (typeof window === 'undefined') return 'unknown';
|
|
39
|
+
|
|
40
|
+
// Check for React
|
|
41
|
+
if (window.React || document.querySelector('[data-reactroot]') ||
|
|
42
|
+
document.querySelector('[data-reactid]')) {
|
|
43
|
+
return 'react';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check for Vue
|
|
47
|
+
if (window.Vue || document.querySelector('[data-v-]') ||
|
|
48
|
+
document.querySelector('[data-vue-app]')) {
|
|
49
|
+
return 'vue';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check for Svelte
|
|
53
|
+
if (window.__SVELTE_HMR__ || document.querySelector('[data-svelte]')) {
|
|
54
|
+
return 'svelte';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check for Solid
|
|
58
|
+
if (window.Solid$$ || document.querySelector('[data-solid]')) {
|
|
59
|
+
return 'solid';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return 'unknown';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============= WebSocket Connection =============
|
|
66
|
+
function connect() {
|
|
67
|
+
if (isConnecting || (ws && ws.readyState === WebSocket.OPEN)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
isConnecting = true;
|
|
72
|
+
|
|
73
|
+
// Build WebSocket URL
|
|
74
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
75
|
+
const host = window.location.hostname;
|
|
76
|
+
const port = getHMRPort();
|
|
77
|
+
const url = protocol + '//' + host + ':' + port + '/_hmr';
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
ws = new WebSocket(url);
|
|
81
|
+
|
|
82
|
+
ws.onopen = handleOpen;
|
|
83
|
+
ws.onclose = handleClose;
|
|
84
|
+
ws.onerror = handleError;
|
|
85
|
+
ws.onmessage = handleMessage;
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.error('[HMR] Failed to create WebSocket:', e);
|
|
88
|
+
isConnecting = false;
|
|
89
|
+
scheduleReconnect();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getHMRPort() {
|
|
94
|
+
// Try to get port from script tag or default to dev server port + 1
|
|
95
|
+
const scripts = document.querySelectorAll('script[data-hmr-port]');
|
|
96
|
+
if (scripts.length > 0) {
|
|
97
|
+
return parseInt(scripts[0].getAttribute('data-hmr-port'), 10);
|
|
98
|
+
}
|
|
99
|
+
return parseInt(window.location.port || '3000', 10) + 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function handleOpen() {
|
|
103
|
+
isConnecting = false;
|
|
104
|
+
reconnectAttempts = 0;
|
|
105
|
+
console.log('[HMR] Connected');
|
|
106
|
+
|
|
107
|
+
// Start heartbeat
|
|
108
|
+
startHeartbeat();
|
|
109
|
+
|
|
110
|
+
// Re-subscribe to files
|
|
111
|
+
subscribedFiles.forEach(function(fileId) {
|
|
112
|
+
sendMessage({ type: 'subscribe', fileId: fileId });
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function handleClose(event) {
|
|
117
|
+
isConnecting = false;
|
|
118
|
+
stopHeartbeat();
|
|
119
|
+
|
|
120
|
+
if (event.code !== 1000) {
|
|
121
|
+
console.log('[HMR] Connection closed, attempting to reconnect...');
|
|
122
|
+
scheduleReconnect();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function handleError(error) {
|
|
127
|
+
isConnecting = false;
|
|
128
|
+
console.error('[HMR] WebSocket error:', error);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function scheduleReconnect() {
|
|
132
|
+
if (reconnectTimer) {
|
|
133
|
+
clearTimeout(reconnectTimer);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (reconnectAttempts >= HMR_CONFIG.maxReconnectAttempts) {
|
|
137
|
+
console.error('[HMR] Max reconnect attempts reached. Please refresh the page.');
|
|
138
|
+
showOverlay({
|
|
139
|
+
message: 'HMR connection lost. Please refresh the page.',
|
|
140
|
+
type: 'error'
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
reconnectAttempts++;
|
|
146
|
+
var delay = HMR_CONFIG.reconnectInterval * reconnectAttempts;
|
|
147
|
+
|
|
148
|
+
reconnectTimer = setTimeout(function() {
|
|
149
|
+
console.log('[HMR] Reconnecting... (attempt ' + reconnectAttempts + ')');
|
|
150
|
+
connect();
|
|
151
|
+
}, delay);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ============= Heartbeat =============
|
|
155
|
+
function startHeartbeat() {
|
|
156
|
+
stopHeartbeat();
|
|
157
|
+
heartbeatTimer = setInterval(function() {
|
|
158
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
159
|
+
sendMessage({ type: 'ping' });
|
|
160
|
+
}
|
|
161
|
+
}, HMR_CONFIG.heartbeatInterval);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function stopHeartbeat() {
|
|
165
|
+
if (heartbeatTimer) {
|
|
166
|
+
clearInterval(heartbeatTimer);
|
|
167
|
+
heartbeatTimer = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ============= Message Handling =============
|
|
172
|
+
function sendMessage(message) {
|
|
173
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
174
|
+
ws.send(JSON.stringify(message));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function handleMessage(event) {
|
|
179
|
+
try {
|
|
180
|
+
var message = JSON.parse(event.data);
|
|
181
|
+
|
|
182
|
+
switch (message.type) {
|
|
183
|
+
case 'connected':
|
|
184
|
+
clientId = message.clientId;
|
|
185
|
+
console.log('[HMR] Client ID:', clientId);
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
case 'pong':
|
|
189
|
+
// Heartbeat response
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
case 'update':
|
|
193
|
+
handleUpdate(message);
|
|
194
|
+
break;
|
|
195
|
+
|
|
196
|
+
case 'reload':
|
|
197
|
+
handleReload(message);
|
|
198
|
+
break;
|
|
199
|
+
|
|
200
|
+
case 'error':
|
|
201
|
+
handleError(message);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
} catch (e) {
|
|
205
|
+
console.error('[HMR] Failed to parse message:', e);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ============= Update Handling =============
|
|
210
|
+
function handleUpdate(message) {
|
|
211
|
+
console.log('[HMR] Update received:', message.fileId);
|
|
212
|
+
|
|
213
|
+
// Hide any existing error overlay
|
|
214
|
+
hideOverlay();
|
|
215
|
+
|
|
216
|
+
var changes = message.changes || [];
|
|
217
|
+
var hasCSSUpdate = changes.some(function(file) {
|
|
218
|
+
return file.endsWith('.css') || file.endsWith('.scss') ||
|
|
219
|
+
file.endsWith('.sass') || file.endsWith('.less');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (hasCSSUpdate) {
|
|
223
|
+
// Handle CSS updates without flash
|
|
224
|
+
updateCSS(changes);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check if we can do hot update
|
|
228
|
+
if (canHotUpdate(changes)) {
|
|
229
|
+
performHotUpdate(message);
|
|
230
|
+
} else {
|
|
231
|
+
// Fall back to full reload
|
|
232
|
+
console.log('[HMR] Cannot hot update, reloading page...');
|
|
233
|
+
window.location.reload();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function handleReload(message) {
|
|
238
|
+
console.log('[HMR] Full reload requested');
|
|
239
|
+
hideOverlay();
|
|
240
|
+
window.location.reload();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function handleError(message) {
|
|
244
|
+
console.error('[HMR] Error:', message.error);
|
|
245
|
+
showOverlay({
|
|
246
|
+
message: message.error.message,
|
|
247
|
+
stack: message.error.stack,
|
|
248
|
+
file: message.error.file,
|
|
249
|
+
line: message.error.line,
|
|
250
|
+
column: message.error.column,
|
|
251
|
+
type: 'error'
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ============= CSS Hot Update =============
|
|
256
|
+
function updateCSS(changedFiles) {
|
|
257
|
+
var links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
258
|
+
|
|
259
|
+
links.forEach(function(link) {
|
|
260
|
+
var href = link.getAttribute('href');
|
|
261
|
+
if (!href) return;
|
|
262
|
+
|
|
263
|
+
// Check if this stylesheet is affected
|
|
264
|
+
var isAffected = changedFiles.some(function(file) {
|
|
265
|
+
return href.includes(file.replace(/^.*\\//, '')) ||
|
|
266
|
+
file.includes(href.replace(/^.*\\//, ''));
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (isAffected) {
|
|
270
|
+
// Add timestamp to force reload
|
|
271
|
+
var newHref = href.split('?')[0] + '?v=' + Date.now();
|
|
272
|
+
|
|
273
|
+
// Create new link and swap
|
|
274
|
+
var newLink = document.createElement('link');
|
|
275
|
+
newLink.rel = 'stylesheet';
|
|
276
|
+
newLink.href = newHref;
|
|
277
|
+
|
|
278
|
+
newLink.onload = function() {
|
|
279
|
+
link.remove();
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
newLink.onerror = function() {
|
|
283
|
+
console.error('[HMR] Failed to reload CSS:', newHref);
|
|
284
|
+
link.remove();
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
link.parentNode.insertBefore(newLink, link);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ============= Hot Update Logic =============
|
|
293
|
+
function canHotUpdate(changes) {
|
|
294
|
+
// Check if all changed files can be hot updated
|
|
295
|
+
return changes.every(function(file) {
|
|
296
|
+
var ext = file.split('.').pop().toLowerCase();
|
|
297
|
+
|
|
298
|
+
// CSS files can always be hot updated
|
|
299
|
+
if (ext === 'css' || ext === 'scss' || ext === 'sass' || ext === 'less') {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// JS/TS files need HMR boundary
|
|
304
|
+
if (ext === 'js' || ext === 'jsx' || ext === 'ts' || ext === 'tsx') {
|
|
305
|
+
return hasHMRBoundary(file);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Framework-specific files
|
|
309
|
+
if (ext === 'vue' || ext === 'svelte') {
|
|
310
|
+
return true; // Vue and Svelte have built-in HMR
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return false;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function hasHMRBoundary(file) {
|
|
318
|
+
// Check if the module accepts hot updates
|
|
319
|
+
// This is a simplified check - in production, we'd track this from the server
|
|
320
|
+
return true; // For now, assume all modules can be hot updated
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function performHotUpdate(message) {
|
|
324
|
+
var changes = message.changes || [];
|
|
325
|
+
|
|
326
|
+
// Framework-specific update handling
|
|
327
|
+
switch (framework) {
|
|
328
|
+
case 'react':
|
|
329
|
+
performReactUpdate(message);
|
|
330
|
+
break;
|
|
331
|
+
case 'vue':
|
|
332
|
+
performVueUpdate(message);
|
|
333
|
+
break;
|
|
334
|
+
case 'svelte':
|
|
335
|
+
performSvelteUpdate(message);
|
|
336
|
+
break;
|
|
337
|
+
case 'solid':
|
|
338
|
+
performSolidUpdate(message);
|
|
339
|
+
break;
|
|
340
|
+
default:
|
|
341
|
+
// Generic update - reload scripts
|
|
342
|
+
performGenericUpdate(message);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ============= React Fast Refresh =============
|
|
347
|
+
function performReactUpdate(message) {
|
|
348
|
+
if (!window.__HMR_REACT_REFRESH__) {
|
|
349
|
+
// React Refresh not available, fall back to reload
|
|
350
|
+
window.location.reload();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
console.log('[HMR] Applying React Fast Refresh...');
|
|
355
|
+
|
|
356
|
+
// Signal to React Refresh that an update is coming
|
|
357
|
+
if (window.__REACT_REFRESH__) {
|
|
358
|
+
try {
|
|
359
|
+
changes.forEach(function(file) {
|
|
360
|
+
// Invalidate the module
|
|
361
|
+
invalidateModule(file);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Trigger React Refresh
|
|
365
|
+
window.__REACT_REFRESH__.performReactRefresh();
|
|
366
|
+
} catch (e) {
|
|
367
|
+
console.error('[HMR] React Fast Refresh failed:', e);
|
|
368
|
+
window.location.reload();
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
// Fallback: reload the page
|
|
372
|
+
window.location.reload();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ============= Vue HMR =============
|
|
377
|
+
function performVueUpdate(message) {
|
|
378
|
+
console.log('[HMR] Applying Vue HMR...');
|
|
379
|
+
|
|
380
|
+
// Vue's HMR is handled by vue-loader and vue-hot-reload-api
|
|
381
|
+
if (window.__VUE_HMR__) {
|
|
382
|
+
try {
|
|
383
|
+
message.changes.forEach(function(file) {
|
|
384
|
+
if (file.endsWith('.vue')) {
|
|
385
|
+
window.__VUE_HMR__.rerender(file);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
} catch (e) {
|
|
389
|
+
console.error('[HMR] Vue HMR failed:', e);
|
|
390
|
+
window.location.reload();
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
window.location.reload();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ============= Svelte HMR =============
|
|
398
|
+
function performSvelteUpdate(message) {
|
|
399
|
+
console.log('[HMR] Applying Svelte HMR...');
|
|
400
|
+
|
|
401
|
+
if (window.__SVELTE_HMR__) {
|
|
402
|
+
try {
|
|
403
|
+
message.changes.forEach(function(file) {
|
|
404
|
+
if (file.endsWith('.svelte')) {
|
|
405
|
+
// Svelte HMR preserves component state
|
|
406
|
+
window.__SVELTE_HMR__.update(file);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
} catch (e) {
|
|
410
|
+
console.error('[HMR] Svelte HMR failed:', e);
|
|
411
|
+
window.location.reload();
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
window.location.reload();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ============= Solid HMR =============
|
|
419
|
+
function performSolidUpdate(message) {
|
|
420
|
+
console.log('[HMR] Applying Solid HMR...');
|
|
421
|
+
|
|
422
|
+
if (window.__SOLID_HMR__) {
|
|
423
|
+
try {
|
|
424
|
+
message.changes.forEach(function(file) {
|
|
425
|
+
invalidateModule(file);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
window.__SOLID_HMR__.update();
|
|
429
|
+
} catch (e) {
|
|
430
|
+
console.error('[HMR] Solid HMR failed:', e);
|
|
431
|
+
window.location.reload();
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
window.location.reload();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ============= Generic Update =============
|
|
439
|
+
function performGenericUpdate(message) {
|
|
440
|
+
console.log('[HMR] Performing generic update...');
|
|
441
|
+
|
|
442
|
+
// For unknown frameworks, reload scripts
|
|
443
|
+
message.changes.forEach(function(file) {
|
|
444
|
+
invalidateModule(file);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Reload the page as a fallback
|
|
448
|
+
window.location.reload();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ============= Module Management =============
|
|
452
|
+
function invalidateModule(fileId) {
|
|
453
|
+
moduleCache.delete(fileId);
|
|
454
|
+
|
|
455
|
+
// Find and reload script tags
|
|
456
|
+
var scripts = document.querySelectorAll('script[src]');
|
|
457
|
+
scripts.forEach(function(script) {
|
|
458
|
+
var src = script.getAttribute('src');
|
|
459
|
+
if (src && src.includes(fileId)) {
|
|
460
|
+
reloadScript(script);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function reloadScript(oldScript) {
|
|
466
|
+
var src = oldScript.getAttribute('src');
|
|
467
|
+
var newSrc = src.split('?')[0] + '?v=' + Date.now();
|
|
468
|
+
|
|
469
|
+
var newScript = document.createElement('script');
|
|
470
|
+
newScript.src = newSrc;
|
|
471
|
+
newScript.type = oldScript.type || 'text/javascript';
|
|
472
|
+
newScript.async = false;
|
|
473
|
+
|
|
474
|
+
// Copy attributes
|
|
475
|
+
Array.from(oldScript.attributes).forEach(function(attr) {
|
|
476
|
+
if (attr.name !== 'src') {
|
|
477
|
+
newScript.setAttribute(attr.name, attr.value);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
oldScript.parentNode.replaceChild(newScript, oldScript);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// ============= Error Overlay =============
|
|
485
|
+
function showOverlay(options) {
|
|
486
|
+
// Remove existing overlay
|
|
487
|
+
hideOverlay();
|
|
488
|
+
|
|
489
|
+
var overlay = document.createElement('div');
|
|
490
|
+
overlay.id = '__hmr-overlay__';
|
|
491
|
+
overlay.style.cssText = [
|
|
492
|
+
'position: fixed',
|
|
493
|
+
'top: 0',
|
|
494
|
+
'left: 0',
|
|
495
|
+
'right: 0',
|
|
496
|
+
'bottom: 0',
|
|
497
|
+
'background: rgba(0, 0, 0, 0.85)',
|
|
498
|
+
'z-index: 99999',
|
|
499
|
+
'display: flex',
|
|
500
|
+
'align-items: center',
|
|
501
|
+
'justify-content: center',
|
|
502
|
+
'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
503
|
+
'color: white',
|
|
504
|
+
'padding: 20px'
|
|
505
|
+
].join(';');
|
|
506
|
+
|
|
507
|
+
var content = document.createElement('div');
|
|
508
|
+
content.style.cssText = [
|
|
509
|
+
'max-width: 800px',
|
|
510
|
+
'max-height: 80vh',
|
|
511
|
+
'overflow: auto',
|
|
512
|
+
'background: #1a1a1a',
|
|
513
|
+
'border-radius: 8px',
|
|
514
|
+
'padding: 20px',
|
|
515
|
+
'box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5)'
|
|
516
|
+
].join(';');
|
|
517
|
+
|
|
518
|
+
var title = document.createElement('h2');
|
|
519
|
+
title.style.cssText = 'color: #ff5555; margin: 0 0 15px 0; font-size: 18px;';
|
|
520
|
+
title.textContent = 'HMR Error';
|
|
521
|
+
|
|
522
|
+
var message = document.createElement('pre');
|
|
523
|
+
message.style.cssText = [
|
|
524
|
+
'background: #282828',
|
|
525
|
+
'padding: 15px',
|
|
526
|
+
'border-radius: 4px',
|
|
527
|
+
'overflow-x: auto',
|
|
528
|
+
'font-size: 13px',
|
|
529
|
+
'line-height: 1.5',
|
|
530
|
+
'white-space: pre-wrap',
|
|
531
|
+
'word-break: break-word'
|
|
532
|
+
].join(';');
|
|
533
|
+
message.textContent = options.message;
|
|
534
|
+
|
|
535
|
+
content.appendChild(title);
|
|
536
|
+
content.appendChild(message);
|
|
537
|
+
|
|
538
|
+
if (options.stack) {
|
|
539
|
+
var stack = document.createElement('pre');
|
|
540
|
+
stack.style.cssText = [
|
|
541
|
+
'background: #282828',
|
|
542
|
+
'padding: 15px',
|
|
543
|
+
'border-radius: 4px',
|
|
544
|
+
'margin-top: 10px',
|
|
545
|
+
'font-size: 12px',
|
|
546
|
+
'color: #888',
|
|
547
|
+
'overflow-x: auto'
|
|
548
|
+
].join(';');
|
|
549
|
+
stack.textContent = options.stack;
|
|
550
|
+
content.appendChild(stack);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (options.file) {
|
|
554
|
+
var file = document.createElement('div');
|
|
555
|
+
file.style.cssText = 'margin-top: 15px; color: #888; font-size: 12px;';
|
|
556
|
+
file.textContent = 'File: ' + options.file +
|
|
557
|
+
(options.line ? ':' + options.line + (options.column ? ':' + options.column : '') : '');
|
|
558
|
+
content.appendChild(file);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
var closeBtn = document.createElement('button');
|
|
562
|
+
closeBtn.style.cssText = [
|
|
563
|
+
'position: absolute',
|
|
564
|
+
'top: 10px',
|
|
565
|
+
'right: 10px',
|
|
566
|
+
'background: transparent',
|
|
567
|
+
'border: none',
|
|
568
|
+
'color: #888',
|
|
569
|
+
'font-size: 20px',
|
|
570
|
+
'cursor: pointer',
|
|
571
|
+
'padding: 5px'
|
|
572
|
+
].join(';');
|
|
573
|
+
closeBtn.textContent = '×';
|
|
574
|
+
closeBtn.onclick = hideOverlay;
|
|
575
|
+
|
|
576
|
+
overlay.style.position = 'relative';
|
|
577
|
+
overlay.appendChild(closeBtn);
|
|
578
|
+
overlay.appendChild(content);
|
|
579
|
+
|
|
580
|
+
document.body.appendChild(overlay);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function hideOverlay() {
|
|
584
|
+
var overlay = document.getElementById('__hmr-overlay__');
|
|
585
|
+
if (overlay) {
|
|
586
|
+
overlay.remove();
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ============= Subscription Management =============
|
|
591
|
+
function subscribe(fileId) {
|
|
592
|
+
subscribedFiles.add(fileId);
|
|
593
|
+
sendMessage({ type: 'subscribe', fileId: fileId });
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function unsubscribe(fileId) {
|
|
597
|
+
subscribedFiles.delete(fileId);
|
|
598
|
+
sendMessage({ type: 'unsubscribe', fileId: fileId });
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// ============= Initialization =============
|
|
602
|
+
function init() {
|
|
603
|
+
// Connect to HMR server
|
|
604
|
+
connect();
|
|
605
|
+
|
|
606
|
+
// Subscribe to current page
|
|
607
|
+
var currentFile = window.location.pathname;
|
|
608
|
+
subscribe(currentFile);
|
|
609
|
+
|
|
610
|
+
// Expose HMR API
|
|
611
|
+
window.__HMR__ = {
|
|
612
|
+
subscribe: subscribe,
|
|
613
|
+
unsubscribe: unsubscribe,
|
|
614
|
+
connect: connect,
|
|
615
|
+
clientId: function() { return clientId; },
|
|
616
|
+
framework: framework
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
console.log('[HMR] Client initialized (framework: ' + framework + ')');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Start when DOM is ready
|
|
623
|
+
if (document.readyState === 'complete') {
|
|
624
|
+
init();
|
|
625
|
+
} else {
|
|
626
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
627
|
+
}
|
|
628
|
+
})();
|
|
629
|
+
`;
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Get the HMR client script with optional configuration
|
|
633
|
+
*/
|
|
634
|
+
export function getHMRClientScript(options?: {
|
|
635
|
+
port?: number;
|
|
636
|
+
}): string {
|
|
637
|
+
if (options?.port) {
|
|
638
|
+
return HMR_CLIENT_SCRIPT.replace(
|
|
639
|
+
"return parseInt(window.location.port || '3000', 10) + 1;",
|
|
640
|
+
`return ${options.port};`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
return HMR_CLIENT_SCRIPT;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Inject HMR client script into HTML content
|
|
648
|
+
*/
|
|
649
|
+
export function injectHMRScript(html: string, port?: number): string {
|
|
650
|
+
const script = getHMRClientScript({ port });
|
|
651
|
+
|
|
652
|
+
// Find the </head> or </body> tag to inject before
|
|
653
|
+
const headMatch = html.match(/<\/head>/i);
|
|
654
|
+
const bodyMatch = html.match(/<\/body>/i);
|
|
655
|
+
|
|
656
|
+
const injectionPoint = headMatch ? headMatch.index! + headMatch[0].length :
|
|
657
|
+
bodyMatch ? bodyMatch.index! + bodyMatch[0].length :
|
|
658
|
+
html.length;
|
|
659
|
+
|
|
660
|
+
const scriptTag = `<script data-hmr-port="${port || ''}">${script}</script>`;
|
|
661
|
+
|
|
662
|
+
return html.slice(0, injectionPoint) + scriptTag + html.slice(injectionPoint);
|
|
663
|
+
}
|