Initial commit

This commit is contained in:
2026-01-18 21:01:43 +03:00
commit 455b6df2ba
3329 changed files with 1143638 additions and 0 deletions
+194
View File
@@ -0,0 +1,194 @@
const fs = require('fs');
const { join, normalize, resolve } = require('path');
const { totalist } = require('totalist/sync');
const { parse } = require('@polka/url');
const { lookup } = require('mrmime');
const noop = () => {};
function isMatch(uri, arr) {
for (let i=0; i < arr.length; i++) {
if (arr[i].test(uri)) return true;
}
}
function toAssume(uri, extns) {
let i=0, x, len=uri.length - 1;
if (uri.charCodeAt(len) === 47) {
uri = uri.substring(0, len);
}
let arr=[], tmp=`${uri}/index`;
for (; i < extns.length; i++) {
x = extns[i] ? `.${extns[i]}` : '';
if (uri) arr.push(uri + x);
arr.push(tmp + x);
}
return arr;
}
function viaCache(cache, uri, extns) {
let i=0, data, arr=toAssume(uri, extns);
for (; i < arr.length; i++) {
if (data = cache[arr[i]]) return data;
}
}
function viaLocal(dir, isEtag, uri, extns) {
let i=0, arr=toAssume(uri, extns);
let abs, stats, name, headers;
for (; i < arr.length; i++) {
abs = normalize(join(dir, name=arr[i]));
if (abs.startsWith(dir) && fs.existsSync(abs)) {
stats = fs.statSync(abs);
if (stats.isDirectory()) continue;
headers = toHeaders(name, stats, isEtag);
headers['Cache-Control'] = isEtag ? 'no-cache' : 'no-store';
return { abs, stats, headers };
}
}
}
function is404(req, res) {
return (res.statusCode=404,res.end());
}
function send(req, res, file, stats, headers) {
let code=200, tmp, opts={};
headers = { ...headers };
for (let key in headers) {
tmp = res.getHeader(key);
if (tmp) headers[key] = tmp;
}
if (tmp = res.getHeader('content-type')) {
headers['Content-Type'] = tmp;
}
if (req.headers.range) {
code = 206;
let [x, y] = req.headers.range.replace('bytes=', '').split('-');
let end = opts.end = parseInt(y, 10) || stats.size - 1;
let start = opts.start = parseInt(x, 10) || 0;
if (end >= stats.size) {
end = stats.size - 1;
}
if (start >= stats.size) {
res.setHeader('Content-Range', `bytes */${stats.size}`);
res.statusCode = 416;
return res.end();
}
headers['Content-Range'] = `bytes ${start}-${end}/${stats.size}`;
headers['Content-Length'] = (end - start + 1);
headers['Accept-Ranges'] = 'bytes';
}
res.writeHead(code, headers);
fs.createReadStream(file, opts).pipe(res);
}
const ENCODING = {
'.br': 'br',
'.gz': 'gzip',
};
function toHeaders(name, stats, isEtag) {
let enc = ENCODING[name.slice(-3)];
let ctype = lookup(name.slice(0, enc && -3)) || '';
if (ctype === 'text/html') ctype += ';charset=utf-8';
let headers = {
'Content-Length': stats.size,
'Content-Type': ctype,
'Last-Modified': stats.mtime.toUTCString(),
};
if (enc) headers['Content-Encoding'] = enc;
if (isEtag) headers['ETag'] = `W/"${stats.size}-${stats.mtime.getTime()}"`;
return headers;
}
module.exports = function (dir, opts={}) {
dir = resolve(dir || '.');
let isNotFound = opts.onNoMatch || is404;
let setHeaders = opts.setHeaders || noop;
let extensions = opts.extensions || ['html', 'htm'];
let gzips = opts.gzip && extensions.map(x => `${x}.gz`).concat('gz');
let brots = opts.brotli && extensions.map(x => `${x}.br`).concat('br');
const FILES = {};
let fallback = '/';
let isEtag = !!opts.etag;
let isSPA = !!opts.single;
if (typeof opts.single === 'string') {
let idx = opts.single.lastIndexOf('.');
fallback += !!~idx ? opts.single.substring(0, idx) : opts.single;
}
let ignores = [];
if (opts.ignores !== false) {
ignores.push(/[/]([A-Za-z\s\d~$._-]+\.\w+){1,}$/); // any extn
if (opts.dotfiles) ignores.push(/\/\.\w/);
else ignores.push(/\/\.well-known/);
[].concat(opts.ignores || []).forEach(x => {
ignores.push(new RegExp(x, 'i'));
});
}
let cc = opts.maxAge != null && `public,max-age=${opts.maxAge}`;
if (cc && opts.immutable) cc += ',immutable';
else if (cc && opts.maxAge === 0) cc += ',must-revalidate';
if (!opts.dev) {
totalist(dir, (name, abs, stats) => {
if (/\.well-known[\\+\/]/.test(name)) {} // keep
else if (!opts.dotfiles && /(^\.|[\\+|\/+]\.)/.test(name)) return;
let headers = toHeaders(name, stats, isEtag);
if (cc) headers['Cache-Control'] = cc;
FILES['/' + name.normalize().replace(/\\+/g, '/')] = { abs, stats, headers };
});
}
let lookup = opts.dev ? viaLocal.bind(0, dir, isEtag) : viaCache.bind(0, FILES);
return function (req, res, next) {
let extns = [''];
let pathname = parse(req).pathname;
let val = req.headers['accept-encoding'] || '';
if (gzips && val.includes('gzip')) extns.unshift(...gzips);
if (brots && /(br|brotli)/i.test(val)) extns.unshift(...brots);
extns.push(...extensions); // [...br, ...gz, orig, ...exts]
if (pathname.indexOf('%') !== -1) {
try { pathname = decodeURI(pathname) }
catch (err) { /* malform uri */ }
}
let data = lookup(pathname, extns) || isSPA && !isMatch(pathname, ignores) && lookup(fallback, extns);
if (!data) return next ? next() : isNotFound(req, res);
if (isEtag && req.headers['if-none-match'] === data.headers['ETag']) {
res.writeHead(304);
return res.end();
}
if (gzips || brots) {
res.setHeader('Vary', 'Accept-Encoding');
}
setHeaders(res, pathname, data.stats);
send(req, res, data.abs, data.stats, data.headers);
};
}
+194
View File
@@ -0,0 +1,194 @@
import * as fs from 'fs';
import { join, normalize, resolve } from 'path';
import { totalist } from 'totalist/sync';
import { parse } from '@polka/url';
import { lookup } from 'mrmime';
const noop = () => {};
function isMatch(uri, arr) {
for (let i=0; i < arr.length; i++) {
if (arr[i].test(uri)) return true;
}
}
function toAssume(uri, extns) {
let i=0, x, len=uri.length - 1;
if (uri.charCodeAt(len) === 47) {
uri = uri.substring(0, len);
}
let arr=[], tmp=`${uri}/index`;
for (; i < extns.length; i++) {
x = extns[i] ? `.${extns[i]}` : '';
if (uri) arr.push(uri + x);
arr.push(tmp + x);
}
return arr;
}
function viaCache(cache, uri, extns) {
let i=0, data, arr=toAssume(uri, extns);
for (; i < arr.length; i++) {
if (data = cache[arr[i]]) return data;
}
}
function viaLocal(dir, isEtag, uri, extns) {
let i=0, arr=toAssume(uri, extns);
let abs, stats, name, headers;
for (; i < arr.length; i++) {
abs = normalize(join(dir, name=arr[i]));
if (abs.startsWith(dir) && fs.existsSync(abs)) {
stats = fs.statSync(abs);
if (stats.isDirectory()) continue;
headers = toHeaders(name, stats, isEtag);
headers['Cache-Control'] = isEtag ? 'no-cache' : 'no-store';
return { abs, stats, headers };
}
}
}
function is404(req, res) {
return (res.statusCode=404,res.end());
}
function send(req, res, file, stats, headers) {
let code=200, tmp, opts={};
headers = { ...headers };
for (let key in headers) {
tmp = res.getHeader(key);
if (tmp) headers[key] = tmp;
}
if (tmp = res.getHeader('content-type')) {
headers['Content-Type'] = tmp;
}
if (req.headers.range) {
code = 206;
let [x, y] = req.headers.range.replace('bytes=', '').split('-');
let end = opts.end = parseInt(y, 10) || stats.size - 1;
let start = opts.start = parseInt(x, 10) || 0;
if (end >= stats.size) {
end = stats.size - 1;
}
if (start >= stats.size) {
res.setHeader('Content-Range', `bytes */${stats.size}`);
res.statusCode = 416;
return res.end();
}
headers['Content-Range'] = `bytes ${start}-${end}/${stats.size}`;
headers['Content-Length'] = (end - start + 1);
headers['Accept-Ranges'] = 'bytes';
}
res.writeHead(code, headers);
fs.createReadStream(file, opts).pipe(res);
}
const ENCODING = {
'.br': 'br',
'.gz': 'gzip',
};
function toHeaders(name, stats, isEtag) {
let enc = ENCODING[name.slice(-3)];
let ctype = lookup(name.slice(0, enc && -3)) || '';
if (ctype === 'text/html') ctype += ';charset=utf-8';
let headers = {
'Content-Length': stats.size,
'Content-Type': ctype,
'Last-Modified': stats.mtime.toUTCString(),
};
if (enc) headers['Content-Encoding'] = enc;
if (isEtag) headers['ETag'] = `W/"${stats.size}-${stats.mtime.getTime()}"`;
return headers;
}
export default function (dir, opts={}) {
dir = resolve(dir || '.');
let isNotFound = opts.onNoMatch || is404;
let setHeaders = opts.setHeaders || noop;
let extensions = opts.extensions || ['html', 'htm'];
let gzips = opts.gzip && extensions.map(x => `${x}.gz`).concat('gz');
let brots = opts.brotli && extensions.map(x => `${x}.br`).concat('br');
const FILES = {};
let fallback = '/';
let isEtag = !!opts.etag;
let isSPA = !!opts.single;
if (typeof opts.single === 'string') {
let idx = opts.single.lastIndexOf('.');
fallback += !!~idx ? opts.single.substring(0, idx) : opts.single;
}
let ignores = [];
if (opts.ignores !== false) {
ignores.push(/[/]([A-Za-z\s\d~$._-]+\.\w+){1,}$/); // any extn
if (opts.dotfiles) ignores.push(/\/\.\w/);
else ignores.push(/\/\.well-known/);
[].concat(opts.ignores || []).forEach(x => {
ignores.push(new RegExp(x, 'i'));
});
}
let cc = opts.maxAge != null && `public,max-age=${opts.maxAge}`;
if (cc && opts.immutable) cc += ',immutable';
else if (cc && opts.maxAge === 0) cc += ',must-revalidate';
if (!opts.dev) {
totalist(dir, (name, abs, stats) => {
if (/\.well-known[\\+\/]/.test(name)) {} // keep
else if (!opts.dotfiles && /(^\.|[\\+|\/+]\.)/.test(name)) return;
let headers = toHeaders(name, stats, isEtag);
if (cc) headers['Cache-Control'] = cc;
FILES['/' + name.normalize().replace(/\\+/g, '/')] = { abs, stats, headers };
});
}
let lookup = opts.dev ? viaLocal.bind(0, dir, isEtag) : viaCache.bind(0, FILES);
return function (req, res, next) {
let extns = [''];
let pathname = parse(req).pathname;
let val = req.headers['accept-encoding'] || '';
if (gzips && val.includes('gzip')) extns.unshift(...gzips);
if (brots && /(br|brotli)/i.test(val)) extns.unshift(...brots);
extns.push(...extensions); // [...br, ...gz, orig, ...exts]
if (pathname.indexOf('%') !== -1) {
try { pathname = decodeURI(pathname) }
catch (err) { /* malform uri */ }
}
let data = lookup(pathname, extns) || isSPA && !isMatch(pathname, ignores) && lookup(fallback, extns);
if (!data) return next ? next() : isNotFound(req, res);
if (isEtag && req.headers['if-none-match'] === data.headers['ETag']) {
res.writeHead(304);
return res.end();
}
if (gzips || brots) {
res.setHeader('Vary', 'Accept-Encoding');
}
setHeaders(res, pathname, data.stats);
send(req, res, data.abs, data.stats, data.headers);
};
}
+2
View File
@@ -0,0 +1,2 @@
export const mimes: Record<string, string>;
export function lookup(extension: string): string | undefined;
+449
View File
@@ -0,0 +1,449 @@
const mimes = {
"3g2": "video/3gpp2",
"3gp": "video/3gpp",
"3gpp": "video/3gpp",
"3mf": "model/3mf",
"aac": "audio/aac",
"ac": "application/pkix-attr-cert",
"adp": "audio/adpcm",
"adts": "audio/aac",
"ai": "application/postscript",
"aml": "application/automationml-aml+xml",
"amlx": "application/automationml-amlx+zip",
"amr": "audio/amr",
"apng": "image/apng",
"appcache": "text/cache-manifest",
"appinstaller": "application/appinstaller",
"appx": "application/appx",
"appxbundle": "application/appxbundle",
"asc": "application/pgp-keys",
"atom": "application/atom+xml",
"atomcat": "application/atomcat+xml",
"atomdeleted": "application/atomdeleted+xml",
"atomsvc": "application/atomsvc+xml",
"au": "audio/basic",
"avci": "image/avci",
"avcs": "image/avcs",
"avif": "image/avif",
"aw": "application/applixware",
"bdoc": "application/bdoc",
"bin": "application/octet-stream",
"bmp": "image/bmp",
"bpk": "application/octet-stream",
"btf": "image/prs.btif",
"btif": "image/prs.btif",
"buffer": "application/octet-stream",
"ccxml": "application/ccxml+xml",
"cdfx": "application/cdfx+xml",
"cdmia": "application/cdmi-capability",
"cdmic": "application/cdmi-container",
"cdmid": "application/cdmi-domain",
"cdmio": "application/cdmi-object",
"cdmiq": "application/cdmi-queue",
"cer": "application/pkix-cert",
"cgm": "image/cgm",
"cjs": "application/node",
"class": "application/java-vm",
"coffee": "text/coffeescript",
"conf": "text/plain",
"cpl": "application/cpl+xml",
"cpt": "application/mac-compactpro",
"crl": "application/pkix-crl",
"css": "text/css",
"csv": "text/csv",
"cu": "application/cu-seeme",
"cwl": "application/cwl",
"cww": "application/prs.cww",
"davmount": "application/davmount+xml",
"dbk": "application/docbook+xml",
"deb": "application/octet-stream",
"def": "text/plain",
"deploy": "application/octet-stream",
"dib": "image/bmp",
"disposition-notification": "message/disposition-notification",
"dist": "application/octet-stream",
"distz": "application/octet-stream",
"dll": "application/octet-stream",
"dmg": "application/octet-stream",
"dms": "application/octet-stream",
"doc": "application/msword",
"dot": "application/msword",
"dpx": "image/dpx",
"drle": "image/dicom-rle",
"dsc": "text/prs.lines.tag",
"dssc": "application/dssc+der",
"dtd": "application/xml-dtd",
"dump": "application/octet-stream",
"dwd": "application/atsc-dwd+xml",
"ear": "application/java-archive",
"ecma": "application/ecmascript",
"elc": "application/octet-stream",
"emf": "image/emf",
"eml": "message/rfc822",
"emma": "application/emma+xml",
"emotionml": "application/emotionml+xml",
"eps": "application/postscript",
"epub": "application/epub+zip",
"exe": "application/octet-stream",
"exi": "application/exi",
"exp": "application/express",
"exr": "image/aces",
"ez": "application/andrew-inset",
"fdf": "application/fdf",
"fdt": "application/fdt+xml",
"fits": "image/fits",
"g3": "image/g3fax",
"gbr": "application/rpki-ghostbusters",
"geojson": "application/geo+json",
"gif": "image/gif",
"glb": "model/gltf-binary",
"gltf": "model/gltf+json",
"gml": "application/gml+xml",
"gpx": "application/gpx+xml",
"gram": "application/srgs",
"grxml": "application/srgs+xml",
"gxf": "application/gxf",
"gz": "application/gzip",
"h261": "video/h261",
"h263": "video/h263",
"h264": "video/h264",
"heic": "image/heic",
"heics": "image/heic-sequence",
"heif": "image/heif",
"heifs": "image/heif-sequence",
"hej2": "image/hej2k",
"held": "application/atsc-held+xml",
"hjson": "application/hjson",
"hlp": "application/winhlp",
"hqx": "application/mac-binhex40",
"hsj2": "image/hsj2",
"htm": "text/html",
"html": "text/html",
"ics": "text/calendar",
"ief": "image/ief",
"ifb": "text/calendar",
"iges": "model/iges",
"igs": "model/iges",
"img": "application/octet-stream",
"in": "text/plain",
"ini": "text/plain",
"ink": "application/inkml+xml",
"inkml": "application/inkml+xml",
"ipfix": "application/ipfix",
"iso": "application/octet-stream",
"its": "application/its+xml",
"jade": "text/jade",
"jar": "application/java-archive",
"jhc": "image/jphc",
"jls": "image/jls",
"jp2": "image/jp2",
"jpe": "image/jpeg",
"jpeg": "image/jpeg",
"jpf": "image/jpx",
"jpg": "image/jpeg",
"jpg2": "image/jp2",
"jpgm": "image/jpm",
"jpgv": "video/jpeg",
"jph": "image/jph",
"jpm": "image/jpm",
"jpx": "image/jpx",
"js": "text/javascript",
"json": "application/json",
"json5": "application/json5",
"jsonld": "application/ld+json",
"jsonml": "application/jsonml+json",
"jsx": "text/jsx",
"jt": "model/jt",
"jxl": "image/jxl",
"jxr": "image/jxr",
"jxra": "image/jxra",
"jxrs": "image/jxrs",
"jxs": "image/jxs",
"jxsc": "image/jxsc",
"jxsi": "image/jxsi",
"jxss": "image/jxss",
"kar": "audio/midi",
"ktx": "image/ktx",
"ktx2": "image/ktx2",
"less": "text/less",
"lgr": "application/lgr+xml",
"list": "text/plain",
"litcoffee": "text/coffeescript",
"log": "text/plain",
"lostxml": "application/lost+xml",
"lrf": "application/octet-stream",
"m1v": "video/mpeg",
"m21": "application/mp21",
"m2a": "audio/mpeg",
"m2t": "video/mp2t",
"m2ts": "video/mp2t",
"m2v": "video/mpeg",
"m3a": "audio/mpeg",
"m4a": "audio/mp4",
"m4p": "application/mp4",
"m4s": "video/iso.segment",
"ma": "application/mathematica",
"mads": "application/mads+xml",
"maei": "application/mmt-aei+xml",
"man": "text/troff",
"manifest": "text/cache-manifest",
"map": "application/json",
"mar": "application/octet-stream",
"markdown": "text/markdown",
"mathml": "application/mathml+xml",
"mb": "application/mathematica",
"mbox": "application/mbox",
"md": "text/markdown",
"mdx": "text/mdx",
"me": "text/troff",
"mesh": "model/mesh",
"meta4": "application/metalink4+xml",
"metalink": "application/metalink+xml",
"mets": "application/mets+xml",
"mft": "application/rpki-manifest",
"mid": "audio/midi",
"midi": "audio/midi",
"mime": "message/rfc822",
"mj2": "video/mj2",
"mjp2": "video/mj2",
"mjs": "text/javascript",
"mml": "text/mathml",
"mods": "application/mods+xml",
"mov": "video/quicktime",
"mp2": "audio/mpeg",
"mp21": "application/mp21",
"mp2a": "audio/mpeg",
"mp3": "audio/mpeg",
"mp4": "video/mp4",
"mp4a": "audio/mp4",
"mp4s": "application/mp4",
"mp4v": "video/mp4",
"mpd": "application/dash+xml",
"mpe": "video/mpeg",
"mpeg": "video/mpeg",
"mpf": "application/media-policy-dataset+xml",
"mpg": "video/mpeg",
"mpg4": "video/mp4",
"mpga": "audio/mpeg",
"mpp": "application/dash-patch+xml",
"mrc": "application/marc",
"mrcx": "application/marcxml+xml",
"ms": "text/troff",
"mscml": "application/mediaservercontrol+xml",
"msh": "model/mesh",
"msi": "application/octet-stream",
"msix": "application/msix",
"msixbundle": "application/msixbundle",
"msm": "application/octet-stream",
"msp": "application/octet-stream",
"mtl": "model/mtl",
"mts": "video/mp2t",
"musd": "application/mmt-usd+xml",
"mxf": "application/mxf",
"mxmf": "audio/mobile-xmf",
"mxml": "application/xv+xml",
"n3": "text/n3",
"nb": "application/mathematica",
"nq": "application/n-quads",
"nt": "application/n-triples",
"obj": "model/obj",
"oda": "application/oda",
"oga": "audio/ogg",
"ogg": "audio/ogg",
"ogv": "video/ogg",
"ogx": "application/ogg",
"omdoc": "application/omdoc+xml",
"onepkg": "application/onenote",
"onetmp": "application/onenote",
"onetoc": "application/onenote",
"onetoc2": "application/onenote",
"opf": "application/oebps-package+xml",
"opus": "audio/ogg",
"otf": "font/otf",
"owl": "application/rdf+xml",
"oxps": "application/oxps",
"p10": "application/pkcs10",
"p7c": "application/pkcs7-mime",
"p7m": "application/pkcs7-mime",
"p7s": "application/pkcs7-signature",
"p8": "application/pkcs8",
"pdf": "application/pdf",
"pfr": "application/font-tdpfr",
"pgp": "application/pgp-encrypted",
"pkg": "application/octet-stream",
"pki": "application/pkixcmp",
"pkipath": "application/pkix-pkipath",
"pls": "application/pls+xml",
"png": "image/png",
"prc": "model/prc",
"prf": "application/pics-rules",
"provx": "application/provenance+xml",
"ps": "application/postscript",
"pskcxml": "application/pskc+xml",
"pti": "image/prs.pti",
"qt": "video/quicktime",
"raml": "application/raml+yaml",
"rapd": "application/route-apd+xml",
"rdf": "application/rdf+xml",
"relo": "application/p2p-overlay+xml",
"rif": "application/reginfo+xml",
"rl": "application/resource-lists+xml",
"rld": "application/resource-lists-diff+xml",
"rmi": "audio/midi",
"rnc": "application/relax-ng-compact-syntax",
"rng": "application/xml",
"roa": "application/rpki-roa",
"roff": "text/troff",
"rq": "application/sparql-query",
"rs": "application/rls-services+xml",
"rsat": "application/atsc-rsat+xml",
"rsd": "application/rsd+xml",
"rsheet": "application/urc-ressheet+xml",
"rss": "application/rss+xml",
"rtf": "text/rtf",
"rtx": "text/richtext",
"rusd": "application/route-usd+xml",
"s3m": "audio/s3m",
"sbml": "application/sbml+xml",
"scq": "application/scvp-cv-request",
"scs": "application/scvp-cv-response",
"sdp": "application/sdp",
"senmlx": "application/senml+xml",
"sensmlx": "application/sensml+xml",
"ser": "application/java-serialized-object",
"setpay": "application/set-payment-initiation",
"setreg": "application/set-registration-initiation",
"sgi": "image/sgi",
"sgm": "text/sgml",
"sgml": "text/sgml",
"shex": "text/shex",
"shf": "application/shf+xml",
"shtml": "text/html",
"sieve": "application/sieve",
"sig": "application/pgp-signature",
"sil": "audio/silk",
"silo": "model/mesh",
"siv": "application/sieve",
"slim": "text/slim",
"slm": "text/slim",
"sls": "application/route-s-tsid+xml",
"smi": "application/smil+xml",
"smil": "application/smil+xml",
"snd": "audio/basic",
"so": "application/octet-stream",
"spdx": "text/spdx",
"spp": "application/scvp-vp-response",
"spq": "application/scvp-vp-request",
"spx": "audio/ogg",
"sql": "application/sql",
"sru": "application/sru+xml",
"srx": "application/sparql-results+xml",
"ssdl": "application/ssdl+xml",
"ssml": "application/ssml+xml",
"stk": "application/hyperstudio",
"stl": "model/stl",
"stpx": "model/step+xml",
"stpxz": "model/step-xml+zip",
"stpz": "model/step+zip",
"styl": "text/stylus",
"stylus": "text/stylus",
"svg": "image/svg+xml",
"svgz": "image/svg+xml",
"swidtag": "application/swid+xml",
"t": "text/troff",
"t38": "image/t38",
"td": "application/urc-targetdesc+xml",
"tei": "application/tei+xml",
"teicorpus": "application/tei+xml",
"text": "text/plain",
"tfi": "application/thraud+xml",
"tfx": "image/tiff-fx",
"tif": "image/tiff",
"tiff": "image/tiff",
"toml": "application/toml",
"tr": "text/troff",
"trig": "application/trig",
"ts": "video/mp2t",
"tsd": "application/timestamped-data",
"tsv": "text/tab-separated-values",
"ttc": "font/collection",
"ttf": "font/ttf",
"ttl": "text/turtle",
"ttml": "application/ttml+xml",
"txt": "text/plain",
"u3d": "model/u3d",
"u8dsn": "message/global-delivery-status",
"u8hdr": "message/global-headers",
"u8mdn": "message/global-disposition-notification",
"u8msg": "message/global",
"ubj": "application/ubjson",
"uri": "text/uri-list",
"uris": "text/uri-list",
"urls": "text/uri-list",
"vcard": "text/vcard",
"vrml": "model/vrml",
"vtt": "text/vtt",
"vxml": "application/voicexml+xml",
"war": "application/java-archive",
"wasm": "application/wasm",
"wav": "audio/wav",
"weba": "audio/webm",
"webm": "video/webm",
"webmanifest": "application/manifest+json",
"webp": "image/webp",
"wgsl": "text/wgsl",
"wgt": "application/widget",
"wif": "application/watcherinfo+xml",
"wmf": "image/wmf",
"woff": "font/woff",
"woff2": "font/woff2",
"wrl": "model/vrml",
"wsdl": "application/wsdl+xml",
"wspolicy": "application/wspolicy+xml",
"x3d": "model/x3d+xml",
"x3db": "model/x3d+fastinfoset",
"x3dbz": "model/x3d+binary",
"x3dv": "model/x3d-vrml",
"x3dvz": "model/x3d+vrml",
"x3dz": "model/x3d+xml",
"xaml": "application/xaml+xml",
"xav": "application/xcap-att+xml",
"xca": "application/xcap-caps+xml",
"xcs": "application/calendar+xml",
"xdf": "application/xcap-diff+xml",
"xdssc": "application/dssc+xml",
"xel": "application/xcap-el+xml",
"xenc": "application/xenc+xml",
"xer": "application/patch-ops-error+xml",
"xfdf": "application/xfdf",
"xht": "application/xhtml+xml",
"xhtml": "application/xhtml+xml",
"xhvml": "application/xv+xml",
"xlf": "application/xliff+xml",
"xm": "audio/xm",
"xml": "text/xml",
"xns": "application/xcap-ns+xml",
"xop": "application/xop+xml",
"xpl": "application/xproc+xml",
"xsd": "application/xml",
"xsf": "application/prs.xsf+xml",
"xsl": "application/xml",
"xslt": "application/xml",
"xspf": "application/xspf+xml",
"xvm": "application/xv+xml",
"xvml": "application/xv+xml",
"yaml": "text/yaml",
"yang": "application/yang",
"yin": "application/yin+xml",
"yml": "text/yaml",
"zip": "application/zip"
};
function lookup(extn) {
let tmp = ('' + extn).trim().toLowerCase();
let idx = tmp.lastIndexOf('.');
return mimes[!~idx ? tmp : tmp.substring(++idx)];
}
exports.mimes = mimes;
exports.lookup = lookup;
+448
View File
@@ -0,0 +1,448 @@
const mimes = {
"3g2": "video/3gpp2",
"3gp": "video/3gpp",
"3gpp": "video/3gpp",
"3mf": "model/3mf",
"aac": "audio/aac",
"ac": "application/pkix-attr-cert",
"adp": "audio/adpcm",
"adts": "audio/aac",
"ai": "application/postscript",
"aml": "application/automationml-aml+xml",
"amlx": "application/automationml-amlx+zip",
"amr": "audio/amr",
"apng": "image/apng",
"appcache": "text/cache-manifest",
"appinstaller": "application/appinstaller",
"appx": "application/appx",
"appxbundle": "application/appxbundle",
"asc": "application/pgp-keys",
"atom": "application/atom+xml",
"atomcat": "application/atomcat+xml",
"atomdeleted": "application/atomdeleted+xml",
"atomsvc": "application/atomsvc+xml",
"au": "audio/basic",
"avci": "image/avci",
"avcs": "image/avcs",
"avif": "image/avif",
"aw": "application/applixware",
"bdoc": "application/bdoc",
"bin": "application/octet-stream",
"bmp": "image/bmp",
"bpk": "application/octet-stream",
"btf": "image/prs.btif",
"btif": "image/prs.btif",
"buffer": "application/octet-stream",
"ccxml": "application/ccxml+xml",
"cdfx": "application/cdfx+xml",
"cdmia": "application/cdmi-capability",
"cdmic": "application/cdmi-container",
"cdmid": "application/cdmi-domain",
"cdmio": "application/cdmi-object",
"cdmiq": "application/cdmi-queue",
"cer": "application/pkix-cert",
"cgm": "image/cgm",
"cjs": "application/node",
"class": "application/java-vm",
"coffee": "text/coffeescript",
"conf": "text/plain",
"cpl": "application/cpl+xml",
"cpt": "application/mac-compactpro",
"crl": "application/pkix-crl",
"css": "text/css",
"csv": "text/csv",
"cu": "application/cu-seeme",
"cwl": "application/cwl",
"cww": "application/prs.cww",
"davmount": "application/davmount+xml",
"dbk": "application/docbook+xml",
"deb": "application/octet-stream",
"def": "text/plain",
"deploy": "application/octet-stream",
"dib": "image/bmp",
"disposition-notification": "message/disposition-notification",
"dist": "application/octet-stream",
"distz": "application/octet-stream",
"dll": "application/octet-stream",
"dmg": "application/octet-stream",
"dms": "application/octet-stream",
"doc": "application/msword",
"dot": "application/msword",
"dpx": "image/dpx",
"drle": "image/dicom-rle",
"dsc": "text/prs.lines.tag",
"dssc": "application/dssc+der",
"dtd": "application/xml-dtd",
"dump": "application/octet-stream",
"dwd": "application/atsc-dwd+xml",
"ear": "application/java-archive",
"ecma": "application/ecmascript",
"elc": "application/octet-stream",
"emf": "image/emf",
"eml": "message/rfc822",
"emma": "application/emma+xml",
"emotionml": "application/emotionml+xml",
"eps": "application/postscript",
"epub": "application/epub+zip",
"exe": "application/octet-stream",
"exi": "application/exi",
"exp": "application/express",
"exr": "image/aces",
"ez": "application/andrew-inset",
"fdf": "application/fdf",
"fdt": "application/fdt+xml",
"fits": "image/fits",
"g3": "image/g3fax",
"gbr": "application/rpki-ghostbusters",
"geojson": "application/geo+json",
"gif": "image/gif",
"glb": "model/gltf-binary",
"gltf": "model/gltf+json",
"gml": "application/gml+xml",
"gpx": "application/gpx+xml",
"gram": "application/srgs",
"grxml": "application/srgs+xml",
"gxf": "application/gxf",
"gz": "application/gzip",
"h261": "video/h261",
"h263": "video/h263",
"h264": "video/h264",
"heic": "image/heic",
"heics": "image/heic-sequence",
"heif": "image/heif",
"heifs": "image/heif-sequence",
"hej2": "image/hej2k",
"held": "application/atsc-held+xml",
"hjson": "application/hjson",
"hlp": "application/winhlp",
"hqx": "application/mac-binhex40",
"hsj2": "image/hsj2",
"htm": "text/html",
"html": "text/html",
"ics": "text/calendar",
"ief": "image/ief",
"ifb": "text/calendar",
"iges": "model/iges",
"igs": "model/iges",
"img": "application/octet-stream",
"in": "text/plain",
"ini": "text/plain",
"ink": "application/inkml+xml",
"inkml": "application/inkml+xml",
"ipfix": "application/ipfix",
"iso": "application/octet-stream",
"its": "application/its+xml",
"jade": "text/jade",
"jar": "application/java-archive",
"jhc": "image/jphc",
"jls": "image/jls",
"jp2": "image/jp2",
"jpe": "image/jpeg",
"jpeg": "image/jpeg",
"jpf": "image/jpx",
"jpg": "image/jpeg",
"jpg2": "image/jp2",
"jpgm": "image/jpm",
"jpgv": "video/jpeg",
"jph": "image/jph",
"jpm": "image/jpm",
"jpx": "image/jpx",
"js": "text/javascript",
"json": "application/json",
"json5": "application/json5",
"jsonld": "application/ld+json",
"jsonml": "application/jsonml+json",
"jsx": "text/jsx",
"jt": "model/jt",
"jxl": "image/jxl",
"jxr": "image/jxr",
"jxra": "image/jxra",
"jxrs": "image/jxrs",
"jxs": "image/jxs",
"jxsc": "image/jxsc",
"jxsi": "image/jxsi",
"jxss": "image/jxss",
"kar": "audio/midi",
"ktx": "image/ktx",
"ktx2": "image/ktx2",
"less": "text/less",
"lgr": "application/lgr+xml",
"list": "text/plain",
"litcoffee": "text/coffeescript",
"log": "text/plain",
"lostxml": "application/lost+xml",
"lrf": "application/octet-stream",
"m1v": "video/mpeg",
"m21": "application/mp21",
"m2a": "audio/mpeg",
"m2t": "video/mp2t",
"m2ts": "video/mp2t",
"m2v": "video/mpeg",
"m3a": "audio/mpeg",
"m4a": "audio/mp4",
"m4p": "application/mp4",
"m4s": "video/iso.segment",
"ma": "application/mathematica",
"mads": "application/mads+xml",
"maei": "application/mmt-aei+xml",
"man": "text/troff",
"manifest": "text/cache-manifest",
"map": "application/json",
"mar": "application/octet-stream",
"markdown": "text/markdown",
"mathml": "application/mathml+xml",
"mb": "application/mathematica",
"mbox": "application/mbox",
"md": "text/markdown",
"mdx": "text/mdx",
"me": "text/troff",
"mesh": "model/mesh",
"meta4": "application/metalink4+xml",
"metalink": "application/metalink+xml",
"mets": "application/mets+xml",
"mft": "application/rpki-manifest",
"mid": "audio/midi",
"midi": "audio/midi",
"mime": "message/rfc822",
"mj2": "video/mj2",
"mjp2": "video/mj2",
"mjs": "text/javascript",
"mml": "text/mathml",
"mods": "application/mods+xml",
"mov": "video/quicktime",
"mp2": "audio/mpeg",
"mp21": "application/mp21",
"mp2a": "audio/mpeg",
"mp3": "audio/mpeg",
"mp4": "video/mp4",
"mp4a": "audio/mp4",
"mp4s": "application/mp4",
"mp4v": "video/mp4",
"mpd": "application/dash+xml",
"mpe": "video/mpeg",
"mpeg": "video/mpeg",
"mpf": "application/media-policy-dataset+xml",
"mpg": "video/mpeg",
"mpg4": "video/mp4",
"mpga": "audio/mpeg",
"mpp": "application/dash-patch+xml",
"mrc": "application/marc",
"mrcx": "application/marcxml+xml",
"ms": "text/troff",
"mscml": "application/mediaservercontrol+xml",
"msh": "model/mesh",
"msi": "application/octet-stream",
"msix": "application/msix",
"msixbundle": "application/msixbundle",
"msm": "application/octet-stream",
"msp": "application/octet-stream",
"mtl": "model/mtl",
"mts": "video/mp2t",
"musd": "application/mmt-usd+xml",
"mxf": "application/mxf",
"mxmf": "audio/mobile-xmf",
"mxml": "application/xv+xml",
"n3": "text/n3",
"nb": "application/mathematica",
"nq": "application/n-quads",
"nt": "application/n-triples",
"obj": "model/obj",
"oda": "application/oda",
"oga": "audio/ogg",
"ogg": "audio/ogg",
"ogv": "video/ogg",
"ogx": "application/ogg",
"omdoc": "application/omdoc+xml",
"onepkg": "application/onenote",
"onetmp": "application/onenote",
"onetoc": "application/onenote",
"onetoc2": "application/onenote",
"opf": "application/oebps-package+xml",
"opus": "audio/ogg",
"otf": "font/otf",
"owl": "application/rdf+xml",
"oxps": "application/oxps",
"p10": "application/pkcs10",
"p7c": "application/pkcs7-mime",
"p7m": "application/pkcs7-mime",
"p7s": "application/pkcs7-signature",
"p8": "application/pkcs8",
"pdf": "application/pdf",
"pfr": "application/font-tdpfr",
"pgp": "application/pgp-encrypted",
"pkg": "application/octet-stream",
"pki": "application/pkixcmp",
"pkipath": "application/pkix-pkipath",
"pls": "application/pls+xml",
"png": "image/png",
"prc": "model/prc",
"prf": "application/pics-rules",
"provx": "application/provenance+xml",
"ps": "application/postscript",
"pskcxml": "application/pskc+xml",
"pti": "image/prs.pti",
"qt": "video/quicktime",
"raml": "application/raml+yaml",
"rapd": "application/route-apd+xml",
"rdf": "application/rdf+xml",
"relo": "application/p2p-overlay+xml",
"rif": "application/reginfo+xml",
"rl": "application/resource-lists+xml",
"rld": "application/resource-lists-diff+xml",
"rmi": "audio/midi",
"rnc": "application/relax-ng-compact-syntax",
"rng": "application/xml",
"roa": "application/rpki-roa",
"roff": "text/troff",
"rq": "application/sparql-query",
"rs": "application/rls-services+xml",
"rsat": "application/atsc-rsat+xml",
"rsd": "application/rsd+xml",
"rsheet": "application/urc-ressheet+xml",
"rss": "application/rss+xml",
"rtf": "text/rtf",
"rtx": "text/richtext",
"rusd": "application/route-usd+xml",
"s3m": "audio/s3m",
"sbml": "application/sbml+xml",
"scq": "application/scvp-cv-request",
"scs": "application/scvp-cv-response",
"sdp": "application/sdp",
"senmlx": "application/senml+xml",
"sensmlx": "application/sensml+xml",
"ser": "application/java-serialized-object",
"setpay": "application/set-payment-initiation",
"setreg": "application/set-registration-initiation",
"sgi": "image/sgi",
"sgm": "text/sgml",
"sgml": "text/sgml",
"shex": "text/shex",
"shf": "application/shf+xml",
"shtml": "text/html",
"sieve": "application/sieve",
"sig": "application/pgp-signature",
"sil": "audio/silk",
"silo": "model/mesh",
"siv": "application/sieve",
"slim": "text/slim",
"slm": "text/slim",
"sls": "application/route-s-tsid+xml",
"smi": "application/smil+xml",
"smil": "application/smil+xml",
"snd": "audio/basic",
"so": "application/octet-stream",
"spdx": "text/spdx",
"spp": "application/scvp-vp-response",
"spq": "application/scvp-vp-request",
"spx": "audio/ogg",
"sql": "application/sql",
"sru": "application/sru+xml",
"srx": "application/sparql-results+xml",
"ssdl": "application/ssdl+xml",
"ssml": "application/ssml+xml",
"stk": "application/hyperstudio",
"stl": "model/stl",
"stpx": "model/step+xml",
"stpxz": "model/step-xml+zip",
"stpz": "model/step+zip",
"styl": "text/stylus",
"stylus": "text/stylus",
"svg": "image/svg+xml",
"svgz": "image/svg+xml",
"swidtag": "application/swid+xml",
"t": "text/troff",
"t38": "image/t38",
"td": "application/urc-targetdesc+xml",
"tei": "application/tei+xml",
"teicorpus": "application/tei+xml",
"text": "text/plain",
"tfi": "application/thraud+xml",
"tfx": "image/tiff-fx",
"tif": "image/tiff",
"tiff": "image/tiff",
"toml": "application/toml",
"tr": "text/troff",
"trig": "application/trig",
"ts": "video/mp2t",
"tsd": "application/timestamped-data",
"tsv": "text/tab-separated-values",
"ttc": "font/collection",
"ttf": "font/ttf",
"ttl": "text/turtle",
"ttml": "application/ttml+xml",
"txt": "text/plain",
"u3d": "model/u3d",
"u8dsn": "message/global-delivery-status",
"u8hdr": "message/global-headers",
"u8mdn": "message/global-disposition-notification",
"u8msg": "message/global",
"ubj": "application/ubjson",
"uri": "text/uri-list",
"uris": "text/uri-list",
"urls": "text/uri-list",
"vcard": "text/vcard",
"vrml": "model/vrml",
"vtt": "text/vtt",
"vxml": "application/voicexml+xml",
"war": "application/java-archive",
"wasm": "application/wasm",
"wav": "audio/wav",
"weba": "audio/webm",
"webm": "video/webm",
"webmanifest": "application/manifest+json",
"webp": "image/webp",
"wgsl": "text/wgsl",
"wgt": "application/widget",
"wif": "application/watcherinfo+xml",
"wmf": "image/wmf",
"woff": "font/woff",
"woff2": "font/woff2",
"wrl": "model/vrml",
"wsdl": "application/wsdl+xml",
"wspolicy": "application/wspolicy+xml",
"x3d": "model/x3d+xml",
"x3db": "model/x3d+fastinfoset",
"x3dbz": "model/x3d+binary",
"x3dv": "model/x3d-vrml",
"x3dvz": "model/x3d+vrml",
"x3dz": "model/x3d+xml",
"xaml": "application/xaml+xml",
"xav": "application/xcap-att+xml",
"xca": "application/xcap-caps+xml",
"xcs": "application/calendar+xml",
"xdf": "application/xcap-diff+xml",
"xdssc": "application/dssc+xml",
"xel": "application/xcap-el+xml",
"xenc": "application/xenc+xml",
"xer": "application/patch-ops-error+xml",
"xfdf": "application/xfdf",
"xht": "application/xhtml+xml",
"xhtml": "application/xhtml+xml",
"xhvml": "application/xv+xml",
"xlf": "application/xliff+xml",
"xm": "audio/xm",
"xml": "text/xml",
"xns": "application/xcap-ns+xml",
"xop": "application/xop+xml",
"xpl": "application/xproc+xml",
"xsd": "application/xml",
"xsf": "application/prs.xsf+xml",
"xsl": "application/xml",
"xslt": "application/xml",
"xspf": "application/xspf+xml",
"xvm": "application/xv+xml",
"xvml": "application/xv+xml",
"yaml": "text/yaml",
"yang": "application/yang",
"yin": "application/yin+xml",
"yml": "text/yaml",
"zip": "application/zip"
};
function lookup(extn) {
let tmp = ('' + extn).trim().toLowerCase();
let idx = tmp.lastIndexOf('.');
return mimes[!~idx ? tmp : tmp.substring(++idx)];
}
export { mimes, lookup };
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (https://lukeed.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+44
View File
@@ -0,0 +1,44 @@
{
"name": "mrmime",
"version": "2.0.1",
"repository": "lukeed/mrmime",
"description": "A tiny (2.8kB) and fast utility for getting a MIME type from an extension or filename",
"module": "index.mjs",
"types": "index.d.ts",
"main": "index.js",
"license": "MIT",
"author": {
"name": "Luke Edwards",
"email": "luke.edwards05@gmail.com",
"url": "https://lukeed.com"
},
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.mjs",
"require": "./index.js"
},
"./package.json": "./package.json"
},
"files": [
"index.d.ts",
"index.mjs",
"index.js"
],
"engines": {
"node": ">=10"
},
"scripts": {
"build": "tsm bin/index.ts",
"test": "uvu -r tsm test"
},
"keywords": [
"mime",
"extension",
"mimetype"
],
"devDependencies": {
"tsm": "2.3.0",
"uvu": "0.5.2"
}
}
+130
View File
@@ -0,0 +1,130 @@
<div align="center">
<img src="logo.png" alt="mrmime" width="320" />
</div>
<div align="center">
<a href="https://npmjs.org/package/mrmime">
<img src="https://badgen.now.sh/npm/v/mrmime" alt="version" />
</a>
<a href="https://github.com/lukeed/mrmime/actions">
<img src="https://github.com/lukeed/mrmime/workflows/CI/badge.svg" alt="CI" />
</a>
<a href="https://licenses.dev/npm/mrmime">
<img src="https://licenses.dev/b/npm/mrmime" alt="licenses" />
</a>
<a href="https://npmjs.org/package/mrmime">
<img src="https://badgen.now.sh/npm/dm/mrmime" alt="downloads" />
</a>
<a href="https://packagephobia.now.sh/result?p=mrmime">
<img src="https://packagephobia.now.sh/badge?p=mrmime" alt="install size" />
</a>
</div>
<div align="center">
A tiny (2.8kB) and fast utility for getting a MIME type from an extension or filename
</div>
## Features
* Lightweight – 2.8kB gzip<br>
_Only includes standard mime types; all experimental and vendor-specific mimetypes removed._
* [Performant](#benchmarks)<br>
_All lookups are O(1) with minimal processing._
* Comprehensive Dictionary<br>
_Generated from [`mime-db`](https://github.com/jshttp/mime-db), which aggregates the IANA, NGINX, and Apache datasets._
* Customizable<br>
_Exposes the `mimes` dictionary for easy additions or overrides._
* Supports Native ESM and [Deno](https://deno.land/x/mrmime)<br>
_Ships with CommonJS and ESM support!_
## Install
```
$ npm install --save mrmime
```
## Usage
```js
import { lookup, mimes } from 'mrmime';
// Get a MIME type
// ---
lookup('txt'); //=> "text/plain"
lookup('.txt'); //=> "text/plain"
lookup('a.txt'); //=> "text/plain"
// Unknown extension
// ---
lookup('.xyz'); //=> undefined
// Add extension to dictionary
// ---
mimes['xyz'] = 'hello/world';
lookup('xyz'); //=> "hello/world"
```
## API
### lookup(input)
Returns: `string` or `undefined`
#### input
Type: `string`
The extension or filename to lookup.
> **Important:**
> * Any `input` value is cast to string, lowercased, and trimmed.
> * If a filename or filepath is provided, only the extension will be used.
## Benchmarks
> Running on Node v16.8.0
```
Load times:
mrmime 0.963ms
mime/lite 3.281ms
mime 6.751ms
Benchmark :: plain ("ext")
mime x 598,849 ops/sec ±0.28% (94 runs sampled)
mime/lite x 536,643 ops/sec ±0.11% (97 runs sampled)
mrmime x 835,885 ops/sec ±0.20% (97 runs sampled)
Benchmark :: leading (".ext")
mime x 368,656 ops/sec ±0.19% (99 runs sampled)
mime/lite x 368,318 ops/sec ±0.13% (97 runs sampled)
mrmime x 533,643 ops/sec ±0.10% (96 runs sampled)
Benchmark :: filename ("file.ext")
mime x 326,907 ops/sec ±0.17% (95 runs sampled)
mime/lite x 327,479 ops/sec ±0.12% (98 runs sampled)
mrmime x 512,823 ops/sec ±0.12% (99 runs sampled)
```
## Credits
Of course, a thank-you to [`mime`](https://github.com/broofa/mime) serving the community all these years & for being a all-encompassing MIME type library. I've only ever needed lookup/`getType` functionality – and now ESM support – so `mrmime` can only ever support 1/3 of what `mime` offers, at best.
This would not be possible without the team behind [`mime-db`](https://github.com/jshttp/mime-db), who have painstakingly maintained an amazing database for 7+ years.
Artwork created by [mintinol](https://www.deviantart.com/mintinol), which I found [here](https://www.deviantart.com/mintinol/art/Mr-Mime-373927920).
Finally, thanks to [Tim Branyen](https://github.com/tbranyen) for donating the package name :)
## License
MIT © [Luke Edwards](https://lukeed.com)
+27
View File
@@ -0,0 +1,27 @@
{
"name": "sirv",
"version": "2.0.4",
"description": "The optimized & lightweight middleware for serving requests to static assets",
"repository": "lukeed/sirv",
"module": "build.mjs",
"types": "sirv.d.ts",
"main": "build.js",
"license": "MIT",
"files": [
"build.*",
"sirv.d.ts"
],
"author": {
"name": "Luke Edwards",
"email": "luke@lukeed.com",
"url": "https://lukeed.com"
},
"engines": {
"node": ">= 10"
},
"dependencies": {
"@polka/url": "^1.0.0-next.24",
"mrmime": "^2.0.0",
"totalist": "^3.0.0"
}
}
+238
View File
@@ -0,0 +1,238 @@
# sirv ![CI](https://github.com/lukeed/sirv/workflows/CI/badge.svg)
> The optimized and lightweight middleware for serving requests to static assets
You may use `sirv` as a *very* fast and lightweight alternative to [`serve-static`](https://www.npmjs.com/package/serve-static).
The massive performance advantage over `serve-static` is explained by **not** relying on the file system for existence checks on every request. These are expensive interactions and must be avoided whenever possible! Instead, when not in "dev" mode, `sirv` performs all its file-system operations upfront and then relies on its cache for future operations.
This middleware will work out of the box for [Polka](https://github.com/lukeed/polka), Express, and other Express-like frameworks. It will also work with the native `http`, `https` and `http2` modules. It requires _very_ little effort to modify/wrap it for servers that don't accept the `(req, res, next)` signature.
:bulb: For a feature-complete CLI application, check out the sibling [`sirv-cli`](https://github.com/lukeed/sirv/tree/master/packages/sirv-cli) package as an alternative to [`zeit/serve`](https://github.com/zeit/serve)~!
## Install
```
$ npm install --save sirv
```
## Usage
```js
const sirv = require('sirv');
const polka = require('polka');
const compress = require('compression')();
// Init `sirv` handler
const assets = sirv('public', {
maxAge: 31536000, // 1Y
immutable: true
});
polka()
.use(compress, assets)
.use('/api', require('./api'))
.listen(3000, err => {
if (err) throw err;
console.log('> Ready on localhost:3000~!');
});
```
## API
### sirv(dir, opts={})
Returns: `Function`
The returned function is a middleware in the standard Express-like signature: `(req, res, next)`, where `req` is the [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage), `res` is the [`http.ServerResponse`](https://nodejs.org/dist/latest-v9.x/docs/api/http.html#http_class_http_serverresponse), and `next` (in this case) is the function to call if no file was found for the given path.
When defined, a `next()` callback is always called _instead of_ the [`opts.onNoMatch`](#optsonnomatch) callback. However, unlike `onNoMatch`, your `next()` is given no arguments.
#### dir
Type: `String`<br>
Default: `.`
The directory from which to read and serve assets. It is resolved to an absolute path &mdash; you must provide an absolute path yourself if `process.cwd()` is not the correct assumption.
#### opts.dev
Type: `Boolean`<br>
Default: `false`
Enable "dev" mode, which disables/skips caching. Instead, `sirv` will traverse the file system ***on every request***.
Additionally, `dev` mode will ignore `maxAge` and `immutable` as these options generate a production-oriented `Cache-Control` header value.
> **Important:** Do not use `dev` mode in production!
#### opts.etag
Type: `Boolean`<br>
Default: `false`
Generate and attach an `ETag` header to responses.
> **Note:** If an incoming request's [`If-None-Match` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) matches the `ETag` value, a `304` response is given.
#### opts.dotfiles
Type: `Boolean`<br>
Default: `false`
Allow requests to dotfiles (files or directories beginning with a `.`).
> **Note:** Requests to [`/.well-known/*`](https://tools.ietf.org/html/rfc8615) are always allowed.
#### opts.extensions
Type: `Array<String>`<br>
Default: `['html', 'htm']`
The file extension fallbacks to check for if a pathame is not initially found. For example, if a `/login` request cannot find a `login` filename, it will then look for `login.html` and `login.htm` before giving up~!
> **Important:** Actually, `sirv` will **also** look for `login/index.html` and `login/index.htm` before giving up.
#### opts.gzip
Type: `Boolean`<br>
Default: `false`
Determine if `sirv` look for **precompiled** `*.gz` files.<br>
Must be enabled _and_ the incoming request's [`Accept Encoding`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) must include "gzip" in order for `sirv` to search for the gzip'd alternative.
> **Note:** The `.gz` assumption also applies to the `opts.extensions` list.
```js
// NOTE: PSEUDO CODE
// Showing lookup logic
// Request: [Accept-Encoding: gzip] "/foobar.jpg"
lookup([
'/foobar.jpg.gz', '/foobar.jpg',
'/foobar.jpg.html.gz', '/foobar.jpg/index.html.gz',
'/foobar.jpg.htm.gz', '/foobar.jpg/index.htm.gz',
'/foobar.jpg.html', '/foobar.jpg/index.html',
'/foobar.jpg.htm', '/foobar.jpg/index.htm',
]);
// Request: [Accept-Encoding: gzip] "/"
lookup([
'/index.html.gz',
'/index.htm.gz',
'/index.html',
'/index.htm',
]);
```
#### opts.brotli
Type: `Boolean`<br>
Default: `false`
Determine if `sirv` look for **precompiled** `*.br` files.<br>
Must be enabled _and_ the incoming request's [`Accept Encoding`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) must include either "br" or "brotli" in order for `sirv` to search for the brotli-compressed alternative.
> **Note:** The `.br` assumption also applies to the `opts.extensions` list.
When both `opts.broli` and `opts.gzip` are enabled &mdash; and all conditions are equal &mdash; then the brotli variant always takes priority.
```js
// NOTE: PSEUDO CODE
// Showing lookup logic
// Request: [Accept-Encoding: br] "/foobar.jpg"
lookup([
'/foobar.jpg.br', '/foobar.jpg',
'/foobar.jpg.html.br', '/foobar.jpg/index.html.br',
'/foobar.jpg.htm.br', '/foobar.jpg/index.htm.br',
'/foobar.jpg.html', '/foobar.jpg/index.html',
'/foobar.jpg.htm', '/foobar.jpg/index.htm',
]);
// Request: [Accept-Encoding: br,gz] "/"
lookup([
'/index.html.br'
'/index.htm.br'
'/index.html.gz'
'/index.htm.gz'
'/index.html'
'/index.htm'
]);
```
#### opts.maxAge
Type: `Number`<br>
Default: `undefined`
Enables the `Cache-Control` header on responses and sets the `max-age` value (in seconds).<br>
For example, `maxAge: 31536000` is equivalent to one year.
#### opts.immutable
Type: `Boolean`<br>
Default: `false`
Appends the [`immutable` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Revalidation_and_reloading) on your `Cache-Control` header, used for uniquely-named assets that will not change!
> **Important:** Will only work if `opts.maxAge` has a value defined!
#### opts.single
Type: `Boolean` or `String`<br>
Default: `false`
Treat the directory as a single-page application.
When `true`, the directory's index page (default `index.html`) will be sent if the request asset does not exist.<br>
You may pass a `string` value to use a file _instead of_ `index.html` as your fallback.
For example, if "/about" is requested but no variants of that file exist, then the response for "/" is sent instead:
```js
// Note: This is psuedo code to illustrate what's happening
// Request: "/about"
let file = find(['/about', '/about.html', '/about.htm', '/about/index.html', '/about.htm']);
if (file) {
send(file);
} else if (opts.single === true) {
file = find(['/', '/index.html', '/index.htm']);
send(file);
} else if (typeof opts.single === 'string') {
file = find([opts.single]);
send(file);
} else {
// next() or 404
}
```
#### opts.ignores
Type: `false` or `Array<String | RegExp>`
Specify paths/patterns that should ignore the fallback behavior that `opts.single` provides.
By default, any asset-like path (URLs that end with an extension) will be ignored. This means that, for example, if `/foobar.jpg` is not found, a `404` response is sent instead of the `index.html` fallback.
Additionally, any `/.well-known/*` pathname ignores the fallback – as do all other dotfile requests when `opts.dotfiles` is enabled.
Any string value(s) will be passed through `new RegExp(value, 'i')` directly.
Finally, you may set `ignores: false` to disable ***all*** ignores, including the defaults. Put differently, this will fallback ***all*** unknown pathnames to your `index.html` (or custom `opts.single` value).
> **Important:** Only has an effect if `opts.single` is enabled.
#### opts.onNoMatch
Type: `Function`
A custom function to run if a file cannot be found for a given request. <br>By default, `sirv` will send a basic `(404) Not found` response.
The function receives the current `req <IncomingMessage>, res <ServerResponse>` pair for as its two arguments.
> **Note:** This won't run if a `next` callback has been provided to the middleware; see [`sirv`](#sirvdir-opts) description.
#### opts.setHeaders
Type: `Function`
A custom function to append or change any headers on the outgoing response. There is no default.
Its signature is `(res, pathname, stats)`, where `res` is the `ServerResponse`, `pathname` is incoming request path (stripped of queries), and `stats` is the file's result from [`fs.statSync`](https://nodejs.org/api/fs.html#fs_fs_statsync_path).
## License
MIT © [Luke Edwards](https://lukeed.com)
+25
View File
@@ -0,0 +1,25 @@
declare module 'sirv' {
import type { Stats } from 'fs';
import type { IncomingMessage, ServerResponse } from 'http';
type Arrayable<T> = T | T[];
export type NextHandler = () => void | Promise<void>;
export type RequestHandler = (req: IncomingMessage, res: ServerResponse, next?: NextHandler) => void;
export interface Options {
dev?: boolean;
etag?: boolean;
maxAge?: number;
immutable?: boolean;
single?: string | boolean;
ignores?: false | Arrayable<string | RegExp>;
extensions?: string[];
dotfiles?: boolean;
brotli?: boolean;
gzip?: boolean;
onNoMatch?: (req: IncomingMessage, res: ServerResponse) => void;
setHeaders?: (res: ServerResponse, pathname: string, stats: Stats) => void;
}
export default function(dir?: string, opts?: Options): RequestHandler;
}