UNPKG

@universal-middleware/compress

Version:
173 lines (166 loc) 6.14 kB
// src/const.ts var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i; // src/encoding-header.ts var simpleEncodingRegExp = /^\s*([^\s;]+)\s*(?:;(.*))?$/; function parseAcceptEncodingHeader(accept) { const accepts = accept.split(","); const out = /* @__PURE__ */ new Map(); let minQuality = 1; for (let i = 0; i < accepts.length; i++) { const encoding = parseEncoding(accepts[i].trim()); if (encoding) { out.set(encoding.encoding, { ...encoding, index: i }); minQuality = Math.min(minQuality, encoding.q || 1); } } if (!out.has("identity")) { out.set("identity", { encoding: "identity", q: minQuality, index: Number.MAX_SAFE_INTEGER }); } return out; } function parseEncoding(str) { const match = simpleEncodingRegExp.exec(str); if (!match) return null; const encoding = match[1]; let q = 1; if (match[2]) { const params = match[2].split(";"); for (let j = 0; j < params.length; j++) { const p = params[j].trim().split("="); if (p[0] === "q") { q = Number.parseFloat(p[1]); break; } } } return { encoding, q }; } function chooseBestEncoding(request, availableEncodings) { let bestEncoding = null; if (availableEncodings.length === 0) return null; const header = request.headers.get("Accept-Encoding"); if (!header) return null; const parsed = parseAcceptEncodingHeader(header); for (const enc of availableEncodings) { const encodingEntry = parsed.get(enc); if (encodingEntry) { if (!bestEncoding || encodingEntry.q > bestEncoding.q || encodingEntry.q === bestEncoding.q && encodingEntry.index < bestEncoding.index) { bestEncoding = encodingEntry; } } } return bestEncoding?.encoding; } // src/runtime.ts var isCompressionStreamAvailable = typeof CompressionStream !== "undefined"; var isZlibAvailable = await isNodeZlibAvailable(); var supportedEncodings = isZlibAvailable ? ["br", "gzip", "deflate"] : isCompressionStreamAvailable ? ["gzip", "deflate"] : []; async function isNodeZlibAvailable() { try { await import( /* @vite-ignore */ 'node:zlib' ); return true; } catch { return false; } } // src/compress.ts var cacheControlNoTransformRegExp = /(?:^|,)\s*?no-transform\s*?(?:,|$)/i; var EncodingGuesser = class { constructor(request, options = {}) { this.request = request; this.options = options; this.encoding = this._guessRequest(); } request; options; encoding; _guessRequest() { if (this.request.method === "HEAD") { return null; } const cacheControl = this.request.headers.get("Cache-Control"); if (cacheControl && cacheControlNoTransformRegExp.test(cacheControl)) { return null; } const chosenEncoding = chooseBestEncoding(this.request, supportedEncodings); if (!chosenEncoding || chosenEncoding === "identity") { return null; } return chosenEncoding; } guessEncoding(response) { if (this.encoding === null) return null; const threshold = this.options?.threshold ?? 1024; const cacheControl = response.headers.get("Cache-Control"); if (cacheControl && cacheControlNoTransformRegExp.test(cacheControl)) { return null; } const contentLength = response.headers.get("Content-Length"); if (contentLength && Number.parseInt(contentLength, 10) < threshold) { return null; } const contentType = response.headers.get("Content-Type"); if (!contentType || !COMPRESSIBLE_CONTENT_TYPE_REGEX.test(contentType)) { return null; } const contentEncoding = response.headers.get("Content-Encoding") ?? "identity"; if (contentEncoding !== "identity") { return null; } return this.encoding; } }; // src/response.ts async function guessCompressor(encoding) { if (isZlibAvailable) { const { compressStream: compressStream2 } = await import('./stream-X3AXMVWI.js'); return (input) => compressStream2(input, encoding); } const { compressStream } = await import('./stream-UOPLJC3G.js'); return (input) => compressStream(input, encoding); } var handleCompression = async (encoding, input, options) => { if (!input.headers.get("Vary")?.includes("Accept-Encoding")) input.headers.append("Vary", "Accept-Encoding"); if (input.headers.get("Content-Encoding")) return input; const { headers, ...optionsRest } = options || {}; const optionsHeaders = new Headers(headers); if (!optionsHeaders.get("Vary")?.includes("Accept-Encoding")) optionsHeaders.append("Vary", "Accept-Encoding"); for (const [header, value] of optionsHeaders) { if (!(input.headers.get(header) ?? "").includes(value)) input.headers.append(header, value); } const compressor = await guessCompressor(encoding); const body = await compressor(input.body); if (body !== null) { input.headers.append("Content-Encoding", encoding); input.headers.delete("Content-Length"); } return new Response(body, { headers: input.headers, status: optionsRest.status ?? input.status, statusText: optionsRest.statusText ?? input.statusText }); }; // src/middleware.ts var compressMiddleware = ((options) => (request) => { const guesser = new EncodingGuesser(request); return function universalMiddlewareCompress(response) { const encoding = guesser.guessEncoding(response); if (!encoding) return response; return handleCompression(encoding, response, options); }; }); var middleware_default = compressMiddleware; export { middleware_default };