1 | const fs = require('fs');
|
2 | const { join } = require('path');
|
3 | const tglob = require('tiny-glob/sync');
|
4 | const parseurl = require('parseurl');
|
5 | const mime = require('mime/lite');
|
6 |
|
7 | const FILES = {};
|
8 | const noop = () => {};
|
9 |
|
10 | function 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 |
|
30 | function toEtag(obj) {
|
31 | return `W/"${obj.size.toString(16)}-${obj.mtime.getTime().toString(16)}"`;
|
32 | }
|
33 |
|
34 | function is404(res) {
|
35 | return (res.statusCode=404,res.end());
|
36 | }
|
37 |
|
38 | module.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 | }
|