UNPKG

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