@bouygues-telecom/staticjs 0.1.11 → 1.0.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/_build/helpers/cachePages.d.ts +15 -0
- package/_build/helpers/cachePages.js +162 -0
- package/_build/helpers/createPage.d.ts +18 -0
- package/_build/helpers/createPage.js +41 -0
- package/_build/helpers/layoutDiscovery.d.ts +9 -0
- package/_build/helpers/layoutDiscovery.js +39 -0
- package/_build/helpers/readPages.d.ts +3 -0
- package/_build/helpers/readPages.js +24 -0
- package/_build/helpers/renderPageRuntime.d.ts +13 -0
- package/_build/helpers/renderPageRuntime.js +222 -0
- package/_build/scripts/build-html.d.ts +1 -0
- package/_build/scripts/build-html.js +138 -0
- package/_build/scripts/cli.d.ts +6 -0
- package/_build/scripts/cli.js +119 -0
- package/_build/scripts/create-static-app.d.ts +2 -0
- package/{dist/scripts/cli.js → _build/scripts/create-static-app.js} +2 -2
- package/_build/scripts/generate-test-multiapps.d.ts +6 -0
- package/_build/scripts/generate-test-multiapps.js +79 -0
- package/_build/server/config/index.d.ts +20 -0
- package/_build/server/config/index.js +25 -0
- package/_build/server/config/vite.config.d.ts +2 -0
- package/_build/server/config/vite.config.js +31 -0
- package/_build/server/config/vite.plugin.d.ts +11 -0
- package/_build/server/config/vite.plugin.js +75 -0
- package/_build/server/index.d.ts +22 -0
- package/_build/server/index.js +124 -0
- package/_build/server/middleware/errorHandling.d.ts +22 -0
- package/_build/server/middleware/errorHandling.js +48 -0
- package/_build/server/middleware/hotReload.d.ts +31 -0
- package/_build/server/middleware/hotReload.js +152 -0
- package/_build/server/middleware/logging.d.ts +13 -0
- package/_build/server/middleware/logging.js +23 -0
- package/_build/server/middleware/parsing.d.ts +9 -0
- package/_build/server/middleware/parsing.js +21 -0
- package/_build/server/middleware/performance.d.ts +13 -0
- package/_build/server/middleware/performance.js +26 -0
- package/_build/server/middleware/rateLimiting.d.ts +17 -0
- package/_build/server/middleware/rateLimiting.js +38 -0
- package/_build/server/middleware/runtime.d.ts +23 -0
- package/_build/server/middleware/runtime.js +186 -0
- package/_build/server/middleware/security.d.ts +24 -0
- package/_build/server/middleware/security.js +39 -0
- package/_build/server/middleware/static.d.ts +9 -0
- package/_build/server/middleware/static.js +63 -0
- package/_build/server/routes/api.d.ts +23 -0
- package/_build/server/routes/api.js +124 -0
- package/_build/server/scripts/revalidate.d.ts +2 -0
- package/{dist → _build/server}/scripts/revalidate.js +3 -3
- package/_build/server/utils/fileWatcher.d.ts +27 -0
- package/_build/server/utils/fileWatcher.js +194 -0
- package/_build/server/utils/startup.d.ts +18 -0
- package/_build/server/utils/startup.js +93 -0
- package/_build/server/utils/vite.d.ts +18 -0
- package/_build/server/utils/vite.js +61 -0
- package/_build/server/utils/websocket.d.ts +26 -0
- package/_build/server/utils/websocket.js +140 -0
- package/package.json +28 -16
- package/LICENSE +0 -190
- package/README.md +0 -158
- package/dist/config/vite.plugin.js +0 -80
- package/dist/helpers/cachePages.js +0 -26
- package/dist/helpers/createPage.js +0 -23
- package/dist/helpers/readPages.js +0 -19
- package/dist/scripts/build-html.js +0 -82
- package/dist/scripts/create-static-app.js +0 -36
- package/dist/scripts/generate-test-multiapps.js +0 -51
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File watcher system for hot reloading
|
|
3
|
+
* Watches source files and triggers WebSocket broadcasts on changes
|
|
4
|
+
*/
|
|
5
|
+
import chokidar from 'chokidar';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { broadcastReload } from './websocket.js';
|
|
8
|
+
import { isDevelopment } from '../config/index.js';
|
|
9
|
+
import { invalidateRuntimeCache } from '../middleware/runtime.js';
|
|
10
|
+
let watcher = null;
|
|
11
|
+
let debounceTimer = null;
|
|
12
|
+
const DEBOUNCE_DELAY = 300; // 300ms debounce
|
|
13
|
+
/**
|
|
14
|
+
* Determine reload type based on file extension
|
|
15
|
+
*/
|
|
16
|
+
const getReloadType = (filePath) => {
|
|
17
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
18
|
+
switch (ext) {
|
|
19
|
+
case '.css':
|
|
20
|
+
case '.scss':
|
|
21
|
+
case '.sass':
|
|
22
|
+
case '.less':
|
|
23
|
+
return 'style';
|
|
24
|
+
case '.tsx':
|
|
25
|
+
case '.jsx':
|
|
26
|
+
case '.ts':
|
|
27
|
+
case '.js':
|
|
28
|
+
return 'page';
|
|
29
|
+
case '.json':
|
|
30
|
+
case '.config.js':
|
|
31
|
+
case '.config.ts':
|
|
32
|
+
return 'full';
|
|
33
|
+
case '.png':
|
|
34
|
+
case '.jpg':
|
|
35
|
+
case '.jpeg':
|
|
36
|
+
case '.gif':
|
|
37
|
+
case '.svg':
|
|
38
|
+
case '.ico':
|
|
39
|
+
case '.woff':
|
|
40
|
+
case '.woff2':
|
|
41
|
+
case '.ttf':
|
|
42
|
+
case '.eot':
|
|
43
|
+
return 'asset';
|
|
44
|
+
default:
|
|
45
|
+
return 'page';
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Handle file change with debouncing
|
|
50
|
+
*/
|
|
51
|
+
const handleFileChange = (eventType, filePath) => {
|
|
52
|
+
// Clear existing timer
|
|
53
|
+
if (debounceTimer) {
|
|
54
|
+
clearTimeout(debounceTimer);
|
|
55
|
+
}
|
|
56
|
+
// Set new timer
|
|
57
|
+
debounceTimer = setTimeout(async () => {
|
|
58
|
+
const reloadType = getReloadType(filePath);
|
|
59
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
60
|
+
// Invalidate runtime cache when source files change
|
|
61
|
+
if (relativePath.includes('src/') || relativePath.includes('src\\')) {
|
|
62
|
+
await invalidateRuntimeCache();
|
|
63
|
+
}
|
|
64
|
+
// Broadcast reload message to WebSocket clients
|
|
65
|
+
broadcastReload(reloadType, {
|
|
66
|
+
file: relativePath,
|
|
67
|
+
event: eventType,
|
|
68
|
+
timestamp: Date.now()
|
|
69
|
+
});
|
|
70
|
+
debounceTimer = null;
|
|
71
|
+
}, DEBOUNCE_DELAY);
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Initialize file watcher
|
|
75
|
+
*/
|
|
76
|
+
export const initializeFileWatcher = () => {
|
|
77
|
+
if (!isDevelopment) {
|
|
78
|
+
// Skipping file watcher in production mode
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
// Define paths to watch based on current working directory
|
|
83
|
+
const cwd = process.cwd();
|
|
84
|
+
const watchPaths = [
|
|
85
|
+
'src', // Source directory
|
|
86
|
+
'src/**/*', // All source files in current directory
|
|
87
|
+
'package.json', // Dependency changes
|
|
88
|
+
'tsconfig.json', // TypeScript configuration
|
|
89
|
+
'eslint.config.js' // ESLint configuration
|
|
90
|
+
];
|
|
91
|
+
// If we're in the main project directory, also watch template directories
|
|
92
|
+
if (cwd.includes('static.js') && !cwd.includes('templates/')) {
|
|
93
|
+
watchPaths.push('templates/**/src/**/*', // Template source files
|
|
94
|
+
'templates/**/package.json', // Template dependencies
|
|
95
|
+
'templates/**/tsconfig.json' // Template TypeScript configs
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
// Create watcher with options
|
|
99
|
+
watcher = chokidar.watch(watchPaths, {
|
|
100
|
+
ignored: [
|
|
101
|
+
'**/node_modules/**',
|
|
102
|
+
'**/_build/**',
|
|
103
|
+
'**/node_modules/**',
|
|
104
|
+
'**/.git/**',
|
|
105
|
+
'**/coverage/**',
|
|
106
|
+
'**/*.log',
|
|
107
|
+
'**/.*'
|
|
108
|
+
],
|
|
109
|
+
ignoreInitial: true,
|
|
110
|
+
persistent: true,
|
|
111
|
+
followSymlinks: true,
|
|
112
|
+
depth: 10, // Allow deep directory watching
|
|
113
|
+
awaitWriteFinish: {
|
|
114
|
+
stabilityThreshold: 100,
|
|
115
|
+
pollInterval: 50
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// Set up event handlers
|
|
119
|
+
watcher
|
|
120
|
+
.on('add', (filePath) => handleFileChange('add', filePath))
|
|
121
|
+
.on('change', (filePath) => handleFileChange('change', filePath))
|
|
122
|
+
.on('unlink', (filePath) => handleFileChange('unlink', filePath))
|
|
123
|
+
.on('addDir', (dirPath) => {
|
|
124
|
+
// Directory added (silent)
|
|
125
|
+
})
|
|
126
|
+
.on('unlinkDir', (dirPath) => {
|
|
127
|
+
// Directory removed, triggering full reload
|
|
128
|
+
// Trigger full reload when directories are removed
|
|
129
|
+
broadcastReload('full', {
|
|
130
|
+
directory: path.relative(process.cwd(), dirPath),
|
|
131
|
+
event: 'unlinkDir'
|
|
132
|
+
});
|
|
133
|
+
})
|
|
134
|
+
.on('error', (err) => {
|
|
135
|
+
console.error('[FileWatcher] Watcher error:', err);
|
|
136
|
+
})
|
|
137
|
+
.on('ready', () => {
|
|
138
|
+
const watched = watcher.getWatched();
|
|
139
|
+
const watchedPaths = Object.keys(watched);
|
|
140
|
+
// File watcher initialized
|
|
141
|
+
});
|
|
142
|
+
return watcher;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error('[FileWatcher] Failed to initialize file watcher:', error);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Get current file watcher instance
|
|
151
|
+
*/
|
|
152
|
+
export const getFileWatcher = () => watcher;
|
|
153
|
+
/**
|
|
154
|
+
* Get watched files count
|
|
155
|
+
*/
|
|
156
|
+
export const getWatchedFilesCount = () => {
|
|
157
|
+
if (!watcher)
|
|
158
|
+
return 0;
|
|
159
|
+
const watched = watcher.getWatched();
|
|
160
|
+
return Object.values(watched).reduce((total, files) => total + files.length, 0);
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Close file watcher and cleanup
|
|
164
|
+
*/
|
|
165
|
+
export const closeFileWatcher = async () => {
|
|
166
|
+
if (!watcher) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
return new Promise((resolve) => {
|
|
170
|
+
// Closing file watcher
|
|
171
|
+
// Clear debounce timer
|
|
172
|
+
if (debounceTimer) {
|
|
173
|
+
clearTimeout(debounceTimer);
|
|
174
|
+
debounceTimer = null;
|
|
175
|
+
}
|
|
176
|
+
// Close watcher
|
|
177
|
+
watcher.close().then(() => {
|
|
178
|
+
// File watcher closed
|
|
179
|
+
watcher = null;
|
|
180
|
+
resolve();
|
|
181
|
+
}).catch((error) => {
|
|
182
|
+
console.error('[FileWatcher] Error closing file watcher:', error);
|
|
183
|
+
watcher = null;
|
|
184
|
+
resolve();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Manually trigger a reload (useful for testing)
|
|
190
|
+
*/
|
|
191
|
+
export const triggerReload = (type = 'page', data = {}) => {
|
|
192
|
+
// Manually triggering reload
|
|
193
|
+
broadcastReload(type, { ...data, manual: true });
|
|
194
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server startup and shutdown utilities
|
|
3
|
+
* Handles server initialization and graceful shutdown
|
|
4
|
+
*/
|
|
5
|
+
import { Server } from 'http';
|
|
6
|
+
import { Express } from 'express';
|
|
7
|
+
/**
|
|
8
|
+
* Start the server with proper error handling
|
|
9
|
+
*/
|
|
10
|
+
export declare const startServer: (app: Express, initializeViteServer?: (app: Express) => Promise<any>) => Promise<Server>;
|
|
11
|
+
/**
|
|
12
|
+
* Graceful shutdown handling
|
|
13
|
+
*/
|
|
14
|
+
export declare const gracefulShutdown: (server: Server, signal: string) => Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Setup process event handlers for graceful shutdown
|
|
17
|
+
*/
|
|
18
|
+
export declare const setupProcessHandlers: (server: Server) => void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server startup and shutdown utilities
|
|
3
|
+
* Handles server initialization and graceful shutdown
|
|
4
|
+
*/
|
|
5
|
+
import { CONFIG } from "../config/index.js";
|
|
6
|
+
import { closeViteServer, getViteServer } from "./vite.js";
|
|
7
|
+
import { closeWebSocketServer, getConnectedClientsCount } from "./websocket.js";
|
|
8
|
+
import { closeFileWatcher, getWatchedFilesCount } from "./fileWatcher.js";
|
|
9
|
+
/**
|
|
10
|
+
* Start the server with proper error handling
|
|
11
|
+
*/
|
|
12
|
+
export const startServer = async (app, initializeViteServer) => {
|
|
13
|
+
// Initialize Vite server first (if provided)
|
|
14
|
+
if (initializeViteServer) {
|
|
15
|
+
await initializeViteServer(app);
|
|
16
|
+
}
|
|
17
|
+
return app.listen(CONFIG.PORT, () => {
|
|
18
|
+
const viteServer = getViteServer();
|
|
19
|
+
const wsClients = getConnectedClientsCount();
|
|
20
|
+
const watchedFiles = getWatchedFilesCount();
|
|
21
|
+
console.log(`
|
|
22
|
+
🚀 StaticJS Server Started
|
|
23
|
+
=====================================
|
|
24
|
+
Environment: ${CONFIG.NODE_ENV}
|
|
25
|
+
Port: ${CONFIG.PORT}
|
|
26
|
+
URL: http://localhost:${CONFIG.PORT}
|
|
27
|
+
Health Check: http://localhost:${CONFIG.PORT}/health
|
|
28
|
+
Pages API: http://localhost:${CONFIG.PORT}/api/pages
|
|
29
|
+
${viteServer ? 'Vite JS Compilation: ✅ Enabled' : 'Vite JS Compilation: ❌ Disabled'}
|
|
30
|
+
${CONFIG.WEBSOCKET_ENABLED ? `WebSocket Hot Reload: ✅ Enabled (${wsClients} clients)` : 'WebSocket Hot Reload: ❌ Disabled'}
|
|
31
|
+
${CONFIG.FILE_WATCHING_ENABLED ? `File Watching: ✅ Enabled (${watchedFiles} files)` : 'File Watching: ❌ Disabled'}
|
|
32
|
+
=====================================
|
|
33
|
+
`);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Graceful shutdown handling
|
|
38
|
+
*/
|
|
39
|
+
export const gracefulShutdown = async (server, signal) => {
|
|
40
|
+
console.log(`\n📡 Received ${signal}. Starting graceful shutdown...`);
|
|
41
|
+
try {
|
|
42
|
+
// Close file watcher first
|
|
43
|
+
if (CONFIG.FILE_WATCHING_ENABLED) {
|
|
44
|
+
console.log('🔍 Closing file watcher...');
|
|
45
|
+
await closeFileWatcher();
|
|
46
|
+
}
|
|
47
|
+
// Close WebSocket server
|
|
48
|
+
if (CONFIG.WEBSOCKET_ENABLED) {
|
|
49
|
+
console.log('🔌 Closing WebSocket server...');
|
|
50
|
+
await closeWebSocketServer();
|
|
51
|
+
}
|
|
52
|
+
// Close Vite server if it exists
|
|
53
|
+
console.log('⚡ Closing Vite server...');
|
|
54
|
+
await closeViteServer();
|
|
55
|
+
// Close HTTP server
|
|
56
|
+
console.log('🌐 Closing HTTP server...');
|
|
57
|
+
server.close((err) => {
|
|
58
|
+
if (err) {
|
|
59
|
+
console.error('❌ Error during server shutdown:', err);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
console.log('✅ Server closed successfully');
|
|
63
|
+
process.exit(0);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error('❌ Error during graceful shutdown:', error);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
// Force shutdown after 10 seconds
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
console.error('⚠️ Forced shutdown after timeout');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}, 10000);
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Setup process event handlers for graceful shutdown
|
|
78
|
+
*/
|
|
79
|
+
export const setupProcessHandlers = (server) => {
|
|
80
|
+
// Handle shutdown signals
|
|
81
|
+
process.on('SIGTERM', () => gracefulShutdown(server, 'SIGTERM'));
|
|
82
|
+
process.on('SIGINT', () => gracefulShutdown(server, 'SIGINT'));
|
|
83
|
+
// Handle uncaught exceptions
|
|
84
|
+
process.on('uncaughtException', (err) => {
|
|
85
|
+
console.error('💥 Uncaught Exception:', err);
|
|
86
|
+
gracefulShutdown(server, 'uncaughtException');
|
|
87
|
+
});
|
|
88
|
+
// Handle unhandled promise rejections
|
|
89
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
90
|
+
console.error('💥 Unhandled Rejection at:', promise, 'reason:', reason);
|
|
91
|
+
gracefulShutdown(server, 'unhandledRejection');
|
|
92
|
+
});
|
|
93
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite server utilities
|
|
3
|
+
* Handles Vite server initialization and JavaScript serving for development mode
|
|
4
|
+
*/
|
|
5
|
+
import { ViteDevServer } from "vite";
|
|
6
|
+
import { Express } from "express";
|
|
7
|
+
/**
|
|
8
|
+
* Initialize Vite server for development mode
|
|
9
|
+
*/
|
|
10
|
+
export declare const initializeViteServer: (app: Express) => Promise<ViteDevServer | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Get the current Vite server instance
|
|
13
|
+
*/
|
|
14
|
+
export declare const getViteServer: () => ViteDevServer | null;
|
|
15
|
+
/**
|
|
16
|
+
* Close the Vite server
|
|
17
|
+
*/
|
|
18
|
+
export declare const closeViteServer: () => Promise<void>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite server utilities
|
|
3
|
+
* Handles Vite server initialization and JavaScript serving for development mode
|
|
4
|
+
*/
|
|
5
|
+
import { createServer as createViteServer } from "vite";
|
|
6
|
+
import { isDevelopment } from "../config/index.js";
|
|
7
|
+
import { registerJavaScriptMiddleware } from "../middleware/runtime.js";
|
|
8
|
+
let viteServer = null;
|
|
9
|
+
/**
|
|
10
|
+
* Initialize Vite server for development mode
|
|
11
|
+
*/
|
|
12
|
+
export const initializeViteServer = async (app) => {
|
|
13
|
+
if (isDevelopment) {
|
|
14
|
+
// Check if Vite server is already initialized
|
|
15
|
+
if (viteServer) {
|
|
16
|
+
// Vite server already initialized
|
|
17
|
+
return viteServer;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
// Initializing Vite server
|
|
21
|
+
// Create Vite server for development mode JS compilation
|
|
22
|
+
viteServer = await createViteServer({
|
|
23
|
+
server: {
|
|
24
|
+
middlewareMode: true,
|
|
25
|
+
hmr: {
|
|
26
|
+
port: 24679 // Use different port to avoid conflicts
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
appType: 'custom',
|
|
30
|
+
configFile: '../../lib/server/config/vite.config.ts'
|
|
31
|
+
});
|
|
32
|
+
// Vite server initialized
|
|
33
|
+
// Add Vite's middleware to handle dependency requests
|
|
34
|
+
app.use(viteServer.middlewares);
|
|
35
|
+
// Register JavaScript serving middleware after Vite server is ready
|
|
36
|
+
registerJavaScriptMiddleware(app, viteServer);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error('❌ Failed to initialize Vite server:', error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return viteServer;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Get the current Vite server instance
|
|
46
|
+
*/
|
|
47
|
+
export const getViteServer = () => viteServer;
|
|
48
|
+
/**
|
|
49
|
+
* Close the Vite server
|
|
50
|
+
*/
|
|
51
|
+
export const closeViteServer = async () => {
|
|
52
|
+
if (viteServer) {
|
|
53
|
+
try {
|
|
54
|
+
await viteServer.close();
|
|
55
|
+
// Vite server closed
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error('❌ Error closing Vite server:', error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket server utilities for hot reloading
|
|
3
|
+
* Handles WebSocket connections and broadcasting reload messages
|
|
4
|
+
*/
|
|
5
|
+
import { WebSocketServer } from 'ws';
|
|
6
|
+
import { Server as HttpServer } from 'http';
|
|
7
|
+
/**
|
|
8
|
+
* Initialize WebSocket server attached to HTTP server
|
|
9
|
+
*/
|
|
10
|
+
export declare const initializeWebSocketServer: (httpServer: HttpServer) => WebSocketServer | null;
|
|
11
|
+
/**
|
|
12
|
+
* Broadcast reload message to all connected clients
|
|
13
|
+
*/
|
|
14
|
+
export declare const broadcastReload: (type?: string, data?: Record<string, any>) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Get current WebSocket server instance
|
|
17
|
+
*/
|
|
18
|
+
export declare const getWebSocketServer: () => WebSocketServer | null;
|
|
19
|
+
/**
|
|
20
|
+
* Get number of connected clients
|
|
21
|
+
*/
|
|
22
|
+
export declare const getConnectedClientsCount: () => number;
|
|
23
|
+
/**
|
|
24
|
+
* Close WebSocket server and cleanup
|
|
25
|
+
*/
|
|
26
|
+
export declare const closeWebSocketServer: () => Promise<void>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket server utilities for hot reloading
|
|
3
|
+
* Handles WebSocket connections and broadcasting reload messages
|
|
4
|
+
*/
|
|
5
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
6
|
+
import { isDevelopment } from '../config/index.js';
|
|
7
|
+
let wss = null;
|
|
8
|
+
let clients = new Set();
|
|
9
|
+
/**
|
|
10
|
+
* Initialize WebSocket server attached to HTTP server
|
|
11
|
+
*/
|
|
12
|
+
export const initializeWebSocketServer = (httpServer) => {
|
|
13
|
+
if (!isDevelopment) {
|
|
14
|
+
// Skipping WebSocket server in production mode
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
// Create WebSocket server attached to HTTP server
|
|
19
|
+
wss = new WebSocketServer({
|
|
20
|
+
server: httpServer,
|
|
21
|
+
path: '/ws'
|
|
22
|
+
});
|
|
23
|
+
wss.on('connection', (ws, req) => {
|
|
24
|
+
// Client connected
|
|
25
|
+
clients.add(ws);
|
|
26
|
+
// Send welcome message
|
|
27
|
+
ws.send(JSON.stringify({
|
|
28
|
+
type: 'connected',
|
|
29
|
+
message: 'Hot reload client connected'
|
|
30
|
+
}));
|
|
31
|
+
// Handle client disconnect
|
|
32
|
+
ws.on('close', () => {
|
|
33
|
+
// Client disconnected
|
|
34
|
+
clients.delete(ws);
|
|
35
|
+
});
|
|
36
|
+
// Handle client errors
|
|
37
|
+
ws.on('error', (error) => {
|
|
38
|
+
console.error('[WebSocket] Client error:', error);
|
|
39
|
+
clients.delete(ws);
|
|
40
|
+
});
|
|
41
|
+
// Handle ping/pong for connection health
|
|
42
|
+
ws.on('pong', () => {
|
|
43
|
+
ws.isAlive = true;
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
// Setup ping interval to detect dead connections
|
|
47
|
+
const pingInterval = setInterval(() => {
|
|
48
|
+
if (wss && wss.clients) {
|
|
49
|
+
wss.clients.forEach((ws) => {
|
|
50
|
+
if (ws.isAlive === false) {
|
|
51
|
+
clients.delete(ws);
|
|
52
|
+
return ws.terminate();
|
|
53
|
+
}
|
|
54
|
+
ws.isAlive = false;
|
|
55
|
+
ws.ping();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}, 30000); // 30 seconds
|
|
59
|
+
wss.on('close', () => {
|
|
60
|
+
clearInterval(pingInterval);
|
|
61
|
+
});
|
|
62
|
+
// WebSocket server initialized
|
|
63
|
+
return wss;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error('[WebSocket] Failed to initialize WebSocket server:', error);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Broadcast reload message to all connected clients
|
|
72
|
+
*/
|
|
73
|
+
export const broadcastReload = (type = 'page', data = {}) => {
|
|
74
|
+
if (!isDevelopment) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (!wss) {
|
|
78
|
+
// WebSocket server not initialized
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const message = {
|
|
82
|
+
type: 'reload',
|
|
83
|
+
reloadType: type,
|
|
84
|
+
timestamp: Date.now(),
|
|
85
|
+
data
|
|
86
|
+
};
|
|
87
|
+
const messageString = JSON.stringify(message);
|
|
88
|
+
let sentCount = 0;
|
|
89
|
+
let deadConnections = 0;
|
|
90
|
+
clients.forEach((ws) => {
|
|
91
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
92
|
+
try {
|
|
93
|
+
ws.send(messageString);
|
|
94
|
+
sentCount++;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error('[WebSocket] Error sending message to client:', error);
|
|
98
|
+
clients.delete(ws);
|
|
99
|
+
deadConnections++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
clients.delete(ws);
|
|
104
|
+
deadConnections++;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// Reload broadcast completed (silent)
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Get current WebSocket server instance
|
|
111
|
+
*/
|
|
112
|
+
export const getWebSocketServer = () => wss;
|
|
113
|
+
/**
|
|
114
|
+
* Get number of connected clients
|
|
115
|
+
*/
|
|
116
|
+
export const getConnectedClientsCount = () => clients.size;
|
|
117
|
+
/**
|
|
118
|
+
* Close WebSocket server and cleanup
|
|
119
|
+
*/
|
|
120
|
+
export const closeWebSocketServer = async () => {
|
|
121
|
+
if (!wss) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
// Closing WebSocket server
|
|
126
|
+
// Close all client connections
|
|
127
|
+
clients.forEach((ws) => {
|
|
128
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
129
|
+
ws.close(1000, 'Server shutting down');
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
clients.clear();
|
|
133
|
+
// Close the server
|
|
134
|
+
wss.close(() => {
|
|
135
|
+
// WebSocket server closed
|
|
136
|
+
wss = null;
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
};
|
package/package.json
CHANGED
|
@@ -1,43 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bouygues-telecom/staticjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"main": "./_build/server/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./_build/server/index.js",
|
|
8
|
+
"./server": "./_build/server/index.js",
|
|
9
|
+
"./config": "./_build/server/config/index.js",
|
|
10
|
+
"./helpers": "./_build/helpers/cachePages.js"
|
|
11
|
+
},
|
|
5
12
|
"files": [
|
|
6
|
-
"
|
|
13
|
+
"_build",
|
|
7
14
|
"package.json",
|
|
8
15
|
"README.md"
|
|
9
16
|
],
|
|
10
17
|
"bin": {
|
|
11
|
-
"create-staticjs-app": "./
|
|
12
|
-
"bt-staticjs": "./
|
|
13
|
-
"
|
|
18
|
+
"create-staticjs-app": "./_build/scripts/create-static-app.js",
|
|
19
|
+
"bt-staticjs": "./_build/scripts/cli.js",
|
|
20
|
+
"static": "./_build/scripts/cli.js",
|
|
21
|
+
"generate-test-multiapps": "./_build/scripts/generate-test-multiapps.js"
|
|
14
22
|
},
|
|
15
23
|
"scripts": {
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"generate:test": "cd templates/react && node ../../scripts/generate-test-multiapps.js",
|
|
19
|
-
"start": "cd templates/react && npm start"
|
|
24
|
+
"build": "rm -rf _build && tsc && chmod +x _build/scripts/*.js",
|
|
25
|
+
"dev": "npm run build && cd ../templates/react && npm run dev"
|
|
20
26
|
},
|
|
21
27
|
"dependencies": {
|
|
22
|
-
"
|
|
28
|
+
"chokidar": "^4.0.1",
|
|
23
29
|
"cli-spinner": "^0.2.10",
|
|
24
30
|
"commander": "^14.0.0",
|
|
31
|
+
"compression": "^1.7.5",
|
|
32
|
+
"cors": "^2.8.5",
|
|
33
|
+
"express": "^5.0.1",
|
|
34
|
+
"express-rate-limit": "^8.0.1",
|
|
25
35
|
"giget": "^2.0.0",
|
|
36
|
+
"helmet": "^8.0.0",
|
|
26
37
|
"nodemon": "^3.1.10",
|
|
27
38
|
"path": "^0.12.7",
|
|
28
39
|
"readline": "^1.3.0",
|
|
29
40
|
"rimraf": "^6.0.1",
|
|
30
|
-
"
|
|
31
|
-
"typescript": "^5"
|
|
41
|
+
"ws": "^8.18.0"
|
|
32
42
|
},
|
|
33
43
|
"volta": {
|
|
34
44
|
"node": "24.1.0"
|
|
35
45
|
},
|
|
36
|
-
"workspaces": [
|
|
37
|
-
"templates/react"
|
|
38
|
-
],
|
|
39
46
|
"devDependencies": {
|
|
47
|
+
"@types/chokidar": "^2.1.7",
|
|
40
48
|
"@types/cli-spinner": "^0.2.3",
|
|
41
|
-
"@types/
|
|
49
|
+
"@types/compression": "^1.8.1",
|
|
50
|
+
"@types/cors": "^2.8.19",
|
|
51
|
+
"@types/express": "^5.0.3",
|
|
52
|
+
"@types/helmet": "^4.0.0",
|
|
53
|
+
"@types/ws": "^8.18.1"
|
|
42
54
|
}
|
|
43
55
|
}
|