@bouygues-telecom/staticjs 1.0.0 → 1.0.1

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,362 @@
1
+ /**
2
+ * Client-side WebSocket hot reload script
3
+ * Handles WebSocket connection and different reload strategies
4
+ */
5
+
6
+ (function() {
7
+ 'use strict';
8
+
9
+ // Only run in development mode
10
+ if (typeof window === 'undefined') {
11
+ return;
12
+ }
13
+
14
+ // Only run in development mode - check for development indicators
15
+ if (window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
16
+ return;
17
+ }
18
+
19
+ function HotReloadClient() {
20
+ this.ws = null;
21
+ this.reconnectAttempts = 0;
22
+ this.maxReconnectAttempts = 10;
23
+ this.reconnectDelay = 1000; // Start with 1 second
24
+ this.maxReconnectDelay = 30000; // Max 30 seconds
25
+ this.isConnected = false;
26
+ this.isReconnecting = false;
27
+ this.statusEl = null;
28
+
29
+ this.init();
30
+ }
31
+
32
+ HotReloadClient.prototype.init = function() {
33
+ this.createStatusIndicator();
34
+ this.connect();
35
+ this.setupVisibilityHandler();
36
+ };
37
+
38
+ HotReloadClient.prototype.createStatusIndicator = function() {
39
+ // Create a small status indicator
40
+ this.statusEl = document.createElement('div');
41
+ this.statusEl.id = 'hot-reload-status';
42
+ this.statusEl.style.cssText =
43
+ 'position: fixed;' +
44
+ 'top: 10px;' +
45
+ 'right: 10px;' +
46
+ 'width: 12px;' +
47
+ 'height: 12px;' +
48
+ 'border-radius: 50%;' +
49
+ 'background: #ff6b6b;' +
50
+ 'z-index: 10000;' +
51
+ 'transition: all 0.3s ease;' +
52
+ 'box-shadow: 0 2px 4px rgba(0,0,0,0.2);' +
53
+ 'opacity: 0.8;';
54
+ this.statusEl.title = 'Hot Reload: Disconnected';
55
+ document.body.appendChild(this.statusEl);
56
+ };
57
+
58
+ HotReloadClient.prototype.updateStatus = function(connected, message) {
59
+ message = message || '';
60
+ if (!this.statusEl) return;
61
+
62
+ this.isConnected = connected;
63
+ this.statusEl.style.background = connected ? '#51cf66' : '#ff6b6b';
64
+ this.statusEl.title = 'Hot Reload: ' + (connected ? 'Connected' : 'Disconnected') + (message ? ' - ' + message : '');
65
+ };
66
+
67
+ HotReloadClient.prototype.connect = function() {
68
+ var self = this;
69
+
70
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
71
+ return;
72
+ }
73
+
74
+ try {
75
+ var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
76
+ var wsUrl = protocol + '//' + window.location.host + '/ws';
77
+
78
+ console.log('[HotReload] Connecting to WebSocket:', wsUrl);
79
+ this.ws = new WebSocket(wsUrl);
80
+
81
+ this.ws.onopen = function() {
82
+ console.log('[HotReload] WebSocket connected');
83
+ self.updateStatus(true);
84
+ self.reconnectAttempts = 0;
85
+ self.reconnectDelay = 1000;
86
+ self.isReconnecting = false;
87
+ };
88
+
89
+ this.ws.onmessage = function(event) {
90
+ self.handleMessage(event);
91
+ };
92
+
93
+ this.ws.onclose = function(event) {
94
+ console.log('[HotReload] WebSocket disconnected:', event.code, event.reason);
95
+ self.updateStatus(false, 'Disconnected');
96
+ self.scheduleReconnect();
97
+ };
98
+
99
+ this.ws.onerror = function(error) {
100
+ console.error('[HotReload] WebSocket error:', error);
101
+ self.updateStatus(false, 'Error');
102
+ };
103
+
104
+ } catch (error) {
105
+ console.error('[HotReload] Failed to create WebSocket connection:', error);
106
+ this.updateStatus(false, 'Failed to connect');
107
+ this.scheduleReconnect();
108
+ }
109
+ };
110
+
111
+ HotReloadClient.prototype.handleMessage = function(event) {
112
+ try {
113
+ var message = JSON.parse(event.data);
114
+
115
+ switch (message.type) {
116
+ case 'connected':
117
+ console.log('[HotReload]', message.message);
118
+ break;
119
+
120
+ case 'reload':
121
+ this.handleReload(message);
122
+ break;
123
+
124
+ default:
125
+ console.log('[HotReload] Unknown message type:', message.type);
126
+ }
127
+ } catch (error) {
128
+ console.error('[HotReload] Error parsing WebSocket message:', error);
129
+ }
130
+ };
131
+
132
+ HotReloadClient.prototype.handleReload = function(message) {
133
+ var reloadType = message.reloadType;
134
+ var data = message.data;
135
+
136
+ console.log('[HotReload] Received ' + reloadType + ' reload request:', data);
137
+
138
+ // Show reload notification
139
+ this.showReloadNotification(reloadType, data);
140
+
141
+ // Execute reload strategy
142
+ switch (reloadType) {
143
+ case 'style':
144
+ this.reloadStyles();
145
+ break;
146
+
147
+ case 'asset':
148
+ this.reloadAssets();
149
+ break;
150
+
151
+ case 'page':
152
+ this.reloadPage();
153
+ break;
154
+
155
+ case 'full':
156
+ this.fullReload();
157
+ break;
158
+
159
+ default:
160
+ this.reloadPage();
161
+ }
162
+ };
163
+
164
+ HotReloadClient.prototype.showReloadNotification = function(type, data) {
165
+ // Create temporary notification
166
+ var notification = document.createElement('div');
167
+ notification.style.cssText =
168
+ 'position: fixed;' +
169
+ 'top: 50px;' +
170
+ 'right: 10px;' +
171
+ 'background: #4c6ef5;' +
172
+ 'color: white;' +
173
+ 'padding: 8px 12px;' +
174
+ 'border-radius: 4px;' +
175
+ 'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;' +
176
+ 'font-size: 12px;' +
177
+ 'z-index: 10001;' +
178
+ 'box-shadow: 0 2px 8px rgba(0,0,0,0.2);' +
179
+ 'opacity: 0;' +
180
+ 'transition: opacity 0.3s ease;';
181
+
182
+ var fileName = data.file ? data.file.split('/').pop() : 'file';
183
+ notification.textContent = 'Reloading ' + type + ': ' + fileName;
184
+
185
+ document.body.appendChild(notification);
186
+
187
+ // Animate in
188
+ setTimeout(function() {
189
+ notification.style.opacity = '1';
190
+ }, 10);
191
+
192
+ // Remove after delay
193
+ setTimeout(function() {
194
+ notification.style.opacity = '0';
195
+ setTimeout(function() {
196
+ if (notification.parentNode) {
197
+ notification.parentNode.removeChild(notification);
198
+ }
199
+ }, 300);
200
+ }, 2000);
201
+ };
202
+
203
+ HotReloadClient.prototype.reloadStyles = function() {
204
+ // Reload all CSS links
205
+ var links = document.querySelectorAll('link[rel="stylesheet"]');
206
+ for (var i = 0; i < links.length; i++) {
207
+ var link = links[i];
208
+ var href = link.href;
209
+ var url = new URL(href);
210
+ url.searchParams.set('t', Date.now().toString());
211
+ link.href = url.toString();
212
+ }
213
+
214
+ // Reload style tags (for CSS-in-JS)
215
+ var styles = document.querySelectorAll('style[data-vite-dev-id]');
216
+ for (var j = 0; j < styles.length; j++) {
217
+ var style = styles[j];
218
+ // Trigger re-evaluation by Vite
219
+ var event = new CustomEvent('vite:invalidate', {
220
+ detail: { path: style.getAttribute('data-vite-dev-id') }
221
+ });
222
+ window.dispatchEvent(event);
223
+ }
224
+ };
225
+
226
+ HotReloadClient.prototype.reloadAssets = function() {
227
+ // Reload images and other assets
228
+ var images = document.querySelectorAll('img');
229
+ for (var i = 0; i < images.length; i++) {
230
+ var img = images[i];
231
+ var src = img.src;
232
+ if (src && !src.startsWith('data:')) {
233
+ var url = new URL(src);
234
+ url.searchParams.set('t', Date.now().toString());
235
+ img.src = url.toString();
236
+ }
237
+ }
238
+ };
239
+
240
+ HotReloadClient.prototype.reloadPage = function() {
241
+ console.log('[HotReload] Reloading page...');
242
+
243
+ // Before reloading, try to preserve scroll position and form data
244
+ var scrollPosition = {
245
+ x: window.scrollX || window.pageXOffset,
246
+ y: window.scrollY || window.pageYOffset
247
+ };
248
+
249
+ // Store scroll position in sessionStorage for restoration after reload
250
+ try {
251
+ sessionStorage.setItem('hotReloadScrollPosition', JSON.stringify(scrollPosition));
252
+ } catch (e) {
253
+ // Ignore storage errors
254
+ }
255
+
256
+ // Perform the reload
257
+ window.location.reload();
258
+ };
259
+
260
+ HotReloadClient.prototype.fullReload = function() {
261
+ console.log('[HotReload] Performing full reload...');
262
+ // Clear cache and reload
263
+ if ('caches' in window) {
264
+ caches.keys().then(function(names) {
265
+ for (var i = 0; i < names.length; i++) {
266
+ caches.delete(names[i]);
267
+ }
268
+ window.location.reload();
269
+ });
270
+ } else {
271
+ window.location.reload();
272
+ }
273
+ };
274
+
275
+ HotReloadClient.prototype.scheduleReconnect = function() {
276
+ var self = this;
277
+
278
+ if (this.isReconnecting || this.reconnectAttempts >= this.maxReconnectAttempts) {
279
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
280
+ console.log('[HotReload] Max reconnection attempts reached');
281
+ this.updateStatus(false, 'Max retries reached');
282
+ }
283
+ return;
284
+ }
285
+
286
+ this.isReconnecting = true;
287
+ this.reconnectAttempts++;
288
+
289
+ console.log('[HotReload] Scheduling reconnection attempt ' + this.reconnectAttempts + ' in ' + this.reconnectDelay + 'ms');
290
+ this.updateStatus(false, 'Reconnecting... (' + this.reconnectAttempts + '/' + this.maxReconnectAttempts + ')');
291
+
292
+ setTimeout(function() {
293
+ self.connect();
294
+ }, this.reconnectDelay);
295
+
296
+ // Exponential backoff with jitter
297
+ this.reconnectDelay = Math.min(
298
+ this.reconnectDelay * 1.5 + Math.random() * 1000,
299
+ this.maxReconnectDelay
300
+ );
301
+ };
302
+
303
+ HotReloadClient.prototype.setupVisibilityHandler = function() {
304
+ var self = this;
305
+
306
+ // Reconnect when page becomes visible (handles browser sleep/wake)
307
+ document.addEventListener('visibilitychange', function() {
308
+ if (!document.hidden && !self.isConnected && !self.isReconnecting) {
309
+ console.log('[HotReload] Page became visible, attempting to reconnect...');
310
+ self.reconnectAttempts = 0;
311
+ self.reconnectDelay = 1000;
312
+ self.connect();
313
+ }
314
+ });
315
+ };
316
+
317
+ HotReloadClient.prototype.disconnect = function() {
318
+ if (this.ws) {
319
+ this.ws.close();
320
+ this.ws = null;
321
+ }
322
+
323
+ if (this.statusEl && this.statusEl.parentNode) {
324
+ this.statusEl.parentNode.removeChild(this.statusEl);
325
+ }
326
+ };
327
+
328
+ // Restore scroll position after hot reload
329
+ function restoreScrollPosition() {
330
+ try {
331
+ var stored = sessionStorage.getItem('hotReloadScrollPosition');
332
+ if (stored) {
333
+ var position = JSON.parse(stored);
334
+ window.scrollTo(position.x, position.y);
335
+ sessionStorage.removeItem('hotReloadScrollPosition');
336
+ }
337
+ } catch (e) {
338
+ // Ignore restoration errors
339
+ }
340
+ }
341
+
342
+ // Initialize hot reload client when DOM is ready
343
+ if (document.readyState === 'loading') {
344
+ document.addEventListener('DOMContentLoaded', function() {
345
+ window.hotReloadClient = new HotReloadClient();
346
+ // Restore scroll position after a short delay to ensure page is fully loaded
347
+ setTimeout(restoreScrollPosition, 100);
348
+ });
349
+ } else {
350
+ window.hotReloadClient = new HotReloadClient();
351
+ // Restore scroll position after a short delay to ensure page is fully loaded
352
+ setTimeout(restoreScrollPosition, 100);
353
+ }
354
+
355
+ // Cleanup on page unload
356
+ window.addEventListener('beforeunload', function() {
357
+ if (window.hotReloadClient) {
358
+ window.hotReloadClient.disconnect();
359
+ }
360
+ });
361
+
362
+ })();
@@ -4,7 +4,9 @@
4
4
  */
