-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdev-server.mjs
More file actions
106 lines (91 loc) · 2.94 KB
/
Copy pathdev-server.mjs
File metadata and controls
106 lines (91 loc) · 2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#!/usr/bin/env node
import http from 'node:http';
import path from 'node:path';
import fs from 'node:fs';
import url from 'node:url';
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
const ROOT = process.cwd();
const PORT = Number(process.env.PORT || 5500);
const MIME = {
'.html': 'text/html; charset=UTF-8',
'.js': 'application/javascript; charset=UTF-8',
'.mjs': 'application/javascript; charset=UTF-8',
'.css': 'text/css; charset=UTF-8',
'.json': 'application/json; charset=UTF-8',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.map': 'application/json; charset=UTF-8',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.txt': 'text/plain; charset=UTF-8'
};
function safeJoin(root, requestPath) {
const decoded = decodeURIComponent(requestPath);
const normalized = path.posix.normalize(decoded).replace(/^\/+/, '/');
const joined = path.join(root, normalized);
if (!joined.startsWith(path.resolve(root))) {
return null;
}
return joined;
}
function sendFile(res, filePath, statusCode = 200) {
const ext = path.extname(filePath).toLowerCase();
const type = MIME[ext] || 'application/octet-stream';
fs.createReadStream(filePath)
.on('open', () => {
res.writeHead(statusCode, { 'Content-Type': type });
})
.on('error', (err) => {
console.error('Read error:', err);
res.writeHead(500, { 'Content-Type': 'text/plain; charset=UTF-8' });
res.end('500 Internal Server Error');
})
.pipe(res);
}
function exists(p) {
try { fs.accessSync(p, fs.constants.R_OK); return true; } catch { return false; }
}
function isDirectory(p) {
try { return fs.statSync(p).isDirectory(); } catch { return false; }
}
const server = http.createServer((req, res) => {
const parsed = url.parse(req.url || '/');
const pathname = parsed.pathname || '/';
const fullPath = safeJoin(ROOT, pathname);
if (!fullPath) {
return sendFile(res, path.join(ROOT, '404.html'), 404);
}
// If path exists and is a file, serve it
if (exists(fullPath) && !isDirectory(fullPath)) {
return sendFile(res, fullPath);
}
// If path is a directory
if (isDirectory(fullPath)) {
const indexPath = path.join(fullPath, 'index.html');
if (exists(indexPath)) {
return sendFile(res, indexPath);
}
// Directory without index.html → serve 404.html (SPA fallback)
const notFound = path.join(ROOT, '404.html');
if (exists(notFound)) {
return sendFile(res, notFound, 200);
}
}
// Fallback for not found paths
const notFound = path.join(ROOT, '404.html');
if (exists(notFound)) {
return sendFile(res, notFound, 404);
}
res.writeHead(404, { 'Content-Type': 'text/plain; charset=UTF-8' });
res.end('404 Not Found');
});
server.listen(PORT, () => {
console.log(`Dev server running at http://127.0.0.1:${PORT}`);
console.log(`Root: ${ROOT}`);
console.log('Fallback: 404.html for directories without index.html and not-found paths');
});