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();