65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
import type { Cache } from "../core/cache.ts";
|
|
import type { Integration, IntegrationContext } from "../contracts/index.ts";
|
|
|
|
export interface GoogleFontsOptions {
|
|
families: string[];
|
|
route?: string;
|
|
cache?: Cache;
|
|
display?: "auto" | "block" | "swap" | "fallback" | "optional";
|
|
}
|
|
|
|
function memoryCache(): Cache {
|
|
const entries = new Map<string, Uint8Array>();
|
|
return {
|
|
async get(key) { return entries.get(key); },
|
|
async put(key, value) { entries.set(key, value); },
|
|
async delete(key) { entries.delete(key); },
|
|
};
|
|
}
|
|
|
|
function text(value: Uint8Array): string {
|
|
return new TextDecoder().decode(value);
|
|
}
|
|
|
|
function googleUrl(options: GoogleFontsOptions): string {
|
|
const family = options.families.map((value) => `family=${encodeURIComponent(value.replace(/\s+/g, "+"))}`).join("&");
|
|
return `https://fonts.googleapis.com/css2?${family}&display=${options.display ?? "swap"}`;
|
|
}
|
|
|
|
/** Serves Google Fonts through project routes and caches both CSS and font files. */
|
|
export function googleFonts(options: GoogleFontsOptions): Integration {
|
|
const route = options.route ?? "/_muddle/fonts.css";
|
|
const proxyPrefix = `${route.replace(/\.css$/, "")}/`;
|
|
const cache = options.cache ?? memoryCache();
|
|
return {
|
|
name: "google-fonts",
|
|
setup(context: IntegrationContext) {
|
|
context.route("GET", route, async () => {
|
|
const key = `css:${googleUrl(options)}`;
|
|
let css = await cache.get(key);
|
|
if (!css) {
|
|
const response = await fetch(googleUrl(options));
|
|
if (!response.ok) return new Response("", { status: 502 });
|
|
css = new Uint8Array(await response.arrayBuffer());
|
|
await cache.put(key, css, { ttl: 86400 });
|
|
}
|
|
const proxied = text(css).replace(/url\((https:\/\/[^)]+)\)/g, (_match, url: string) => `url(${proxyPrefix}${encodeURIComponent(url)})`);
|
|
return new Response(proxied, { headers: { "content-type": "text/css; charset=utf-8", "cache-control": "public, max-age=86400" } });
|
|
});
|
|
context.route("GET", `${proxyPrefix}*`, async (request) => {
|
|
const encoded = new URL(request.url).pathname.slice(proxyPrefix.length);
|
|
const url = decodeURIComponent(encoded);
|
|
if (!url.startsWith("https://fonts.gstatic.com/")) return new Response("Not Found", { status: 404 });
|
|
const key = `font:${url}`;
|
|
let font = await cache.get(key);
|
|
if (!font) {
|
|
const response = await fetch(url);
|
|
if (!response.ok) return new Response("", { status: 502 });
|
|
font = new Uint8Array(await response.arrayBuffer());
|
|
await cache.put(key, font, { ttl: 31536000 });
|
|
}
|
|
return new Response(font, { headers: { "content-type": "font/woff2", "cache-control": "public, max-age=31536000, immutable" } });
|
|
});
|
|
},
|
|
};
|
|
}
|