Skip to content

Instantly share code, notes, and snippets.

@yebt
Created April 9, 2025 21:51
Show Gist options
  • Save yebt/531fdb3075a23bd6bd2dc3f8e475b325 to your computer and use it in GitHub Desktop.
Save yebt/531fdb3075a23bd6bd2dc3f8e475b325 to your computer and use it in GitHub Desktop.
Live server
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`);

Usage

node live-server.js [path]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment