visualifyjs 2.5.3 → 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.
- package/.claude/mem/TIMELINE.md +36 -0
- package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-external-script-solution.md +24 -0
- package/.claude/mem/notes/2026-02-11-3d-visualization-docs-fix-session-summary.md +43 -0
- package/.claude/mem/notes/2026-02-11-cli-fix-editor-command-alias.md +26 -0
- package/.claude/mem/notes/2026-02-11-phase-3-developer-experience-completed.md +51 -0
- package/.claude/mem/notes/2026-02-11-phase-4-web-workers-implementation-complete.md +59 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-2-3d-visualization-complete.md +50 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-2-committed-ready-for-phase-3.md +33 -0
- package/.claude/mem/notes/2026-02-11-visualify-phase-3-complete-developer-experience.md +52 -0
- package/.claude/mem/notes/2026-02-11-visualify-repository-cleanup-complete.md +28 -0
- package/.claude/mem/notes/2026-02-18-codebase-cleanup-docsify-plugin-documentation.md +37 -0
- package/.claude/mem/notes/2026-02-19-css-grid-layout-fix-displaycontents-on-vcontroller.md +18 -0
- package/.claude/mem/notes/2026-02-19-docsify-plugin-fixes-latex-and-visualify-code-bloc.md +26 -0
- package/.claude/mem/notes/2026-02-19-page-mode-docs-update-decisions.md +23 -0
- package/.claude/mem/notes/2026-02-19-react-context-infinite-re-render-loop-fix-pattern.md +31 -0
- package/.claude/mem/notes/2026-02-19-version-300-bump-and-build-fixes.md +32 -0
- package/.claude/mem/notes/2026-02-19-visualify-build-deployment-architecture-bug-fixes.md +25 -0
- package/.claude/mem/notes/2026-02-19-visualify-dist-iife-self-contained-build-config.md +30 -0
- package/.claude/mem/notes/2026-02-19-visualify-infinite-loop-i18n-fixes.md +31 -0
- package/.claude/mem/notes/2026-02-19-visualify-v3-bundle-splitting-docs-restructuring.md +32 -0
- package/.claude/mem/notes/2026-02-20-bundle-externalization-final-architecture.md +29 -0
- package/.claude/mem/notes/2026-02-20-chromium-page-fix-unstable-keys-and-double-event-b.md +27 -0
- package/.claude/mem/notes/2026-02-20-console-cleanup-bundle-optimization-commit.md +20 -0
- package/.claude/mem/notes/2026-02-20-dotbio-dot-plot-fix-useeffect-dependency.md +21 -0
- package/.claude/mem/notes/2026-02-20-public-folder-cleanup-and-readme-rewrite.md +25 -0
- package/.claude/mem/notes/2026-02-20-v300-release-and-beta-channel-strategy.md +29 -0
- package/.claude/mem/notes/2026-02-20-visium-background-image-unknown-legend-fix.md +19 -0
- package/.claude/mem/notes/2026-02-20-visualify-cdn-loader-bundle-externalization.md +34 -0
- package/.claude/mem/sessions/session-2026-02-20-031524.md +54 -0
- package/.claude/settings.local.json +21 -0
- package/.github/workflows/static.yml.bak +51 -51
- package/.sisyphus/boulder.json +65 -0
- package/.sisyphus/plans/phase-4-advanced-optimizations.md +217 -0
- package/LICENSE +674 -674
- package/README.md +94 -59
- package/config-overrides.js +31 -31
- package/dist/stats.html +4949 -0
- package/dist/visualify-3d.esm.js +1 -0
- package/dist/visualify-3d.js +1 -0
- package/dist/visualify-core.esm.js +1 -0
- package/dist/visualify-core.js +1 -0
- package/dist/visualify-docs.esm.js +1 -0
- package/dist/visualify-docs.js +1 -0
- package/dist/visualify-loader.js +1 -0
- package/dist/visualify-pages.esm.js +1 -0
- package/dist/visualify-pages.js +1 -0
- package/dist/visualify-portal.esm.js +1 -0
- package/dist/visualify-portal.js +1 -0
- package/dist/visualify-shared.js +26571 -0
- package/dist/visualify.js +1 -188
- package/docs/CHANGELOG.md +148 -0
- package/docs/cli/commands.md +513 -0
- package/docs/configuration/visualify-json.md +474 -0
- package/docs/docs/3d-visualization.md +374 -0
- package/docs/docs/CLI.md +303 -34
- package/docs/docs/README.md +65 -65
- package/docs/docs/Rechart/bar.md +190 -190
- package/docs/docs/Rechart/funnel.md +241 -193
- package/docs/docs/Rechart/line.md +355 -355
- package/docs/docs/Rechart/pie.md +225 -225
- package/docs/docs/Rechart/radar.md +253 -253
- package/docs/docs/Rechart/scatter.md +298 -0
- package/docs/docs/_404.md +51 -51
- package/docs/docs/_coverpage.md +11 -11
- package/docs/docs/_sidebar.md +54 -43
- package/docs/docs/components/dotBio.md +87 -34
- package/docs/docs/components/echart.md +171 -82
- package/docs/docs/components/html.md +61 -34
- package/docs/docs/components/macaron.md +156 -145
- package/docs/docs/components/markdown.md +42 -0
- package/docs/docs/components/more.md +183 -142
- package/docs/docs/components/plotly.md +132 -62
- package/docs/docs/components/scatterL.md +171 -70
- package/docs/docs/components/visium.md +112 -57
- package/docs/docs/configuration.md +121 -123
- package/docs/docs/deploy.md +31 -31
- package/docs/docs/docsify-plugin.md +655 -0
- package/docs/docs/hmr.md +165 -0
- package/docs/docs/i18n.md +332 -0
- package/docs/docs/log.md +30 -1
- package/docs/docs/more-pages.md +23 -23
- package/docs/docs/quickstart.md +148 -119
- package/docs/docs/rechart-attributes.md +74 -74
- package/docs/docs/rechart-basic-usage.md +160 -162
- package/docs/docs/theme.md +5 -5
- package/docs/docs/typescript.md +306 -0
- package/docs/docs/visual-editor.md +359 -0
- package/docs/index.html +85 -71
- package/docs/manifest.json +23 -23
- package/docs/migration/v3-migration.md +392 -0
- package/docs/static/css/fluff-stuff.css +169 -169
- package/docs/static/css/font-awesome.min.css +4 -4
- package/docs/static/css/visualify.css +6 -25
- package/docs/static/js/3d-viz-examples.js +181 -0
- package/docs/static/js/configuration.js +630 -448
- package/docs/static/js/visualify.js +1 -188
- package/package.json +106 -84
- package/rollup.config.mjs +766 -76
- package/src/_css/404.css +115 -115
- package/src/_css/App.css +37 -37
- package/src/_css/autoSuggestion.css +26 -26
- package/src/_css/circular-progress.css +32 -32
- package/src/_css/index.css +36 -36
- package/src/_css/modern.css +350 -25
- package/src/_media/corner.svg +8 -8
- package/src/_media/download.svg +3 -3
- package/src/_media/logo.svg +14 -14
- package/src/_test/App.test.js +15 -15
- package/src/_utils/reportWebVitals.js +13 -13
- package/src/a11y/README.md +177 -0
- package/src/a11y/aria-labels.js +339 -0
- package/src/a11y/color-contrast.js +535 -0
- package/src/a11y/index.js +197 -0
- package/src/a11y/keyboard-nav.js +523 -0
- package/src/a11y/styles.css +165 -0
- package/src/cli/commands/dev.js +214 -0
- package/src/cli/commands/docs.js +521 -0
- package/src/cli/commands/edit.js +379 -0
- package/src/cli/commands/init.js +213 -0
- package/src/cli/commands/portal.js +236 -0
- package/src/cli/dev-server.js +530 -0
- package/src/cli/hmr.js +456 -0
- package/src/cli/index.js +180 -0
- package/src/cli/utils/config.js +207 -0
- package/src/cli/utils/logger.js +241 -0
- package/src/config/defaults.ts +122 -0
- package/src/config/index.ts +72 -0
- package/src/config/loader.ts +478 -0
- package/src/config/schema.ts +227 -0
- package/src/config/validator.ts +337 -0
- package/src/core/appContext.js +34 -27
- package/src/core/components/Bar.js +383 -0
- package/src/core/components/Bar3D.js +473 -0
- package/src/core/components/LargeDatasetChart.js +296 -0
- package/src/core/components/Line3D.js +310 -0
- package/src/core/components/Scatter.js +392 -188
- package/src/core/components/Scatter3D.js +455 -0
- package/src/core/components/ScatterBio.js +601 -572
- package/src/core/components/Surface3D.js +326 -0
- package/src/core/components/ThreeCustom.js +648 -0
- package/src/core/components/ThreeScene.js +459 -0
- package/src/core/components/VisiumPlot.js +191 -165
- package/src/core/components/browser.js +42 -42
- package/src/core/components/dotplot.js +413 -413
- package/src/core/components/html.js +29 -29
- package/src/core/components/list.js +178 -178
- package/src/core/components/macaron.js +206 -201
- package/src/core/components/markdown.js +56 -56
- package/src/core/components/parser.scatterBio.js +582 -579
- package/src/core/components/ratio.js +82 -80
- package/src/core/components/scatterL.js +206 -173
- package/src/core/components/searchbar.js +156 -131
- package/src/core/components/selection.js +310 -193
- package/src/core/components/timeline.js +236 -281
- package/src/core/components/visium.js +114 -97
- package/src/core/data-processor.js +413 -0
- package/src/core/fetch/condfetch.js +82 -82
- package/src/core/fetch/fetch.js +92 -92
- package/src/core/fetch/json.js +29 -29
- package/src/core/fetch/vfetch.js +42 -42
- package/src/core/hmr-client.js +724 -0
- package/src/core/liveEditor.js +44 -44
- package/src/core/modules/codeEditorWithPreview.js +104 -104
- package/src/core/modules/echarts/common.js +20 -20
- package/src/core/modules/echarts/gl.js +228 -0
- package/src/core/modules/echarts/presetHandler.js +41 -41
- package/src/core/modules/echarts/presets/esodev.chromium.js +172 -172
- package/src/core/modules/echarts/presets/esodev.codex.js +130 -130
- package/src/core/modules/echarts/presets/esodev.visium.js +123 -123
- package/src/core/modules/echarts/presets/mmtrbc.js +186 -186
- package/src/core/modules/echarts.js +70 -71
- package/src/core/modules/echartsUtils.js +43 -43
- package/src/core/modules/echartswitcher.js +227 -152
- package/src/core/modules/replotly/presetHandler.js +24 -24
- package/src/core/modules/replotly/presets/minimum.js +18 -18
- package/src/core/modules/replotly/presets/mmtrbc.dot.js +114 -114
- package/src/core/modules/replotly/presets/mmtrbc.violin.js +100 -100
- package/src/core/modules/replotly.js +74 -71
- package/src/core/modules/threejs/Camera.js +373 -0
- package/src/core/modules/threejs/Lighting.js +459 -0
- package/src/core/modules/threejs/Renderer.js +364 -0
- package/src/core/modules/threejs/Scene.js +266 -0
- package/src/core/modules/threejs/index.js +155 -0
- package/src/core/pages/404.js +50 -50
- package/src/core/pages/error.js +27 -27
- package/src/core/pages/jsonPage.js +62 -62
- package/src/core/pages/loading.js +44 -44
- package/src/core/parser/echart.data.js +204 -183
- package/src/core/parser/echart.features.js +125 -125
- package/src/core/parser/echart.general.js +147 -143
- package/src/core/parser/echart.hilbert.js +57 -57
- package/src/core/parser/echart.parser.js +210 -210
- package/src/core/parser/echart.series.js +67 -67
- package/src/core/parser/echart.types.js +76 -76
- package/src/core/parser/plotly.config.js +10 -10
- package/src/core/parser/plotly.data.js +132 -132
- package/src/core/parser/plotly.layout.js +9 -9
- package/src/core/parser/plotly.violin.js +18 -18
- package/src/core/recharts.js +361 -62
- package/src/core/router/alias.js +49 -49
- package/src/core/router/jsonRouter.js +31 -31
- package/src/core/themes/modern.js +32 -32
- package/src/core/themes/themeSelector.js +33 -33
- package/src/core/visualify.js +213 -47
- package/src/core/widgets/circularProgress.js +23 -23
- package/src/core/widgets/controller.js +116 -83
- package/src/core/widgets/errorBoundary.js +36 -36
- package/src/core/widgets/footer.js +185 -177
- package/src/core/widgets/header.js +238 -234
- package/src/core/widgets/layout/Grid.js +31 -31
- package/src/core/widgets/layout.js +36 -36
- package/src/core/widgets/mapping.js +56 -42
- package/src/core/workers/data-worker.js +349 -0
- package/src/core/workers/worker-pool.js +396 -0
- package/src/docsify/bundle.js +215 -0
- package/src/docsify/markdown.js +271 -0
- package/src/docsify/plugin.js +268 -0
- package/src/editor/README.md +172 -0
- package/src/editor/components/ChartBuilder.jsx +341 -0
- package/src/editor/components/ChartTypeSidebar.jsx +91 -0
- package/src/editor/components/Editor.jsx +367 -0
- package/src/editor/components/Preview.jsx +446 -0
- package/src/editor/components/PropertyPanel.jsx +468 -0
- package/src/editor/components/StatusBar.jsx +85 -0
- package/src/editor/context/EditorContext.js +248 -0
- package/src/editor/hooks/useDebounce.js +32 -0
- package/src/editor/index.js +315 -0
- package/src/editor/styles/editor.css +637 -0
- package/src/editor/utils/chartValidator.js +263 -0
- package/src/entries/charts3d.js +70 -0
- package/src/entries/core.js +78 -0
- package/src/entries/docs.js +154 -0
- package/src/entries/pages.js +93 -0
- package/src/entries/portal.js +204 -0
- package/src/entries/shared.js +50 -0
- package/src/i18n/formatters.js +455 -0
- package/src/i18n/index.js +169 -0
- package/src/i18n/locales/ar.json +137 -0
- package/src/i18n/locales/de.json +137 -0
- package/src/i18n/locales/en.json +137 -0
- package/src/i18n/locales/es.json +137 -0
- package/src/i18n/locales/he.json +137 -0
- package/src/i18n/locales/zh.json +137 -0
- package/src/i18n/rtl.css +183 -0
- package/src/index.js +82 -62
- package/src/loader.js +103 -0
- package/src/setupTests.js +5 -5
- package/tsconfig.json +51 -0
- package/types/charts.d.ts +569 -0
- package/types/components.d.ts +441 -0
- package/types/config.d.ts +199 -0
- 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
|
+
};
|