Last active
June 17, 2024 17:45
-
-
Save Igloczek/30d03cae4230396190c18916c95601d3 to your computer and use it in GitHub Desktop.
Astro middleware for inlining critical JS to reduce requests chains
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Not sure whether it's genius or stupid, but it definitely works. | |
// The goal is to reduce chaining critical requests. | |
// By default in most cases Astro do this: html -> small chunk -> big chunk(s), which is kinda like Require.js, where you need to download file, to know what file you want to download. | |
// Code below takes this small chunk and inlines it into html, so we have at least one client-side request to make less: html -> big chunk(s). | |
// Additionally we can add "modulepreload" to start fetching those files faster (not sure where in this particular case it makes any sense, but was easy to add, so I did it). | |
// For sure this code isn't optimal, I just quickly hacked it just to have PoC, but I belive it won't hurt server-side performance that much. | |
// If you know some better way to do it, that doesn't require digging through HTML and replacing strings, please let me know. | |
import fs from "node:fs" | |
import path from "node:path" | |
import { fileURLToPath } from "node:url" | |
import { defineMiddleware } from "astro:middleware" | |
const astroRoot = fileURLToPath( | |
new URL("../../../dist/client/_astro", import.meta.url), | |
) | |
const jsFiles = fs | |
.readdirSync(astroRoot) | |
.filter((file) => file.endsWith(".js")) | |
.map((file) => { | |
const filePath = path.join(astroRoot, file) | |
const content = fs | |
.readFileSync(filePath, "utf-8") | |
.replaceAll('import"./', 'import"/_astro/') | |
.replaceAll('from"./', 'from"/_astro/') | |
.replaceAll("sourceMappingURL=", "sourceMappingURL=/_astro/") | |
const scripts = [ | |
...content.matchAll(/import"([^"]+)"|import[^"]+from"([^"]+)/g), | |
]?.map((match) => match[1] || match[2]) | |
const modules = scripts | |
.map((path) => `<link rel="modulepreload" href="${path}" />`) | |
.join("") | |
return { | |
path: file, | |
content, | |
modules, | |
} | |
}) | |
export const onRequest = defineMiddleware(async (context, next) => { | |
try { | |
const response = await next() | |
if (response.headers.get("Content-Type") !== "text/html") { | |
return response | |
} | |
const raw = await response.text() | |
const html = raw.replace( | |
/<script\stype="module"\s+src="\/_astro\/([^"]+)"><\/script>/g, | |
(match, scriptPath) => { | |
const jsFile = jsFiles.find((file) => file.path === scriptPath) | |
if (jsFile) { | |
return `${jsFile.modules}<script type="module">${jsFile.content}</script>` | |
} else { | |
return match | |
} | |
}, | |
) | |
return new Response(html, { | |
status: response.status, | |
headers: response.headers, | |
}) | |
} catch (error) { | |
console.error(error) | |
return next() | |
} | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment