Skip to content

Instantly share code, notes, and snippets.

@Tuarisa
Created December 9, 2023 17:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Tuarisa/651e3b35b99192e8cc453ac56755940f to your computer and use it in GitHub Desktop.
Save Tuarisa/651e3b35b99192e8cc453ac56755940f to your computer and use it in GitHub Desktop.
Live reload server for static folder
import http from 'node:http';
import fs from 'node:fs';
import path from 'node:path';
import { WebSocketServer } from 'ws';
const connections = new Map();
let currentConnectionNumber = 0
const staticFolder = './dist'
const MIME_TYPES = {
html: 'text/html; charset=UTF-8',
json: 'application/json; charset=UTF-8',
js: 'application/javascript; charset=UTF-8',
css: 'text/css',
png: 'image/png',
ico: 'image/x-icon',
svg: 'image/svg+xml',
};
const HEADERS = {
'X-XSS-Protection': '1; mode=block',
'X-Content-Type-Options': 'nosniff',
'Strict-Transport-Security': 'max-age=31536000; includeSubdomains; preload',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
const serveStatic = (staticPath) => async (req, res) => {
const url = req.url === '/' ? '/index.html' : req.url;
const filePath = path.join(staticPath, url);
try {
let data = await fs.promises.readFile(filePath);
const fileExt = path.extname(filePath).substring(1);
const mimeType = MIME_TYPES[fileExt] || MIME_TYPES.html;
if (fileExt === 'html') {
data = `${data}
<script>
let ws = new WebSocket('ws://' + window.location.host + '/path');
console.log('Live reload server connected');
ws.onmessage = (event) => {
if (event.data === 'reload') window.location.reload();
};
</script>`
}
res.writeHead(200, { ...HEADERS, 'Content-Type': mimeType });
res.end(data);
} catch (err) {
res.statusCode = 404;
res.end('"File is not found"');
}
};
class Watcher {
constructor(connection) {
this.connection = connection;
this.connectionNumber = currentConnectionNumber++;
connections.set(this.connectionNumber, connection);
}
destroy() {
connections.delete(this.connectionNumber);
}
}
const watch = (path) => {
fs.watch(path, (event, file) => {
console.log(file)
connections.forEach(connection => {
connection.send('reload');
})
});
};
class Server {
constructor() {
const staticPath = path.join('.', staticFolder);
this.staticHandler = serveStatic(staticPath);
this.httpServer = http.createServer();
const port = 4000;
this.listen(port);
console.log(`API on port ${port}`);
}
listen(port) {
this.httpServer.on('request', async (req, res) => {
this.staticHandler(req, res);
});
const wsServer = new WebSocketServer({ server: this.httpServer });
wsServer.on('connection', (connection, req) => {
const watcherConnection = new Watcher(connection)
connection.on('message', (data) => {
connection.send('reply')
});
connection.on('close', () => {
watcherConnection.destroy()
});
});
this.httpServer.listen(port);
}
}
watch(staticFolder);
new Server();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment