@universal-middleware/compress
Version:
Universal compress middleware
171 lines (164 loc) • 6.11 kB
JavaScript
// 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();
}
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-AIYWG5C2.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 };