0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-03 22:29:08 -05:00
astro/packages/integrations/web-vitals/src/middleware.ts
2024-05-03 17:40:53 +02:00

60 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { MiddlewareHandler } from 'astro';
/**
* Middleware which adds the web vitals `<meta>` tag to each pages `<head>`.
*
* @example
* <meta name="x-astro-vitals-route" content="/blog/[slug]" />
*/
export const onRequest: MiddlewareHandler = async ({ params, url }, next) => {
const response = await next();
const contentType = response.headers.get('Content-Type');
if (contentType !== 'text/html') return response;
const webVitalsMetaTag = getMetaTag(url, params);
return new Response(
response.body
?.pipeThrough(new TextDecoderStream())
.pipeThrough(HeadInjectionTransformStream(webVitalsMetaTag))
.pipeThrough(new TextEncoderStream()),
response
);
};
/** TransformStream which injects the passed HTML just before the closing </head> tag. */
function HeadInjectionTransformStream(htmlToInject: string) {
let hasInjected = false;
return new TransformStream({
transform: (chunk, controller) => {
if (!hasInjected) {
const headCloseIndex = chunk.indexOf('</head>');
if (headCloseIndex > -1) {
chunk = chunk.slice(0, headCloseIndex) + htmlToInject + chunk.slice(headCloseIndex);
hasInjected = true;
}
}
controller.enqueue(chunk);
},
});
}
/** Get a `<meta>` tag to identify the current Astro route. */
function getMetaTag(url: URL, params: Record<string, string | undefined>) {
let route = url.pathname;
for (const [key, value] of Object.entries(params)) {
if (value) route = route.replace(value, `[${key}]`);
}
route = miniEncodeAttribute(stripTrailingSlash(route));
return `<meta name="x-astro-vitals-route" content="${route}" />`;
}
function stripTrailingSlash(str: string) {
return str.length > 1 && str.at(-1) === '/' ? str.slice(0, -1) : str;
}
function miniEncodeAttribute(str: string) {
return str
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;');
}