visualifyjs 2.5.3-2.dev → 3.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.
Files changed (252) hide show
  1. package/.claude/mem/TIMELINE.md +36 -0
  2. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
  3. package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
  4. package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
  5. package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
  6. package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
  7. package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
  8. package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
  9. package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
  10. package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
  11. package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
  12. package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
  13. package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
  14. package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
  15. package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
  16. package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
  17. package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
  18. package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
  19. package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
  20. package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
  21. package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
  22. package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
  23. package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
  24. package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
  25. package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
  26. package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
  27. package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
  28. package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
  29. package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
  30. package/.claude/settings.local.json +21 -0
  31. package/.github/workflows/static.yml.bak +51 -51
  32. package/.sisyphus/boulder.json +65 -0
  33. package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
  34. package/LICENSE +674 -674
  35. package/README.md +94 -59
  36. package/config-overrides.js +31 -31
  37. package/dist/stats.html +4949 -0
  38. package/dist/visualify-3d.esm.js +1 -0
  39. package/dist/visualify-3d.js +1 -0
  40. package/dist/visualify-core.esm.js +1 -0
  41. package/dist/visualify-core.js +1 -0
  42. package/dist/visualify-docs.esm.js +1 -0
  43. package/dist/visualify-docs.js +1 -0
  44. package/dist/visualify-loader.js +1 -0
  45. package/dist/visualify-pages.esm.js +1 -0
  46. package/dist/visualify-pages.js +1 -0
  47. package/dist/visualify-portal.esm.js +1 -0
  48. package/dist/visualify-portal.js +1 -0
  49. package/dist/visualify-shared.js +26571 -0
  50. package/dist/visualify.js +1 -188
  51. package/docs/CHANGELOG.md +148 -0
  52. package/docs/cli/commands.md +513 -0
  53. package/docs/configuration/visualify-json.md +474 -0
  54. package/docs/docs/3d-visualization.md +374 -0
  55. package/docs/docs/CLI.md +303 -34
  56. package/docs/docs/README.md +65 -65
  57. package/docs/docs/Rechart/bar.md +190 -190
  58. package/docs/docs/Rechart/funnel.md +241 -241
  59. package/docs/docs/Rechart/line.md +355 -355
  60. package/docs/docs/Rechart/pie.md +225 -225
  61. package/docs/docs/Rechart/radar.md +253 -253
  62. package/docs/docs/Rechart/scatter.md +298 -298
  63. package/docs/docs/_404.md +51 -51
  64. package/docs/docs/_coverpage.md +11 -11
  65. package/docs/docs/_sidebar.md +54 -44
  66. package/docs/docs/components/dotBio.md +87 -34
  67. package/docs/docs/components/echart.md +171 -82
  68. package/docs/docs/components/html.md +61 -34
  69. package/docs/docs/components/macaron.md +156 -145
  70. package/docs/docs/components/markdown.md +42 -0
  71. package/docs/docs/components/more.md +183 -142
  72. package/docs/docs/components/plotly.md +132 -62
  73. package/docs/docs/components/scatterL.md +171 -70
  74. package/docs/docs/components/visium.md +112 -57
  75. package/docs/docs/configuration.md +121 -121
  76. package/docs/docs/deploy.md +31 -31
  77. package/docs/docs/docsify-plugin.md +655 -0
  78. package/docs/docs/hmr.md +165 -0
  79. package/docs/docs/i18n.md +332 -0
  80. package/docs/docs/log.md +30 -9
  81. package/docs/docs/more-pages.md +23 -23
  82. package/docs/docs/quickstart.md +148 -124
  83. package/docs/docs/rechart-attributes.md +74 -74
  84. package/docs/docs/rechart-basic-usage.md +160 -162
  85. package/docs/docs/theme.md +5 -5
  86. package/docs/docs/typescript.md +306 -0
  87. package/docs/docs/visual-editor.md +359 -0
  88. package/docs/index.html +85 -71
  89. package/docs/manifest.json +23 -23
  90. package/docs/migration/v3-migration.md +392 -0
  91. package/docs/static/css/fluff-stuff.css +169 -169
  92. package/docs/static/css/font-awesome.min.css +4 -4
  93. package/docs/static/css/visualify.css +6 -25
  94. package/docs/static/js/3d-viz-examples.js +181 -0
  95. package/docs/static/js/configuration.js +630 -448
  96. package/docs/static/js/visualify.js +1 -188
  97. package/package.json +106 -84
  98. package/rollup.config.mjs +766 -76
  99. package/src/_css/404.css +115 -115
  100. package/src/_css/App.css +37 -37
  101. package/src/_css/autoSuggestion.css +26 -26
  102. package/src/_css/circular-progress.css +32 -32
  103. package/src/_css/index.css +36 -36
  104. package/src/_css/modern.css +350 -25
  105. package/src/_media/corner.svg +8 -8
  106. package/src/_media/download.svg +3 -3
  107. package/src/_media/logo.svg +14 -14
  108. package/src/_test/App.test.js +15 -15
  109. package/src/_utils/reportWebVitals.js +13 -13
  110. package/src/a11y/README.md +177 -0
  111. package/src/a11y/aria-labels.js +339 -0
  112. package/src/a11y/color-contrast.js +535 -0
  113. package/src/a11y/index.js +197 -0
  114. package/src/a11y/keyboard-nav.js +523 -0
  115. package/src/a11y/styles.css +165 -0
  116. package/src/cli/commands/dev.js +214 -0
  117. package/src/cli/commands/docs.js +521 -0
  118. package/src/cli/commands/edit.js +379 -0
  119. package/src/cli/commands/init.js +213 -0
  120. package/src/cli/commands/portal.js +236 -0
  121. package/src/cli/dev-server.js +530 -0
  122. package/src/cli/hmr.js +456 -0
  123. package/src/cli/index.js +180 -0
  124. package/src/cli/utils/config.js +207 -0
  125. package/src/cli/utils/logger.js +241 -0
  126. package/src/config/defaults.ts +122 -0
  127. package/src/config/index.ts +72 -0
  128. package/src/config/loader.ts +478 -0
  129. package/src/config/schema.ts +227 -0
  130. package/src/config/validator.ts +337 -0
  131. package/src/core/appContext.js +34 -27
  132. package/src/core/components/Bar.js +383 -0
  133. package/src/core/components/Bar3D.js +473 -0
  134. package/src/core/components/LargeDatasetChart.js +296 -0
  135. package/src/core/components/Line3D.js +310 -0
  136. package/src/core/components/Scatter.js +392 -188
  137. package/src/core/components/Scatter3D.js +455 -0
  138. package/src/core/components/ScatterBio.js +601 -572
  139. package/src/core/components/Surface3D.js +326 -0
  140. package/src/core/components/ThreeCustom.js +648 -0
  141. package/src/core/components/ThreeScene.js +459 -0
  142. package/src/core/components/VisiumPlot.js +191 -165
  143. package/src/core/components/browser.js +42 -42
  144. package/src/core/components/dotplot.js +413 -413
  145. package/src/core/components/html.js +29 -29
  146. package/src/core/components/list.js +178 -178
  147. package/src/core/components/macaron.js +206 -201
  148. package/src/core/components/markdown.js +56 -56
  149. package/src/core/components/parser.scatterBio.js +582 -587
  150. package/src/core/components/ratio.js +82 -80
  151. package/src/core/components/scatterL.js +206 -173
  152. package/src/core/components/searchbar.js +156 -131
  153. package/src/core/components/selection.js +310 -193
  154. package/src/core/components/timeline.js +236 -281
  155. package/src/core/components/visium.js +114 -97
  156. package/src/core/data-processor.js +413 -0
  157. package/src/core/fetch/condfetch.js +82 -82
  158. package/src/core/fetch/fetch.js +92 -92
  159. package/src/core/fetch/json.js +29 -29
  160. package/src/core/fetch/vfetch.js +42 -42
  161. package/src/core/hmr-client.js +724 -0
  162. package/src/core/liveEditor.js +44 -44
  163. package/src/core/modules/codeEditorWithPreview.js +104 -104
  164. package/src/core/modules/echarts/common.js +20 -20
  165. package/src/core/modules/echarts/gl.js +228 -0
  166. package/src/core/modules/echarts/presetHandler.js +41 -41
  167. package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
  168. package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
  169. package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
  170. package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
  171. package/src/core/modules/echarts.js +70 -71
  172. package/src/core/modules/echartsUtils.js +43 -43
  173. package/src/core/modules/echartswitcher.js +227 -152
  174. package/src/core/modules/replotly/presetHandler.js +24 -24
  175. package/src/core/modules/replotly/presets/minimum.js +18 -18
  176. package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
  177. package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
  178. package/src/core/modules/replotly.js +74 -71
  179. package/src/core/modules/threejs/Camera.js +373 -0
  180. package/src/core/modules/threejs/Lighting.js +459 -0
  181. package/src/core/modules/threejs/Renderer.js +364 -0
  182. package/src/core/modules/threejs/Scene.js +266 -0
  183. package/src/core/modules/threejs/index.js +155 -0
  184. package/src/core/pages/404.js +50 -50
  185. package/src/core/pages/error.js +27 -27
  186. package/src/core/pages/jsonPage.js +62 -62
  187. package/src/core/pages/loading.js +44 -44
  188. package/src/core/parser/echart.data.js +204 -183
  189. package/src/core/parser/echart.features.js +125 -125
  190. package/src/core/parser/echart.general.js +147 -147
  191. package/src/core/parser/echart.hilbert.js +57 -57
  192. package/src/core/parser/echart.parser.js +210 -210
  193. package/src/core/parser/echart.series.js +67 -67
  194. package/src/core/parser/echart.types.js +76 -76
  195. package/src/core/parser/plotly.config.js +10 -10
  196. package/src/core/parser/plotly.data.js +132 -132
  197. package/src/core/parser/plotly.layout.js +9 -9
  198. package/src/core/parser/plotly.violin.js +18 -18
  199. package/src/core/recharts.js +361 -62
  200. package/src/core/router/alias.js +49 -49
  201. package/src/core/router/jsonRouter.js +31 -31
  202. package/src/core/themes/modern.js +32 -32
  203. package/src/core/themes/themeSelector.js +33 -33
  204. package/src/core/visualify.js +213 -47
  205. package/src/core/widgets/circularProgress.js +23 -23
  206. package/src/core/widgets/controller.js +116 -83
  207. package/src/core/widgets/errorBoundary.js +36 -36
  208. package/src/core/widgets/footer.js +185 -177
  209. package/src/core/widgets/header.js +238 -234
  210. package/src/core/widgets/layout/Grid.js +31 -31
  211. package/src/core/widgets/layout.js +36 -36
  212. package/src/core/widgets/mapping.js +56 -42
  213. package/src/core/workers/data-worker.js +349 -0
  214. package/src/core/workers/worker-pool.js +396 -0
  215. package/src/docsify/bundle.js +215 -0
  216. package/src/docsify/markdown.js +271 -0
  217. package/src/docsify/plugin.js +268 -0
  218. package/src/editor/README.md +172 -0
  219. package/src/editor/components/ChartBuilder.jsx +341 -0
  220. package/src/editor/components/ChartTypeSidebar.jsx +91 -0
  221. package/src/editor/components/Editor.jsx +367 -0
  222. package/src/editor/components/Preview.jsx +446 -0
  223. package/src/editor/components/PropertyPanel.jsx +468 -0
  224. package/src/editor/components/StatusBar.jsx +85 -0
  225. package/src/editor/context/EditorContext.js +248 -0
  226. package/src/editor/hooks/useDebounce.js +32 -0
  227. package/src/editor/index.js +315 -0
  228. package/src/editor/styles/editor.css +637 -0
  229. package/src/editor/utils/chartValidator.js +263 -0
  230. package/src/entries/charts3d.js +70 -0
  231. package/src/entries/core.js +78 -0
  232. package/src/entries/docs.js +154 -0
  233. package/src/entries/pages.js +93 -0
  234. package/src/entries/portal.js +204 -0
  235. package/src/entries/shared.js +50 -0
  236. package/src/i18n/formatters.js +455 -0
  237. package/src/i18n/index.js +169 -0
  238. package/src/i18n/locales/ar.json +137 -0
  239. package/src/i18n/locales/de.json +137 -0
  240. package/src/i18n/locales/en.json +137 -0
  241. package/src/i18n/locales/es.json +137 -0
  242. package/src/i18n/locales/he.json +137 -0
  243. package/src/i18n/locales/zh.json +137 -0
  244. package/src/i18n/rtl.css +183 -0
  245. package/src/index.js +82 -62
  246. package/src/loader.js +103 -0
  247. package/src/setupTests.js +5 -5
  248. package/tsconfig.json +51 -0
  249. package/types/charts.d.ts +569 -0
  250. package/types/components.d.ts +441 -0
  251. package/types/config.d.ts +199 -0
  252. package/types/index.d.ts +353 -0
