From bfc20f56bf5ee336160a61e25ab3fcaca5f4b184 Mon Sep 17 00:00:00 2001 From: gsb Date: Thu, 30 Apr 2026 22:46:03 +0000 Subject: [PATCH] Add dev server with livereload npm run dev starts a dev server on port 8080 serving the test page and ribbit dist files. Watches src/ and test/integration/ for changes, rebuilds automatically, and notifies connected browsers to reload via EventSource on port 8081. The test page includes a livereload script that auto-reloads when the dev server signals a rebuild. --- package.json | 1 + test/integration/dev-server.js | 106 +++++++++++++++++++++++++++++++++ test/integration/index.html | 5 ++ 3 files changed, 112 insertions(+) create mode 100644 test/integration/dev-server.js diff --git a/package.json b/package.json index f2adae3..66064ea 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "build:core": "esbuild src/ts/ribbit-core.ts --bundle --format=iife --global-name=ribbit --sourcemap --outfile=dist/ribbit/ribbit-core.js", "build:core-min": "esbuild src/ts/ribbit-core.ts --bundle --format=iife --global-name=ribbit --minify --outfile=dist/ribbit/ribbit-core.min.js", "build:css": "cp src/static/ribbit-core.css dist/ribbit/ && cp -r src/static/themes dist/ribbit/", + "dev": "npm run build && node test/integration/dev-server.js", "test": "npm run build && jest --verbose", "test:integration": "npm run build && node test/integration/test.js && node test/integration/test_wysiwyg.js", "test:coverage": "npm run build && jest --coverage" diff --git a/test/integration/dev-server.js b/test/integration/dev-server.js new file mode 100644 index 0000000..e0f5796 --- /dev/null +++ b/test/integration/dev-server.js @@ -0,0 +1,106 @@ +/** + * Development server with livereload. + * + * Serves the test page and ribbit dist files. Watches src/ for + * changes, rebuilds automatically, and notifies connected browsers + * to reload via a simple EventSource stream. + * + * Run: npm run dev + */ +const { createServer } = require('./server'); +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const PORT = 8080; +const WATCH_DIRS = [ + path.join(__dirname, '..', '..', 'src'), + path.join(__dirname, '..', '..', 'test', 'integration'), +]; +const DEBOUNCE_MS = 300; + +const server = createServer(PORT); +const reloadClients = []; + +// Patch the server to add the livereload endpoint +const originalServer = require('http').createServer; +const httpServer = server._server || (() => { + // Access the internal server by starting and intercepting + let captured = null; + const origListen = require('http').Server.prototype.listen; + require('http').Server.prototype.listen = function (...args) { + captured = this; + return origListen.apply(this, args); + }; + server.start(); + require('http').Server.prototype.listen = origListen; + return captured; +})(); + +// Simpler approach: create a standalone livereload server +const reloadServer = require('http').createServer((request, response) => { + response.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Access-Control-Allow-Origin': '*', + }); + reloadClients.push(response); + request.on('close', () => { + const index = reloadClients.indexOf(response); + if (index >= 0) { + reloadClients.splice(index, 1); + } + }); +}); + +function notifyReload() { + for (const client of reloadClients) { + client.write('data: reload\n\n'); + } +} + +function rebuild() { + try { + console.log('\nšŸ”Ø Rebuilding...'); + execSync('npm run build:js && npm run build:css', { + cwd: path.join(__dirname, '..', '..'), + stdio: 'pipe', + }); + console.log('āœ… Build complete'); + notifyReload(); + } catch (error) { + console.error('āŒ Build failed:', error.stderr?.toString().slice(0, 500)); + } +} + +let debounceTimer = null; +function onFileChange(filename) { + if (debounceTimer) { + clearTimeout(debounceTimer); + } + console.log(`šŸ“ Changed: ${filename}`); + debounceTimer = setTimeout(rebuild, DEBOUNCE_MS); +} + +// Watch source directories +for (const directory of WATCH_DIRS) { + if (fs.existsSync(directory)) { + fs.watch(directory, { recursive: true }, (eventType, filename) => { + if (filename && !filename.includes('node_modules')) { + onFileChange(filename); + } + }); + } +} + +server.start().then(() => { + reloadServer.listen(PORT + 1, () => { + console.log(`\n🐸 Ribbit dev server`); + console.log(` Editor: http://localhost:${PORT}`); + console.log(` Livereload: http://localhost:${PORT + 1} (EventSource)`); + console.log(` Watching: src/, test/integration/`); + console.log(`\n Add this to the page to enable livereload:`); + console.log(` \n`); + }); +}); diff --git a/test/integration/index.html b/test/integration/index.html index 29f9769..72e15d8 100644 --- a/test/integration/index.html +++ b/test/integration/index.html @@ -42,5 +42,10 @@ editor.run(); window.__ribbitEditor = editor; +