UNPKG

2.44 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 isNotFound = opts.onNoMatch || is404;
45 let extensions = opts.extensions || ['html', 'htm'];
46
47 if (opts.dev) {
48 return function (req, res, next) {
49 let uri = decodeURIComponent(req.path || req.pathname || parser(req).pathname);
50 let arr = uri.includes('.') ? [uri] : toAssume(uri, extensions);
51 let file = arr.map(x => join(dir, x)).find(fs.existsSync);
52 if (!file) return next ? next() : isNotFound(res);
53 res.setHeader('Content-Type', mime.getType(file));
54 fs.createReadStream(file).pipe(res);
55 }
56 }
57
58 let setHeaders = opts.setHeaders || noop;
59 let cc = opts.maxAge != null && `public,max-age=${opts.maxAge}`;
60 if (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 if (cc) headers['Cache-Control'] = cc;
74 if (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 = decodeURIComponent(req.path || req.pathname || parser(req).pathname);
80 let data = find(pathname, extensions);
81 if (!data) return next ? next() : isNotFound(res);
82
83 setHeaders(res, pathname, data.stats);
84 res.writeHead(200, data.headers);
85
86 fs.createReadStream(data.abs).pipe(res);
87 }
88}