5
5
  import { createServer as createViteServer } from "vite";
6
6
  import { isDevelopment } from "../config/index.js";
7
- import { registerJavaScriptMiddleware } from "../middleware/runtime.js";
7
+ import { registerJavaScriptMiddleware, registerCSSMiddleware } from "../middleware/runtime.js";
8
+ import path from "path";
9
+ import { fileURLToPath } from "url";
8
10
  let viteServer = null;
9
11
  /**
10
12
  * Initialize Vite server for development mode
@@ -18,6 +20,10 @@ export const initializeViteServer = async (app) => {
18
20
  }
19
21
  try {
20
22
  // Initializing Vite server
23
+ // Get the absolute path to the vite config file within the package
24
+ const __filename = fileURLToPath(import.meta.url);
25
+ const __dirname = path.dirname(__filename);
26
+ const configPath = path.resolve(__dirname, '../config/vite.config.js');
21
27
  // Create Vite server for development mode JS compilation
22
28
  viteServer = await createViteServer({
23
29
  server: {
@@ -27,16 +33,31 @@ export const initializeViteServer = async (app) => {
27
33
  }
28
34
  },
29
35
  appType: 'custom',
30
- configFile: '../../lib/server/config/vite.config.ts'
36
+ configFile: configPath
31
37
  });
32
38
  // Vite server initialized
33
39
  // Add Vite's middleware to handle dependency requests
34
40
  app.use(viteServer.middlewares);
35
41
  // Register JavaScript serving middleware after Vite server is ready
36
42
  registerJavaScriptMiddleware(app, viteServer);
43
+ // Register CSS serving middleware after Vite server is ready
44
+ registerCSSMiddleware(app, viteServer);
37
45
  }
38
46
  catch (error) {
39
47
  console.error('❌ Failed to initialize Vite server:', error);
48
+ // Check for common Node.js version compatibility issues
49
+ if (error instanceof TypeError && error.message.includes('crypto.hash is not a function')) {
50
+ console.error('');
51
+ console.error('🔧 This error is likely due to a Node.js version compatibility issue.');
52
+ console.error(' The crypto.hash() method requires Node.js 15.0.0 or higher.');
53
+ console.error(' Current Node.js version:', process.version);
54
+ console.error('');
55
+ console.error('💡 Solutions:');
56
+ console.error(' 1. Update Node.js to version 18+ (recommended)');
57
+ console.error(' 2. Use a compatible Vite version (5.x instead of 6.x)');
58
+ console.error(' 3. Check your package.json engines field');
59
+ console.error('');
60
+ }
40
61
  }
41
62
  }
42
63
  return viteServer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bouygues-telecom/staticjs",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "main": "./_build/server/index.js",
6
6
  "exports": {
@@ -21,7 +21,7 @@
21
21
  "generate-test-multiapps": "./_build/scripts/generate-test-multiapps.js"
22
22
  },
23
23
  "scripts": {
24
- "build": "rm -rf _build && tsc && chmod +x _build/scripts/*.js",
24
+ "build": "rm -rf _build && tsc && chmod +x _build/scripts/*.js && cp -r server/static _build/server/",
25
25
  "dev": "npm run build && cd ../templates/react && npm run dev"
26
26
  },
27
27
  "dependencies": {
@@ -38,8 +38,15 @@
38
38
  "path": "^0.12.7",
39
39
  "readline": "^1.3.0",
40
40
  "rimraf": "^6.0.1",
41
+ "sass": "^1.77.0",
41
42
  "ws": "^8.18.0"
42
43
  },
44
+ "peerDependencies": {
45
+ "vite": "^5.0.0 || ^6.0.0"
46
+ },
47
+ "engines": {
48
+ "node": ">=18.0.0"
49
+ },
43
50
  "volta": {
44
51
  "node": "24.1.0"
45
52
  },