UNPKG

elysia-remix

Version:

Use React Router v7 or Remix with Elysia with HMR support!

810 lines (802 loc) 17.3 kB
import { Elysia, NotFoundError } from 'elysia'; function getDefaultExportFromCjs(x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x; } var fastDecodeUriComponent; var hasRequiredFastDecodeUriComponent; function requireFastDecodeUriComponent () { if (hasRequiredFastDecodeUriComponent) return fastDecodeUriComponent; hasRequiredFastDecodeUriComponent = 1; var UTF8_ACCEPT = 12; var UTF8_REJECT = 0; var UTF8_DATA = [ // The first part of the table maps bytes to character to a transition. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 7, 10, 9, 9, 9, 11, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // The second part of the table maps a state to a new state when adding a // transition. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 24, 36, 48, 60, 72, 84, 96, 0, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // The third part maps the current transition to a mask that needs to apply // to the byte. 127, 63, 63, 63, 0, 31, 15, 15, 15, 7, 7, 7 ]; function decodeURIComponent(uri) { var percentPosition = uri.indexOf("%"); if (percentPosition === -1) return uri; var length = uri.length; var decoded = ""; var last = 0; var codepoint = 0; var startOfOctets = percentPosition; var state = UTF8_ACCEPT; while (percentPosition > -1 && percentPosition < length) { var high = hexCodeToInt(uri[percentPosition + 1], 4); var low = hexCodeToInt(uri[percentPosition + 2], 0); var byte = high | low; var type = UTF8_DATA[byte]; state = UTF8_DATA[256 + state + type]; codepoint = codepoint << 6 | byte & UTF8_DATA[364 + type]; if (state === UTF8_ACCEPT) { decoded += uri.slice(last, startOfOctets); decoded += codepoint <= 65535 ? String.fromCharCode(codepoint) : String.fromCharCode( 55232 + (codepoint >> 10), 56320 + (codepoint & 1023) ); codepoint = 0; last = percentPosition + 3; percentPosition = startOfOctets = uri.indexOf("%", last); } else if (state === UTF8_REJECT) { return null; } else { percentPosition += 3; if (percentPosition < length && uri.charCodeAt(percentPosition) === 37) continue; return null; } } return decoded + uri.slice(last); } var HEX = { "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "A": 10, "b": 11, "B": 11, "c": 12, "C": 12, "d": 13, "D": 13, "e": 14, "E": 14, "f": 15, "F": 15 }; function hexCodeToInt(c, shift) { var i = HEX[c]; return i === void 0 ? 255 : i << shift; } fastDecodeUriComponent = decodeURIComponent; return fastDecodeUriComponent; } var fastDecodeUriComponentExports = requireFastDecodeUriComponent(); var fastDecodeURI = /*@__PURE__*/getDefaultExportFromCjs(fastDecodeUriComponentExports); let fs, path; const isBun = typeof Bun < "u" && !!Bun.file; function getBuiltinModule() { if (fs || (fs = process.getBuiltinModule("fs/promises")), path || (path = process.getBuiltinModule("path")), !path) { console.warn("@elysiajs/static require path to be available."); return; } return [fs, path]; } async function listHTMLFiles(dir) { if (fs || getBuiltinModule(), isBun) { const glob = new Bun.Glob("**/*.html"), files = []; for await (const file of glob.scan(dir)) files.push(path.join(dir, file)); return files; } return []; } async function listFiles(dir) { if (fs || getBuiltinModule(), isBun) { const glob = new Bun.Glob("**/*"), files2 = []; for await (const file of glob.scan(dir)) files2.push(path.join(dir, file)); return files2; } const files = await fs.readdir(dir).catch(() => []); return (await Promise.all( files.map(async (name) => { const file = dir + path.sep + name, stats = await fs.stat(file).catch(() => null); return stats ? stats.isDirectory() ? await listFiles(file) : [path.resolve(dir, file)] : []; }) )).flat(); } function fileExists(path2) { return fs || getBuiltinModule(), fs.stat(path2).then( () => true, () => false ); } class LRUCache { constructor(max = 250, ttl = 10800) { this.max = max; this.ttl = ttl; this.map = /* @__PURE__ */ new Map(); } get(key) { const entry = this.map.get(key); if (entry) return entry[1] <= Date.now() ? void this.delete(key) : (this.map.delete(key), this.map.set(key, entry), entry[0]); } set(key, value) { if (this.interval || (this.interval = setInterval(() => { const now = Date.now(); for (const [key2, entry] of this.map) entry[1] <= now && this.map.delete(key2); }, this.ttl)), this.map.has(key)) this.map.delete(key); else if (this.map.size >= this.max) { const oldestKey = this.map.keys().next().value; oldestKey !== void 0 && this.delete(oldestKey); } this.map.set(key, [value, Date.now() + this.ttl * 1e3]); } delete(key) { this.map.get(key) && this.map.delete(key); } clear() { this.map.clear(); } size() { return this.map.size; } [Symbol.dispose]() { this.interval && clearInterval(this.interval); } } function isCached(headers, etag, filePath) { if (headers["cache-control"] && /no-cache|no-store/.test(headers["cache-control"])) return false; if ("if-none-match" in headers) { const ifNoneMatch = headers["if-none-match"]; return ifNoneMatch === "*" ? true : ifNoneMatch === null || typeof etag != "string" ? false : ifNoneMatch === etag; } if (headers["if-modified-since"]) { const ifModifiedSince = headers["if-modified-since"]; try { return fs.stat(filePath).then((stat) => { if (stat.mtime !== void 0 && stat.mtime.getTime() <= Date.parse(ifModifiedSince)) return true; }); } catch { } } return false; } let Crypto; function getFile(path2) { return isBun ? Bun.file(path2) : (fs || getBuiltinModule(), fs.readFile(path2)); } async function generateETag(file) { return isBun ? new Bun.CryptoHasher("md5").update(await file.arrayBuffer()).digest("base64") : (Crypto || (Crypto = process.getBuiltinModule("crypto")), Crypto ? Crypto.createHash("md5").update(file).digest("base64") : void console.warn( "[@elysiajs/static] crypto is required to generate etag." )); } const isNotEmpty = (obj) => { if (!obj) return false; for (const _ in obj) return true; return false; }; async function staticPlugin({ assets = "public", prefix = "/public", staticLimit = 1024, alwaysStatic = process.env.NODE_ENV === "production", ignorePatterns = [".DS_Store", ".git", ".env"], headers: initialHeaders, maxAge = 86400, directive = "public", etag: useETag = true, extension = true, indexHTML = true, detail, bunFullstack = false, decodeURI, silent } = {}) { if (typeof process > "u" || typeof process.getBuiltinModule > "u") return silent || console.warn( "[@elysiajs/static] require process.getBuiltinModule. Static plugin is disabled" ), new Elysia(); const builtinModule = getBuiltinModule(); if (!builtinModule) return new Elysia(); const [fs, path] = builtinModule, normalizePath = path.sep !== "/" ? (p) => p.replace(/\\/g, "/") : (p) => p, fileCache = new LRUCache(); prefix === path.sep && (prefix = ""); const assetsDir = path.resolve(assets), shouldIgnore = ignorePatterns.length ? (file) => ignorePatterns.find( (pattern) => typeof pattern == "string" ? pattern.includes(file) : pattern.test(file) ) : () => false, app = new Elysia({ name: "static", seed: prefix }); if (alwaysStatic) { const files = await listFiles(path.resolve(assets)); if (files.length <= staticLimit) for (const absolutePath of files) { let handleCache2 = function({ headers: requestHeaders }) { if (etag) { let cached = isCached( requestHeaders, etag, absolutePath ); if (cached === true) return new Response(null, { status: 304, headers: isNotEmpty(initialHeaders) ? initialHeaders : void 0 }); if (cached !== false) { const cache2 = fileCache.get(pathName); return cache2 ? cache2.clone() : cached.then((cached2) => { if (cached2) return new Response(null, { status: 304, headers: initialHeaders || void 0 }); const response2 = new Response(file, { headers: Object.assign( { "Cache-Control": maxAge ? `${directive}, max-age=${maxAge}` : directive }, initialHeaders, etag ? { Etag: etag } : {} ) }); return fileCache.set(prefix, response2), response2.clone(); }); } } const cache = fileCache.get(pathName); if (cache) return cache.clone(); const response = new Response(file, { headers: Object.assign( { "Cache-Control": maxAge ? `${directive}, max-age=${maxAge}` : directive }, initialHeaders, etag ? { Etag: etag } : {} ) }); return fileCache.set(pathName, response), response.clone(); }; if (!absolutePath || shouldIgnore(absolutePath)) continue; let relativePath = absolutePath.replace(assetsDir, ""); decodeURI && (relativePath = fastDecodeURI(relativePath) ?? relativePath); let pathName = normalizePath(path.join(prefix, relativePath)); if (isBun && absolutePath.endsWith(".html")) { let htmlFile; try { htmlFile = bunFullstack ? (await import(absolutePath)).default : getFile(absolutePath); } catch (error) { silent || console.error( `[@elysiajs/static] Failed to load HTML file: ${absolutePath}`, error ); continue; } app.get(pathName, htmlFile, { detail: typeof detail == "function" ? detail(pathName) : detail }), indexHTML && pathName.endsWith("/index.html") && app.get(pathName.replace("/index.html", ""), htmlFile, { detail: typeof detail == "function" ? detail( pathName.replace("/index.html", "") ) : detail }); continue; } extension || (pathName = normalizePath( pathName.slice(0, pathName.lastIndexOf(".")) )); const file = isBun ? getFile(absolutePath) : await getFile(absolutePath); if (!file) return silent || console.warn( `[@elysiajs/static] Failed to load file: ${absolutePath}` ), new Elysia(); const etag = await generateETag(file); app.get( pathName, useETag ? handleCache2 : new Response( file, isNotEmpty(initialHeaders) ? { headers: initialHeaders } : void 0 ), { detail: typeof detail == "function" ? detail(pathName) : detail } ), indexHTML && pathName.endsWith("/index.html") && app.get( pathName.replace("/index.html", ""), useETag ? handleCache2 : new Response( file, isNotEmpty(initialHeaders) ? { headers: initialHeaders } : void 0 ), { detail: typeof detail == "function" ? detail( pathName.replace("/index.html", "") ) : detail } ); } return app; } if ( // @ts-ignore private property !(`GET_${prefix}/*` in app.routeTree) ) { if (isBun) { const htmls = await listHTMLFiles(path.resolve(assets)); for (const absolutePath of htmls) { if (!absolutePath || shouldIgnore(absolutePath)) continue; let relativePath = absolutePath.replace(assetsDir, ""); const pathName = normalizePath(path.join(prefix, relativePath)); let htmlFile; try { htmlFile = bunFullstack ? (await import(absolutePath)).default : getFile(absolutePath); } catch (error) { silent || console.error( `[@elysiajs/static] Failed to load HTML file: ${absolutePath}`, error ); continue; } app.get(pathName, htmlFile, { detail: typeof detail == "function" ? detail(pathName) : detail }), indexHTML && pathName.endsWith("/index.html") && app.get(pathName.replace("/index.html", ""), htmlFile, { detail: typeof detail == "function" ? detail(pathName.replace("/index.html", "")) : detail }); } } app.onError(() => { }).get( `${prefix.endsWith("/") ? prefix.slice(0, -1) : prefix}/*`, async ({ params, headers: requestHeaders }) => { const pathName = normalizePath( path.join( assets, decodeURI ? fastDecodeURI(params["*"]) ?? params["*"] : params["*"] ) ); if (shouldIgnore(pathName)) throw new NotFoundError(); const cache = fileCache.get(pathName); if (cache) return cache.clone(); try { const fileStat = await fs.stat(pathName).catch(() => null); if (!fileStat) throw new NotFoundError(); if (!indexHTML && fileStat.isDirectory()) throw new NotFoundError(); let file; if (!isBun && indexHTML) { const htmlPath = path.join(pathName, "index.html"), cache2 = fileCache.get(htmlPath); if (cache2) return cache2.clone(); await fileExists(htmlPath) && (file = await getFile(htmlPath)); } if (!file && !fileStat.isDirectory() && await fileExists(pathName)) file = await getFile(pathName); else throw new NotFoundError(); if (!useETag) return new Response( file, isNotEmpty(initialHeaders) ? { headers: initialHeaders } : void 0 ); const etag = await generateETag(file); if (etag && await isCached(requestHeaders, etag, pathName)) return new Response(null, { status: 304 }); const response = new Response(file, { headers: Object.assign( { "Cache-Control": maxAge ? `${directive}, max-age=${maxAge}` : directive }, initialHeaders, etag ? { Etag: etag } : {} ) }); return fileCache.set(pathName, response), response.clone(); } catch (error) { throw error instanceof NotFoundError ? error : (silent || console.error("[@elysiajs/static]", error), new NotFoundError()); } }, { detail: typeof detail == "function" ? detail( `${prefix.endsWith("/") ? prefix.slice(0, -1) : prefix}/*` ) : detail } ); } return app; } export { getDefaultExportFromCjs as g, staticPlugin as s };