node live-server.js [path]
Created
April 9, 2025 21:51
-
-
Save yebt/531fdb3075a23bd6bd2dc3f8e475b325 to your computer and use it in GitHub Desktop.
Live server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const http = require('http'); | |
const fs = require('fs'); | |
const path = require('path'); | |
const { createServer } = require('net'); | |
// Colores para la consola | |
const colors = { | |
reset: "\x1b[0m", | |
bright: "\x1b[1m", | |
dim: "\x1b[2m", | |
underscore: "\x1b[4m", | |
blink: "\x1b[5m", | |
reverse: "\x1b[7m", | |
hidden: "\x1b[8m", | |
fg: { | |
black: "\x1b[30m", | |
red: "\x1b[31m", | |
green: "\x1b[32m", | |
yellow: "\x1b[33m", | |
blue: "\x1b[34m", | |
magenta: "\x1b[35m", | |
cyan: "\x1b[36m", | |
white: "\x1b[37m" | |
}, | |
bg: { | |
black: "\x1b[40m", | |
red: "\x1b[41m", | |
green: "\x1b[42m", | |
yellow: "\x1b[43m", | |
blue: "\x1b[44m", | |
magenta: "\x1b[45m", | |
cyan: "\x1b[46m", | |
white: "\x1b[47m" | |
} | |
}; | |
// Obtener la ruta desde los argumentos o usar el directorio actual | |
const args = process.argv.slice(2); | |
const DIRECTORY_TO_SERVE = args[0] || './'; | |
// Configuración | |
const PORT = 3000; | |
const WS_PORT = 8080; | |
// Función para crear el código del cliente de WebSocket que se inyectará en las páginas HTML | |
const getWebSocketClientCode = () => { | |
return ` | |
<script> | |
(function() { | |
// Crear una conexión WebSocket | |
const socket = new WebSocket('ws://localhost:${WS_PORT}'); | |
// Escuchar eventos del servidor | |
socket.addEventListener('message', function(event) { | |
if (event.data === 'reload') { | |
console.log('🔄 Cambios detectados, recargando página...'); | |
window.location.reload(); | |
} | |
}); | |
socket.addEventListener('close', function() { | |
console.log('❌ Conexión WebSocket cerrada. Intentando reconectar en 1 segundo...'); | |
setTimeout(function() { | |
window.location.reload(); | |
}, 1000); | |
}); | |
console.log('✅ Cliente de live-reload conectado'); | |
})(); | |
</script> | |
`; | |
}; | |
// Crear un servidor HTTP para archivos estáticos | |
const server = http.createServer((req, res) => { | |
// Convertir URL a ruta del sistema de archivos | |
let filePath = path.join(DIRECTORY_TO_SERVE, req.url === '/' ? 'index.html' : req.url); | |
// Obtener la extensión del archivo | |
const extname = path.extname(filePath); | |
// Definir tipos MIME comunes | |
const contentType = { | |
'.html': 'text/html', | |
'.js': 'text/javascript', | |
'.css': 'text/css', | |
'.json': 'application/json', | |
'.png': 'image/png', | |
'.jpg': 'image/jpg', | |
'.jpeg': 'image/jpeg', | |
'.gif': 'image/gif', | |
'.svg': 'image/svg+xml', | |
'.ico': 'image/x-icon', | |
'.woff': 'font/woff', | |
'.woff2': 'font/woff2', | |
'.ttf': 'font/ttf' | |
}[extname] || 'text/plain'; | |
// Leer el archivo | |
fs.readFile(filePath, (err, content) => { | |
if (err) { | |
if (err.code === 'ENOENT') { | |
// Archivo no encontrado | |
console.log(`${colors.fg.yellow}⚠️ 404${colors.reset} - ${req.url}`); | |
res.writeHead(404); | |
res.end('Archivo no encontrado'); | |
} else { | |
// Error del servidor | |
console.log(`${colors.fg.red}⛔ 500${colors.reset} - ${req.url} - ${err.code}`); | |
res.writeHead(500); | |
res.end(`Error del servidor: ${err.code}`); | |
} | |
} else { | |
// Éxito - devolver el contenido | |
console.log(`${colors.fg.green}✅ 200${colors.reset} - ${req.url}`); | |
res.writeHead(200, { 'Content-Type': contentType }); | |
// Inyectar el código de WebSocket en archivos HTML | |
if (extname === '.html') { | |
const htmlWithWebSocket = content.toString().replace('</body>', getWebSocketClientCode() + '</body>'); | |
res.end(htmlWithWebSocket, 'utf-8'); | |
} else { | |
res.end(content, 'utf-8'); | |
} | |
} | |
}); | |
}); | |
// Implementación básica de un servidor WebSocket | |
const wsClients = []; | |
const wsServer = createServer(socket => { | |
console.log(`${colors.fg.cyan}🔌 Nuevo cliente WebSocket conectado${colors.reset}`); | |
// Realizar el handshake WebSocket | |
socket.once('data', data => { | |
const headers = data.toString(); | |
if (headers.includes('GET') && headers.includes('Upgrade: websocket')) { | |
const key = headers.match(/Sec-WebSocket-Key: (.+)/)[1].trim(); | |
const acceptKey = require('crypto') | |
.createHash('sha1') | |
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11') | |
.digest('base64'); | |
socket.write( | |
'HTTP/1.1 101 Switching Protocols\r\n' + | |
'Upgrade: websocket\r\n' + | |
'Connection: Upgrade\r\n' + | |
`Sec-WebSocket-Accept: ${acceptKey}\r\n` + | |
'\r\n' | |
); | |
wsClients.push(socket); | |
socket.on('close', () => { | |
const index = wsClients.indexOf(socket); | |
if (index !== -1) { | |
wsClients.splice(index, 1); | |
} | |
console.log(`${colors.fg.yellow}🔌 Cliente WebSocket desconectado${colors.reset}`); | |
}); | |
} | |
}); | |
}); | |
// Función para enviar mensaje de recarga a todos los clientes | |
function notifyReload() { | |
const message = Buffer.from([ | |
0x81, // FIN + Opcode | |
0x06, // Payload length (6 bytes: "reload") | |
0x72, 0x65, 0x6c, 0x6f, 0x61, 0x64 // "reload" en ASCII | |
]); | |
wsClients.forEach(client => { | |
try { | |
client.write(message); | |
} catch (e) { | |
console.error(`${colors.fg.red}⛔ Error al enviar mensaje a cliente:${colors.reset}`, e); | |
} | |
}); | |
} | |
// Observar cambios en el directorio de archivos estáticos | |
function watchDirectory(dir) { | |
fs.watch(dir, { recursive: true }, (eventType, filename) => { | |
if (filename) { | |
console.log(`${colors.fg.magenta}🔄 Archivo modificado:${colors.reset} ${filename}`); | |
notifyReload(); | |
} | |
}); | |
console.log(`${colors.fg.cyan}👀 Observando cambios en el directorio:${colors.reset} ${dir}`); | |
} | |
// Comprobar si existe index.html, si no, crear uno básico | |
const indexPath = path.join(DIRECTORY_TO_SERVE, 'index.html'); | |
if (!fs.existsSync(indexPath)) { | |
try { | |
fs.writeFileSync( | |
indexPath, | |
'<!DOCTYPE html>\n<html>\n<head>\n <title>Live Server</title>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n <style>\n body { font-family: system-ui, -apple-system, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; line-height: 1.6; }\n code { background: #f4f4f4; padding: 2px 5px; border-radius: 3px; }\n </style>\n</head>\n<body>\n <h1>¡Live Server funcionando!</h1>\n <p>Edita este archivo y verás cómo se recarga automáticamente.</p>\n <p>Estás sirviendo archivos desde: <code>' + path.resolve(DIRECTORY_TO_SERVE) + '</code></p>\n</body>\n</html>' | |
); | |
console.log(`${colors.fg.green}✅ Se ha creado un archivo index.html básico${colors.reset}`); | |
} catch (err) { | |
console.log(`${colors.fg.yellow}⚠️ No se pudo crear index.html:${colors.reset}`, err.message); | |
} | |
} | |
// Iniciar servidores | |
server.listen(PORT, () => { | |
console.log(`\n${colors.bright}${colors.fg.green}🚀 Servidor HTTP ejecutándose en${colors.reset} ${colors.fg.cyan}http://localhost:${PORT}/${colors.reset}`); | |
}); | |
wsServer.listen(WS_PORT, () => { | |
console.log(`${colors.bright}${colors.fg.green}🔌 Servidor WebSocket ejecutándose en${colors.reset} ${colors.fg.cyan}ws://localhost:${WS_PORT}${colors.reset}`); | |
}); | |
// Iniciar observación del directorio | |
watchDirectory(DIRECTORY_TO_SERVE); | |
console.log(`\n${colors.bright}${colors.fg.green}===============================================${colors.reset}`); | |
console.log(`${colors.bright}${colors.fg.green}📡 SERVIDOR DE DESARROLLO CON RECARGA AUTOMÁTICA${colors.reset}`); | |
console.log(`${colors.bright}${colors.fg.green}===============================================${colors.reset}`); | |
console.log(`${colors.fg.yellow}1.${colors.reset} Sirviendo archivos desde: ${colors.fg.cyan}${path.resolve(DIRECTORY_TO_SERVE)}${colors.reset}`); | |
console.log(`${colors.fg.yellow}2.${colors.reset} Accede a tu sitio en: ${colors.fg.cyan}http://localhost:${PORT}${colors.reset}`); | |
console.log(`${colors.fg.yellow}3.${colors.reset} Edita cualquier archivo y observa la recarga automática`); | |
console.log(`${colors.fg.yellow}4.${colors.reset} Presiona ${colors.bright}Ctrl+C${colors.reset} para detener el servidor\n`); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment