UNPKG

1.85 kBJavaScriptView Raw
1const fs = require('fs');
2const { join } = require('path');
3const tglob = require('tiny-glob/sync');
4const parseurl = require('parseurl');
5const mime = require('mime/lite');
6
7const FILES = {};
8const noop = () => {};
9
10function find(uri, extns) {
11 if (!!~uri.lastIndexOf('.')) return FILES[uri];
12
13 let i, j, x, arr=[], data, len=uri.length-1;
14 if (uri.charCodeAt(len) === 47) uri=uri.substring(0, len);
15
16 let tmp = `${uri}/index`;
17 for (i=0; i < extns.length; i++) {
18 x = '.' + extns[i];
19 if (uri) arr.push(uri + x);
20 arr.push(tmp + x);
21 }
22
23 for (i=0; i < arr.length; i++) {
24 if (data=FILES[arr[i]]) break;
25 }
26
27 return data;
28}
29
30function toEtag(obj) {
31 return `W/"${obj.size.toString(16)}-${obj.mtime.getTime().toString(16)}"`;
32}
33
34function is404(res) {
35 return (res.statusCode=404,res.end());
36}
37
38module.exports = function (dir, opts={}) {
39 let cc = opts.maxAge && `public,max-age=${opts.maxAge}`;
40 cc && opts.immutable && (cc += ',immutable');
41
42 opts.cwd = dir;
43 let abs, stats, headers;
44 opts.dot = !!opts.dotfiles;
45 tglob('**/*.*', opts).forEach(str => {
46 abs = join(dir, str);
47 stats = fs.statSync(abs);
48 headers = {
49 'content-length': stats.size,
50 'content-type': mime.getType(str),
51 'last-modified': stats.mtime.toUTCString()
52 };
53 cc && (headers['cache-control'] = cc);
54 opts.etag && (headers['etag'] = toEtag(stats));
55 FILES['/' + str] = { abs, stats, headers };
56 });
57
58 let notFound = opts.onNoMatch || is404;
59 let setHeaders = opts.setHeaders || noop;
60 let extensions = opts.extensions || ['html', 'htm'];
61
62 return function (req, res, next) {
63 let pathname = req.path || req.pathname || parseurl(req).pathname;
64 let data = find(pathname, extensions);
65 if (!data) return next ? next() : notFound(res);
66
67 setHeaders(res, pathname, data.stats);
68 res.writeHead(200, data.headers);
69
70 fs.createReadStream(data.abs).pipe(res);
71 }
72}