/** * 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`); }); });