UNPKG

@shgysk8zer0/http-server

Version:

A powerful but lightweight node server built using web standards

169 lines (144 loc) 4.22 kB
'use strict'; var promises = require('node:fs/promises'); var node_path = require('node:path'); var node_fs = require('node:fs'); const BASE = `file://${process.cwd()}/`; function resolveModulePath(path) { if (path instanceof URL || path instanceof Function) { return path; } else if (path[0] === '.' || path[0] === '/') { return `${BASE}${path.replaceAll(/(\.+\/)/g, '')}`; } else { // May require `--experimental-import-meta-resolve` to work as expected (resolve relative to project root and make use of `BASE`) try { return undefined(path, BASE); } catch(err) { throw new Error(`Unable to import module ${path}. Try running again with \`node --experimental-import-meta-resolve\`.`, { cause: err }); } } } function getContentType(path) { switch(path.toLowerCase().split('.').at(-1)) { case 'html': return 'text/html'; case 'js': return 'application/javascript'; case 'css': return 'text/css'; case 'jpeg': return 'image/jpeg'; case 'png': return 'image/png'; case 'svg': return 'image/svg+xml'; case 'txt': return 'text/plain'; default: return 'application/octet-stream'; } } /** * * @param {string} path * @param {string} base * @returns {ReadableStream} */ const getFileStream = (path, base = `file://${process.cwd()}/`) => new ReadableStream({ async start(controller) { try { const url = URL.parse(path, base); if (! (url instanceof URL) || url.protocol !== 'file:') { throw new Error('Invalid file path.'); } else { const { createReadStream } = await import('node:fs'); const fileStream = createReadStream(url.pathname); for await (const chunk of fileStream) { controller.enqueue(chunk); } } } catch(err) { controller.error(err); } finally { controller.close(); } } }); /** * * @param {string} path * @param {object} [options] * @param {string} [options.base] * @param {string|null} [options.compression=null] * @returns {Promise<Response>} */ async function respondWithFile(path, { base = `file://${process.cwd()}/`, compression = null, ...headers } = {}) { const stream = getFileStream(path, base); if (typeof compression === 'string') { return new Response(stream.pipeThrough(new CompressionStream(compression), { headers: { 'Content-Type': getContentType(path), 'Content-Encoding': compression, ...headers, } })); } else { return new Response(stream, { headers: { 'Content-Type': getContentType(path), ...headers, }, }); } } /** * Creates a `file:` URL relative from the `pathname` of a URL, relative to project root. * * @param {string|URL} url The URL to resolve using `pathname`. * @param {string} [root="/"] The root directory, relative to the project root/working directory. * @returns {URL} The resolved file URL (`file:///path/to/project/:root/:pathname`). * @throws {TypeError} If `url` is not a string or URL. */ function getFileURL(url, root = '/') { if (typeof url === 'string') { return getFileURL(URL.parse(url), root); } else if (! (url instanceof URL)) { throw new TypeError('`url` must be a string or `URL`.'); } else { const base = `file:${process.cwd()}/`; const path = './' + [ ...root.split('/').filter(seg => seg.length !== 0), ...url.pathname.split('/').filter(seg => seg.length !== 0), ].join('/'); return new URL(path, base); } } /** * * @param {string} path * @param {object} options * @param {string[]} [options.indexFiles=["index.html","index.html"]] * @returns {Promise<string|null>} */ async function resolveStaticPath(path, { indexFiles = ['index.html', 'index.htm'] } = {}) { if (node_fs.existsSync(path)) { const stats = await promises.stat(path); if (stats.isFile()) { return path; } else if (stats.isDirectory()) { // Try each potential index file return indexFiles.map(index => node_path.join(path, index)).find(node_fs.existsSync) ?? null; } } else { return null; } } exports.getContentType = getContentType; exports.getFileStream = getFileStream; exports.getFileURL = getFileURL; exports.resolveModulePath = resolveModulePath; exports.resolveStaticPath = resolveStaticPath; exports.respondWithFile = respondWithFile;