最简单的WEB热更新调试服务器

const http = require('http');
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const os = require('os');

const host = '0.0.0.0';
const port = 3000;
const clients = [];

function handleSSE(req, res) {
    if (req.headers.accept && req.headers.accept === 'text/event-stream') {
        res.writeHead(200, {
            'Content-Type': 'text/event-stream',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'Access-Control-Allow-Origin': '*'
        });
        res.write('retry: 10000\n\n'); // 添加重连时间

        clients.push(res);

        req.on('close', () => {
            const index = clients.indexOf(res);
            if (index !== -1) clients.splice(index, 1);
        });
    } else {
        res.writeHead(404);
        res.end();
    }
}

function getIPAddress() {
    const interfaces = os.networkInterfaces();
    for (const devName in interfaces) {
        const iface = interfaces[devName];
        for (let i = 0; i < iface.length; i++) {
            const alias = iface[i];
            if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
                return alias.address;
            }
        }
    }
    return 'localhost';
}

function injectScript(content) {
    const script = `
        <script>
            (function() {
                const eventSource = new EventSource('/events');
                eventSource.onmessage = function(event) {
                    if (event.data === 'reload') window.location.reload();
                };
                eventSource.onerror = function() {
                    console.log('SSE error. Attempting reconnect...');
                    setTimeout(() => eventSource.close(), 1000);
                };
            })();
        </script>
    `;
    return content.toString().replace('</body>', `${script}</body>`);
}

const server = http.createServer((req, res) => {
    let filePath = '.' + req.url;
    if (filePath === './') filePath = './index.html';

    if (req.url === '/events') {
        handleSSE(req, res);
        return;
    }

    const extname = path.extname(filePath);
    let contentType = 'text/html';
    switch (extname) {
        case '.css': contentType = 'text/css'; break;
        case '.js': contentType = 'text/javascript'; break;
        case '.json': contentType = 'application/json'; break;
        case '.png': contentType = 'image/png'; break;
        case '.jpg': contentType = 'image/jpg'; break;
        case '.ico': contentType = 'image/x-icon'; break;
    }

    if (!fs.existsSync(filePath)) {
        if (filePath === './index.html') {
            res.writeHead(200, { 'Content-Type': 'text/html' });
            res.end(`
                <!DOCTYPE html>
                <html><body>
                    <p><strong>服务器正常,访问地址:</strong> http://${getIPAddress()}:${port}</p>
                    <script>
                        // 默认页面的SSE逻辑
                        const es = new EventSource('/events');
                        es.onmessage = e => e.data === 'reload' && location.reload();
                    </script>
                </body></html>
            `);
            return;
        }
        res.writeHead(404);
        res.end('File not found');
        return;
    }

    fs.readFile(filePath, (err, content) => {
        if (err) {
            res.writeHead(500);
            res.end('Server error: ' + err.code);
            return;
        }

        // 注入热更新脚本到HTML文件
        if (contentType === 'text/html') {
            content = injectScript(content);
        }

        res.writeHead(200, { 'Content-Type': contentType });
        res.end(content);
    });
});

server.listen(port, host, () => {
    const ip = getIPAddress();
    const url = `http://${ip}:${port}`;
    console.log(`Server running at ${url}`);

    try {
        const command = process.platform === 'win32' ? 'start' :
            process.platform === 'darwin' ? 'open' : 'xdg-open';
        exec(`${command} ${url}`, (error) => {
            if (error) console.log(`请手动访问: ${url}`);
        });
    } catch (e) {
        console.log(`请手动访问: ${url}`);
    }
});

// 文件监控
const watchedFiles = new Set();

function watchFile(file) {
    if (watchedFiles.has(file)) return;

    watchedFiles.add(file);
    console.log(`监控文件: ${file}`);

    fs.watch(file, (event) => {
        if (event === 'change') {
            console.log(`文件更新: ${file}`);
            clients.forEach(client => {
                client.write('data: reload\n\n'); // 发送SSE事件
            });
        }
    });
}

// 初始化监控
function initWatching() {
    // 监控所有文件类型
    fs.readdir('.', (err, files) => {
        if (err) return;

        files.forEach(file => {
            if (fs.statSync(file).isFile()) {
                watchFile(file);
            }
        });
    });

    // 监控新文件
    fs.watch('.', (event, filename) => {
        if (event === 'rename' && !watchedFiles.has(filename) && fs.existsSync(filename)) {
            watchFile(filename);
        }
    });
}

initWatching();

 

posted @ 2025-07-31 09:39  华腾智算  阅读(7)  评论(0)    收藏  举报
https://damo.alibaba.com/ https://tianchi.aliyun.com/course?spm=5176.21206777.J_3941670930.5.87dc17c9BZNvLL