@@ -0,0 +1,530 @@
1
+ /**
2
+ * @fileoverview Development server with Hot Module Replacement (HMR) for Visualify CLI
3
+ * @module cli/dev-server
4
+ *
5
+ * Provides an Express server with WebSocket support for HMR,
6
+ * static file serving, and error overlay functionality.
7
+ */
8
+
9
+ const express = require('express');
10
+ const http = require('http');
11
+ const path = require('path');
12
+ const fs = require('fs').promises;
13
+ const { WebSocketServer } = require('ws');
14
+ const logger = require('./utils/logger');
15
+ const { createHMREngine } = require('./hmr');
16
+ const { loadConfig } = require('./utils/config');
17
+
18
+ /**
19
+ * Default server configuration
20
+ * @readonly
21
+ * @type {Object}
22
+ */
23
+ const DEFAULT_SERVER_CONFIG = {
24
+ port: 3000,
25
+ host: 'localhost',
26
+ hmr: true,
27
+ staticDirs: ['public', 'dist', 'build'],
28
+ cors: true,
29
+ };
30
+
31
+ /**
32
+ * Development Server class with HMR support
33
+ */
34
+ class DevServer {
35
+ /**
36
+ * Create a dev server instance
37
+ * @param {Object} options - Server configuration options
38
+ * @param {number} [options.port=3000] - Port to run the server on
39
+ * @param {string} [options.host='localhost'] - Host to bind the server to
40
+ * @param {boolean} [options.hmr=true] - Enable HMR
41
+ * @param {string} [options.rootDir] - Root directory for serving files
42
+ * @param {string} [options.mode='portal'] - Development mode (docs or portal)
43
+ */
44
+ constructor(options = {}) {
45
+ this.config = {
46
+ ...DEFAULT_SERVER_CONFIG,
47
+ ...options,
48
+ };
49
+
50
+ this.rootDir = options.rootDir || process.cwd();
51
+ this.mode = options.mode || 'portal';
52
+
53
+ this.app = null;
54
+ this.server = null;
55
+ this.wss = null;
56
+ this.hmrEngine = null;
57
+ this.isRunning = false;
58
+
59
+ // Error overlay HTML template
60
+ this.errorOverlayTemplate = null;
61
+ }
62
+
63
+ /**
64
+ * Start the development server
65
+ * @returns {Promise<void>}
66
+ */
67
+ async start() {
68
+ if (this.isRunning) {
69
+ logger.warn('Dev server is already running');
70
+ return;
71
+ }
72
+
73
+ logger.header('Starting Development Server');
74
+ logger.debug('Server config:', this.config);
75
+
76
+ try {
77
+ // Create Express app
78
+ this.app = express();
79
+
80
+ // Configure middleware
81
+ await this.configureMiddleware();
82
+
83
+ // Create HTTP server
84
+ this.server = http.createServer(this.app);
85
+
86
+ // Setup WebSocket server for HMR
87
+ if (this.config.hmr) {
88
+ await this.setupWebSocket();
89
+ }
90
+
91
+ // Start listening
92
+ await this.startListening();
93
+
94
+ this.isRunning = true;
95
+ this.printServerInfo();
96
+ } catch (err) {
97
+ logger.error('Failed to start dev server:', err.message);
98
+ await this.stop();
99
+ throw err;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Stop the development server
105
+ * @returns {Promise<void>}
106
+ */
107
+ async stop() {
108
+ if (!this.isRunning) {
109
+ return;
110
+ }
111
+
112
+ logger.debug('Stopping dev server...');
113
+
114
+ // Stop HMR engine
115
+ if (this.hmrEngine) {
116
+ await this.hmrEngine.stop();
117
+ this.hmrEngine = null;
118
+ }
119
+
120
+ // Close WebSocket server
121
+ if (this.wss) {
122
+ this.wss.close();
123
+ this.wss = null;
124
+ }
125
+
126
+ // Close HTTP server
127
+ if (this.server) {
128
+ await new Promise((resolve) => {
129
+ this.server.close(resolve);
130
+ });
131
+ this.server = null;
132
+ }
133
+
134
+ this.app = null;
135
+ this.isRunning = false;
136
+ logger.success('Dev server stopped');
137
+ }
138
+
139
+ /**
140
+ * Configure Express middleware
141
+ * @private
142
+ * @returns {Promise<void>}
143
+ */
144
+ async configureMiddleware() {
145
+ // CORS middleware
146
+ if (this.config.cors) {
147
+ this.app.use((req, res, next) => {
148
+ res.header('Access-Control-Allow-Origin', '*');
149
+ res.header(
150
+ 'Access-Control-Allow-Headers',
151
+ 'Origin, X-Requested-With, Content-Type, Accept',
152
+ );
153
+ next();
154
+ });
155
+ }
156
+
157
+ // Parse JSON bodies
158
+ this.app.use(express.json());
159
+
160
+ // Inject HMR client script
161
+ this.app.use(this.injectHMRClient.bind(this));
162
+
163
+ // Serve static files from configured directories
164
+ for (const dir of this.config.staticDirs) {
165
+ const staticPath = path.join(this.rootDir, dir);
166
+ try {
167
+ await fs.access(staticPath);
168
+ this.app.use(express.static(staticPath));
169
+ logger.debug(`Serving static files from: ${dir}`);
170
+ } catch {
171
+ logger.debug(`Static directory not found: ${dir}`);
172
+ }
173
+ }
174
+
175
+ // API endpoint for server status
176
+ this.app.get('/__hmr/status', (req, res) => {
177
+ res.json({
178
+ status: 'ok',
179
+ mode: this.mode,
180
+ hmr: this.config.hmr,
181
+ hmrStatus: this.hmrEngine?.getStatus() || null,
182
+ timestamp: Date.now(),
183
+ });
184
+ });
185
+
186
+ // API endpoint for current config
187
+ this.app.get('/__hmr/config', async (req, res) => {
188
+ try {
189
+ const config = await loadConfig();
190
+ res.json(config);
191
+ } catch (err) {
192
+ res.status(500).json({ error: err.message });
193
+ }
194
+ });
195
+
196
+ // Health check endpoint
197
+ this.app.get('/__health', (req, res) => {
198
+ res.json({ status: 'healthy', timestamp: Date.now() });
199
+ });
200
+
201
+ // SPA fallback for client-side routing
202
+ this.app.get('*', this.handleSPAFallback.bind(this));
203
+
204
+ // Error handling middleware
205
+ this.app.use(this.handleError.bind(this));
206
+ }
207
+
208
+ /**
209
+ * Inject HMR client script into HTML responses
210
+ * @private
211
+ * @param {Object} req - Express request
212
+ * @param {Object} res - Express response
213
+ * @param {Function} next - Express next function
214
+ */
215
+ injectHMRClient(req, res, next) {
216
+ // Only process HTML requests
217
+ if (!req.headers.accept?.includes('text/html')) {
218
+ return next();
219
+ }
220
+
221
+ const originalSend = res.send.bind(res);
222
+
223
+ res.send = (body) => {
224
+ if (typeof body === 'string' && body.includes('</html>')) {
225
+ const hmrScript = this.generateHMRClientScript();
226
+ body = body.replace('</head>', `${hmrScript}</head>`);
227
+ }
228
+ return originalSend(body);
229
+ };
230
+
231
+ next();
232
+ }
233
+
234
+ /**
235
+ * Generate the HMR client script to inject
236
+ * @private
237
+ * @returns {string} The script tag HTML
238
+ */
239
+ generateHMRClientScript() {
240
+ const wsProtocol = 'ws';
241
+ const wsUrl = `${wsProtocol}://${this.config.host}:${this.config.port}/__hmr`;
242
+
243
+ return `
244
+ <script>
245
+ (function() {
246
+ // Visualify HMR Client
247
+ window.__VISUALIFY_HMR__ = {
248
+ enabled: true,
249
+ wsUrl: '${wsUrl}',
250
+ connected: false,
251
+ reconnectAttempts: 0,
252
+ maxReconnectAttempts: 10,
253
+ reconnectDelay: 1000
254
+ };
255
+
256
+ // HMR client will be initialized by the main hmr-client.js module
257
+ // This script just sets up the configuration
258
+ if (typeof window.VisualifyHMR !== 'undefined') {
259
+ window.VisualifyHMR.init(window.__VISUALIFY_HMR__.wsUrl);
260
+ }
261
+ })();
262
+ </script>`;
263
+ }
264
+
265
+ /**
266
+ * Setup WebSocket server for HMR
267
+ * @private
268
+ * @returns {Promise<void>}
269
+ */
270
+ async setupWebSocket() {
271
+ // Create WebSocket server
272
+ this.wss = new WebSocketServer({
273
+ server: this.server,
274
+ path: '/__hmr',
275
+ });
276
+
277
+ // Create HMR engine
278
+ this.hmrEngine = createHMREngine({
279
+ rootDir: this.rootDir,
280
+ debounceMs: 300,
281
+ });
282
+
283
+ // Handle WebSocket connections
284
+ this.wss.on('connection', (ws, req) => {
285
+ logger.debug(`WebSocket client connected from ${req.socket.remoteAddress}`);
286
+ this.hmrEngine.addClient(ws);
287
+
288
+ // Handle messages from client
289
+ ws.on('message', (data) => {
290
+ this.handleClientMessage(ws, data);
291
+ });
292
+ });
293
+
294
+ // Start HMR engine
295
+ await this.hmrEngine.start();
296
+
297
+ logger.debug('WebSocket server setup complete');
298
+ }
299
+
300
+ /**
301
+ * Handle messages from WebSocket clients
302
+ * @private
303
+ * @param {WebSocket} ws - The WebSocket client
304
+ * @param {Buffer} data - The message data
305
+ */
306
+ handleClientMessage(ws, data) {
307
+ try {
308
+ const message = JSON.parse(data.toString());
309
+ logger.debug('Received message from client:', message.type);
310
+
311
+ switch (message.type) {
312
+ case 'ready':
313
+ // Client is ready for updates
314
+ logger.debug('Client reported ready');
315
+ break;
316
+
317
+ case 'error':
318
+ // Client encountered an error
319
+ logger.error('Client error:', message.message);
320
+ break;
321
+
322
+ case 'pong':
323
+ // Heartbeat response
324
+ break;
325
+
326
+ default:
327
+ logger.debug('Unknown message type:', message.type);
328
+ }
329
+ } catch (err) {
330
+ logger.debug('Failed to parse client message:', err.message);
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Handle SPA fallback for client-side routing
336
+ * @private
337
+ * @param {Object} req - Express request
338
+ * @param {Object} res - Express response
339
+ */
340
+ async handleSPAFallback(req, res) {
341
+ // Don't handle API routes
342
+ if (req.path.startsWith('/__')) {
343
+ return res.status(404).json({ error: 'Not found' });
344
+ }
345
+
346
+ // Try to serve index.html
347
+ const indexPaths = [
348
+ path.join(this.rootDir, 'public', 'index.html'),
349
+ path.join(this.rootDir, 'index.html'),
350
+ path.join(this.rootDir, 'dist', 'index.html'),
351
+ ];
352
+
353
+ for (const indexPath of indexPaths) {
354
+ try {
355
+ let content = await fs.readFile(indexPath, 'utf-8');
356
+
357
+ // Inject HMR client if enabled
358
+ if (this.config.hmr && !content.includes('__VISUALIFY_HMR__')) {
359
+ const hmrScript = this.generateHMRClientScript();
360
+ content = content.replace('</head>', `${hmrScript}</head>`);
361
+ }
362
+
363
+ return res.send(content);
364
+ } catch {
365
+ // Try next path
366
+ }
367
+ }
368
+
369
+ // No index.html found, serve a default page
370
+ res.send(this.generateDefaultPage());
371
+ }
372
+
373
+ /**
374
+ * Generate a default HTML page when no index.html is found
375
+ * @private
376
+ * @returns {string} The HTML page
377
+ */
378
+ generateDefaultPage() {
379
+ const hmrScript = this.config.hmr ? this.generateHMRClientScript() : '';
380
+
381
+ return `
382
+ <!DOCTYPE html>
383
+ <html lang="en">
384
+ <head>
385
+ <meta charset="UTF-8">
386
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
387
+ <title>Visualify Dev Server</title>
388
+ <style>
389
+ body {
390
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
391
+ max-width: 800px;
392
+ margin: 50px auto;
393
+ padding: 20px;
394
+ background: #f5f5f5;
395
+ }
396
+ .container {
397
+ background: white;
398
+ padding: 40px;
399
+ border-radius: 8px;
400
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
401
+ }
402
+ h1 { color: #333; }
403
+ .info { color: #666; margin: 20px 0; }
404
+ .status { padding: 10px; border-radius: 4px; margin: 10px 0; }
405
+ .status.ok { background: #d4edda; color: #155724; }
406
+ .status.warn { background: #fff3cd; color: #856404; }
407
+ code {
408
+ background: #f4f4f4;
409
+ padding: 2px 6px;
410
+ border-radius: 3px;
411
+ font-family: monospace;
412
+ }
413
+ </style>
414
+ ${hmrScript}
415
+ </head>
416
+ <body>
417
+ <div class="container">
418
+ <h1>Visualify Development Server</h1>
419
+ <div class="status ok">Server is running in ${this.mode} mode</div>
420
+ <p class="info">
421
+ No index.html file was found in your project.
422
+ Create a <code>public/index.html</code> file to customize this page.
423
+ </p>
424
+ <p><strong>Mode:</strong> ${this.mode}</p>
425
+ <p><strong>HMR:</strong> ${this.config.hmr ? 'Enabled' : 'Disabled'}</p>
426
+ <p><strong>Root:</strong> ${this.rootDir}</p>
427
+ <hr>
428
+ <p class="info">
429
+ <a href="/__hmr/status">HMR Status</a> |
430
+ <a href="/__health">Health Check</a>
431
+ </p>
432
+ </div>
433
+ </body>
434
+ </html>`;
435
+ }
436
+
437
+ /**
438
+ * Handle errors in Express
439
+ * @private
440
+ * @param {Error} err - The error object
441
+ * @param {Object} req - Express request
442
+ * @param {Object} res - Express response
443
+ * @param {Function} next - Express next function
444
+ */
445
+ handleError(err, req, res, next) {
446
+ logger.error('Server error:', err.message);
447
+
448
+ if (res.headersSent) {
449
+ return next(err);
450
+ }
451
+
452
+ res.status(500).json({
453
+ error: 'Internal server error',
454
+ message: err.message,
455
+ ...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
456
+ });
457
+ }
458
+
459
+ /**
460
+ * Start listening on the configured port
461
+ * @private
462
+ * @returns {Promise<void>}
463
+ */
464
+ async startListening() {
465
+ return new Promise((resolve, reject) => {
466
+ this.server.listen(this.config.port, this.config.host, (err) => {
467
+ if (err) {
468
+ reject(err);
469
+ } else {
470
+ resolve();
471
+ }
472
+ });
473
+
474
+ this.server.on('error', (err) => {
475
+ if (err.code === 'EADDRINUSE') {
476
+ reject(new Error(`Port ${this.config.port} is already in use`));
477
+ } else {
478
+ reject(err);
479
+ }
480
+ });
481
+ });
482
+ }
483
+
484
+ /**
485
+ * Print server information to console
486
+ * @private
487
+ */
488
+ printServerInfo() {
489
+ const protocol = 'http';
490
+ const url = `${protocol}://${this.config.host}:${this.config.port}`;
491
+
492
+ logger.newline();
493
+ logger.success(`Development server running at: ${url}`);
494
+ logger.info(`Mode: ${this.mode}`);
495
+ logger.info(`HMR: ${this.config.hmr ? 'Enabled' : 'Disabled'}`);
496
+ logger.newline();
497
+ logger.tip('Press Ctrl+C to stop the server');
498
+ logger.newline();
499
+ }
500
+
501
+ /**
502
+ * Get server status
503
+ * @returns {Object} Status information
504
+ */
505
+ getStatus() {
506
+ return {
507
+ isRunning: this.isRunning,
508
+ config: this.config,
509
+ mode: this.mode,
510
+ hmrStatus: this.hmrEngine?.getStatus() || null,
511
+ };
512
+ }
513
+ }
514
+
515
+ /**
516
+ * Create and start a development server
517
+ * @param {Object} options - Server configuration options
518
+ * @returns {Promise<DevServer>} The started dev server
519
+ */
520
+ async function createDevServer(options = {}) {
521
+ const server = new DevServer(options);
522
+ await server.start();
523
+ return server;
524
+ }
525
+
526
+ module.exports = {
527
+ DevServer,
528
+ createDevServer,
529
+ DEFAULT_SERVER_CONFIG,
530
+ };