From 8add018f7479429572920789241036b205200db7 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:11:06 +0200 Subject: [PATCH] Add Vite dev server and HMR integration --- InfoLogger/README.md | 2 +- InfoLogger/docker-compose.dev.yaml | 6 + InfoLogger/docker-compose.test.yaml | 8 +- InfoLogger/index.js | 51 ++++-- InfoLogger/package-lock.json | 273 ---------------------------- InfoLogger/package.json | 3 +- InfoLogger/src/index.js | 11 +- 7 files changed, 63 insertions(+), 291 deletions(-) diff --git a/InfoLogger/README.md b/InfoLogger/README.md index 0f23f7fce..4d6ae849f 100644 --- a/InfoLogger/README.md +++ b/InfoLogger/README.md @@ -106,7 +106,7 @@ Run `npm run docker:cleanup` to remove all containers created by the above and t Sources live in [src/](src/) and are bundled by [Vite](https://vite.dev/) ([vite.config.mjs](vite.config.mjs)). A lightweight MVC app on top of `@aliceo2/web-ui`. -- **Build & serving** - `npm run build` bundles the frontend into `dist/`, which [index.js](index.js) serves (`npm run mocha` builds first). +- **Build & serving** - With `NODE_ENV=development` (set by `npm run start:dev` and the docker compose files), [index.js](index.js) serves the frontend through Vite middleware, with hot module reload over a WebSocket on port `5173`. Otherwise it serves the bundle from `dist/`, produced by `npm run build` (`npm run mocha` builds first). - **App shell** - [index.html](index.html) bootstraps [src/index.js](src/index.js), which mounts the top-level [Model](src/Model.js) and renders [view.js](src/view.js). - **Sub-models** - [log/Log.js](src/log/Log.js) drives the log table (rendering in `tableLogs*.js`, inspector, status bar, context menu, zoom, etc.). [logFilter/LogFilter.js](src/logFilter/LogFilter.js) owns the filter inputs. [table/Table.js](src/table/Table.js) holds shared table state. - **Supporting code** - [services/](src/services/), [constants/](src/constants/), [common/](src/common/), [about/](src/about/) (about component). diff --git a/InfoLogger/docker-compose.dev.yaml b/InfoLogger/docker-compose.dev.yaml index 001329a0c..7b51dcd45 100644 --- a/InfoLogger/docker-compose.dev.yaml +++ b/InfoLogger/docker-compose.dev.yaml @@ -11,6 +11,7 @@ services: ports: - "8080:8080" + - "5173:5173" # Vite HMR WS volumes: - type: bind @@ -41,3 +42,8 @@ services: read_only: true source: ./index.js target: /home/node/app/index.js + # Use the local Framework source to allow edits from it to come through before it gets released + - type: bind + read_only: true + source: ../Framework + target: /home/node/app/node_modules/@aliceo2/web-ui diff --git a/InfoLogger/docker-compose.test.yaml b/InfoLogger/docker-compose.test.yaml index 4063107eb..4a6466081 100644 --- a/InfoLogger/docker-compose.test.yaml +++ b/InfoLogger/docker-compose.test.yaml @@ -6,5 +6,9 @@ services: restart: "no" - environment: - NODE_ENV: development + volumes: + # Use the local Framework source to allow edits from it to come through before it gets released + - type: bind + read_only: true + source: ../Framework + target: /home/node/app/node_modules/@aliceo2/web-ui \ No newline at end of file diff --git a/InfoLogger/index.js b/InfoLogger/index.js index 9a87605b2..2aec04597 100644 --- a/InfoLogger/index.js +++ b/InfoLogger/index.js @@ -21,20 +21,47 @@ const api = require('./lib/api.js'); // ------------------------------------------------------- -// Start server HTTP and bind WebSocket to it -const http = new HttpServer(config.http, config.jwt, config.openId); +const isDev = process.env.NODE_ENV === 'development'; +const VITE_HMR_PORT = 5173; + +// Start server HTTP and bind WebSocket to it; the HMR port is only allowed in CSP during development +const http = new HttpServer({ + ...config.http, + ...isDev && { extraConnectSrc: [`ws://${config.http.hostname ?? 'localhost'}:${VITE_HMR_PORT}`] }, +}, config.jwt, config.openId); const ws = new WebSocket(http); const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'ilg'}/index`); // Expose application to / -const distPath = path.join(__dirname, 'dist'); -if (!fs.existsSync(distPath)) { - logger.errorMessage(`Frontend build not found at ${distPath}, run "npm run build" first`); -} else { - logger.infoMessage(`Serving frontend files from ${distPath}`); -} -http.addStaticPath(distPath); - -// Attach services to web server -api.attachTo(http, ws); +const start = async () => { + if (isDev) { + logger.infoMessage(`Serving frontend files through Vite middleware, HMR on port ${VITE_HMR_PORT}`); + const { createServer } = require('vite'); + + const vite = await createServer({ + server: { middlewareMode: true, hmr: { port: VITE_HMR_PORT } }, + appType: 'mpa', + }); + + // Vite ends unmatched requests with 404, so API calls must skip it + http.routerStatics.use((req, res, next) => + req.path.startsWith('/api/') ? next() : vite.middlewares(req, res, next)); + } else { + const distPath = path.join(__dirname, 'dist'); + if (!fs.existsSync(distPath)) { + logger.errorMessage(`Frontend build not found at ${distPath}, run "npm run build" first`); + } else { + logger.infoMessage(`Serving frontend files from ${distPath}`); + } + http.addStaticPath(distPath); + } + + // Attach services to web server + api.attachTo(http, ws); +}; + +start().catch((error) => { + logger.errorMessage(`Failed to start InfoLogger GUI: ${error.message}`); + process.exit(1); +}); diff --git a/InfoLogger/package-lock.json b/InfoLogger/package-lock.json index fc590e8b1..bf74dcf77 100644 --- a/InfoLogger/package-lock.json +++ b/InfoLogger/package-lock.json @@ -22,7 +22,6 @@ "eslint-plugin-jsdoc": "65.3.1", "globals": "17.13.0", "mocha": "12.0.0", - "nodemon": "3.1.14", "nyc": "18.0.0", "puppeteer": "25.14.0", "sinon": "22.1.0", @@ -1459,33 +1458,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/append-transform": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", @@ -1549,19 +1521,6 @@ "node": ">=6.0.0" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/body-parser": { "version": "1.20.8", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz", @@ -1627,19 +1586,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -2694,19 +2640,6 @@ "flat-cache": "^6.1.23" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -3170,13 +3103,6 @@ "node": ">= 4" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3212,19 +3138,6 @@ "node": ">= 0.10" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3255,16 +3168,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -4302,132 +4205,6 @@ "node": ">=18" } }, - "node_modules/nodemon": { - "version": "3.1.14", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", - "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^10.2.1", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/nodemon/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/nodemon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/nodemon/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/nodemon/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/nodemon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/nyc": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-18.0.0.tgz", @@ -5053,13 +4830,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, - "license": "MIT" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5536,19 +5306,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/sinon": { "version": "22.1.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-22.1.0.tgz", @@ -5770,19 +5527,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/to-valid-identifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-valid-identifier/-/to-valid-identifier-1.0.0.tgz", @@ -5810,16 +5554,6 @@ "node": ">=0.6" } }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -5919,13 +5653,6 @@ "node": ">=14.17" } }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, "node_modules/undici-types": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", diff --git a/InfoLogger/package.json b/InfoLogger/package.json index 7a79f439f..1ff1cc286 100644 --- a/InfoLogger/package.json +++ b/InfoLogger/package.json @@ -26,7 +26,7 @@ "coverage": "nyc npm test && npm run coverage:report", "coverage:report": "nyc report --reporter=html --reporter=json", "start": "node index.js", - "start:dev": "vite build && nodemon --inspect --watch index.js --watch lib --watch config.js index.js", + "start:dev": "NODE_ENV=development node --inspect --watch-path=index.js --watch-path=lib --watch-path=config.js index.js", "simul": "node test/live-simulator/runInfoLoggerServer.js", "seed": "node docker/database/seed-messages.js", "docker:dev": "docker compose -f docker-compose.yaml -f docker-compose.dev.yaml up --build", @@ -48,7 +48,6 @@ "eslint-plugin-jsdoc": "65.3.1", "globals": "17.13.0", "mocha": "12.0.0", - "nodemon": "3.1.14", "nyc": "18.0.0", "puppeteer": "25.14.0", "sinon": "22.1.0", diff --git a/InfoLogger/src/index.js b/InfoLogger/src/index.js index 3ff5b7ed6..33c9fdcad 100644 --- a/InfoLogger/src/index.js +++ b/InfoLogger/src/index.js @@ -27,7 +27,16 @@ import Model from './Model.js'; const model = new Model(); const debug = true; // shows when redraw is done StatefulComponent.useRenderer(model); // Register the model for the stateful components -mount(document.body, view, model, debug); +let currentView = view; +mount(document.body, (model) => currentView(model), model, debug); // Expose model to interact with it the browser's console window.model = model; + +// Dev only: swap in the new view tree on edit and keep model state; stripped from the prod build +if (import.meta.hot) { + import.meta.hot.accept('./view.js', (newModule) => { + currentView = newModule.default; + model.notify(); + }); +}