From 036b879c53b182d8220b45ca19e2eb37f8c33260 Mon Sep 17 00:00:00 2001 From: Oscar Otero Date: Sat, 26 Oct 2024 12:17:19 +0200 Subject: [PATCH] precompress middleware. close #664 --- CHANGELOG.md | 4 ++- middlewares/precompress.ts | 69 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 middlewares/precompress.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 48081997..918113a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Go to the `v1` branch to see the changelog of Lume 1. - New plugin: `google_fonts` to download and self-host automatically fonts from Google Fonts. - New plugin: `brotli` to compress files. [#681] - New plugin: `gzip` to compress files. [#680] +- New `precompress` middleware, to serve precompressed files. [#664] ### Fixed - Nav plugin: Breadcrumb with urls with CJK characters. @@ -558,6 +559,7 @@ Go to the `v1` branch to see the changelog of Lume 1. [#652]: https://github.com/lumeland/lume/issues/652 [#655]: https://github.com/lumeland/lume/issues/655 [#662]: https://github.com/lumeland/lume/issues/662 +[#664]: https://github.com/lumeland/lume/issues/664 [#667]: https://github.com/lumeland/lume/issues/667 [#668]: https://github.com/lumeland/lume/issues/668 [#671]: https://github.com/lumeland/lume/issues/671 @@ -566,8 +568,8 @@ Go to the `v1` branch to see the changelog of Lume 1. [#677]: https://github.com/lumeland/lume/issues/677 [#678]: https://github.com/lumeland/lume/issues/678 [#679]: https://github.com/lumeland/lume/issues/679 -[#681]: https://github.com/lumeland/lume/issues/681 [#680]: https://github.com/lumeland/lume/issues/680 +[#681]: https://github.com/lumeland/lume/issues/681 [Unreleased]: https://github.com/lumeland/lume/compare/v2.3.3...HEAD [2.3.3]: https://github.com/lumeland/lume/compare/v2.3.2...v2.3.3 diff --git a/middlewares/precompress.ts b/middlewares/precompress.ts new file mode 100644 index 00000000..50bdc1c7 --- /dev/null +++ b/middlewares/precompress.ts @@ -0,0 +1,69 @@ +import { merge } from "../core/utils/object.ts"; +import { contentType } from "../deps/media_types.ts"; +import { extname } from "../deps/path.ts"; + +import type { Middleware, RequestHandler } from "../core/server.ts"; + +/** The options to configure the middleware server */ +export interface Options { + encodings: Record; +} + +export const defaults: Options = { + encodings: { + br: ".br", + gzip: ".gz", + }, +}; + +export function preCompress(userOptions?: Options): Middleware { + const options = merge(defaults, userOptions); + + return async function ( + request: Request, + next: RequestHandler, + ): Promise { + const accepted = new Set( + request.headers.get("Accept-Encoding") + ?.split(",") + .map((encoding) => encoding.split(";").shift()?.trim()) + .filter((encoding) => encoding), + ); + + if (!accepted.size) { + return next(request); + } + + for (const [encoding, ext] of Object.entries(options.encodings)) { + if (!accepted.has(encoding)) { + continue; + } + + const newUrl = new URL(request.url); + const initialExtension = newUrl.pathname.endsWith("/") + ? ".html" + : extname(newUrl.pathname); + newUrl.pathname += newUrl.pathname.endsWith("/") + ? `index.html${ext}` + : ext; + const compressedRequest = new Request(newUrl, request); + const response = await next(compressedRequest); + + if (response.status >= 400) { + continue; + } + + response.headers.set( + "Content-Type", + contentType(initialExtension) || "application/octet-stream", + ); + response.headers.set("Content-Encoding", encoding); + response.headers.append("Vary", "Accept-Encoding"); + return response; + } + + return next(request); + }; +} + +export default preCompress;