diff --git a/index.ts b/index.ts index 1f2665f..7949847 100644 --- a/index.ts +++ b/index.ts @@ -7,23 +7,51 @@ import { experimental_AstroContainer as AstroContainer } from "astro/container"; import ts from "typescript"; import type { Integration, IntegrationContext } from "muddle/contracts"; -export interface AstroIntegrationOptions { dir?: string; partials?: boolean } +export interface AstroIntegrationOptions { + dir?: string; + partials?: boolean; +} function routeFor(root: string, file: string): string { - const path = relative(root, file).replace(/\\/g, "/").replace(/\.astro$/, ""); + const path = relative(root, file) + .replace(/\\/g, "/") + .replace(/\.astro$/, ""); return `/${path.replace(/index$/, "").replace(/\/+/g, "/") || ""}` || "/"; } -export function astroIntegration(options: AstroIntegrationOptions = {}): Integration { +export function astroIntegration( + options: AstroIntegrationOptions = {}, +): Integration { return { name: "astro", async setup(context: IntegrationContext) { - context.loader("astro", { name: "astro", extensions: [".astro"], async load(file) { return { data: {}, content: await renderAstro(file.source, undefined, file.path) }; } }); + context.loader("astro", { + name: "astro", + extensions: [".astro"], + async load(file) { + return { + data: {}, + content: await renderAstro(file.source, undefined, file.path), + }; + }, + }); const dir = resolve(context.config.root, options.dir ?? "src/pages"); if (!existsSync(dir)) return; for (const file of await astroFiles(dir)) { const route = routeFor(dir, file); - context.route("GET", route, async (request) => new Response(await renderAstro(await readFile(file, "utf8"), request, file), { headers: { "content-type": "text/html; charset=utf-8" } })); + context.route( + "GET", + route, + async (request) => + new Response( + await renderAstro(await readFile(file, "utf8"), request, file), + { + headers: { + "content-type": "text/html; charset=utf-8", + }, + }, + ), + ); } }, }; @@ -39,42 +67,92 @@ async function astroFiles(dir: string): Promise { return files; } -export async function renderAstro(source: string, request = new Request("http://localhost/"), filename = resolve("inline.astro")): Promise { +export async function renderAstro( + source: string, + request = new Request("http://localhost/"), + filename = resolve("inline.astro"), +): Promise { const component = await compileAstro(source, filename); const container = await AstroContainer.create(); return container.renderToString(component, { request }); } -async function compileAstro(source: string, filename: string, ancestors: string[] = []): Promise { - if (ancestors.includes(filename)) throw new Error(`Circular Astro import ${filename}`); - const result = await transform(source, { filename, internalURL: "astro/runtime/server/index.js" }); - let code = result.code.replace(/,\s*createMetadata as \$\$createMetadata/, "").replace(/export const \$\$metadata = [^\n]+\n/g, ""); +async function compileAstro( + source: string, + filename: string, + ancestors: string[] = [], +): Promise { + if (ancestors.includes(filename)) { + throw new Error(`Circular Astro import ${filename}`); + } + const result = await transform(source, { + filename, + internalURL: "astro/runtime/server/index.js", + }); + let code = result.code + .replace(/,\s*createMetadata as \$\$createMetadata/, "") + .replace(/export const \$\$metadata = [^\n]+\n/g, ""); for (const match of [...code.matchAll(/from\s+["']([^"']+)["']/g)]) { const specifier = match[1]!; let url: string; if (specifier.endsWith(".astro")) { const file = resolve(filename, "..", specifier); - const child = await compileAstro(await readFile(file, "utf8"), file, [...ancestors, filename]); + const child = await compileAstro(await readFile(file, "utf8"), file, [ + ...ancestors, + filename, + ]); const key = `__muddle_astro_${crypto.randomUUID().replaceAll("-", "")}`; (globalThis as Record)[key] = child; - url = `data:text/javascript;base64,${Buffer.from(`export default globalThis[${JSON.stringify(key)}]`).toString("base64")}`; - } else url = specifier.startsWith(".") ? pathToFileURL(resolve(filename, "..", specifier)).href : import.meta.resolve(specifier); - code = code.replaceAll(`"${specifier}"`, JSON.stringify(url)).replaceAll(`'${specifier}'`, JSON.stringify(url)); + url = `data:text/javascript;base64,${Buffer.from( + `export default globalThis[${JSON.stringify(key)}]`, + ).toString("base64")}`; + } else { + url = specifier.startsWith(".") + ? pathToFileURL(resolve(filename, "..", specifier)).href + : import.meta.resolve(specifier); + } + code = code + .replaceAll(`"${specifier}"`, JSON.stringify(url)) + .replaceAll(`'${specifier}'`, JSON.stringify(url)); } - code = ts.transpileModule(code, { compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 } }).outputText; - return (await import(`data:text/javascript;base64,${Buffer.from(code).toString("base64")}`)).default; + code = ts.transpileModule(code, { + compilerOptions: { + module: ts.ModuleKind.ESNext, + target: ts.ScriptTarget.ES2022, + }, + }).outputText; + return ( + await import( + `data:text/javascript;base64,${Buffer.from(code).toString("base64")}` + ) + ).default; } /** Import into TemplateContext with `.import("Card", astroComponent("./Card.astro"))`. */ export function astroComponent(file: string) { return { - async render(props: Record, context: { params: Record; locals: Record }) { + async render( + props: Record, + context: { + params: Record; + locals: Record; + }, + ) { const path = resolve(file); const component = await compileAstro(await readFile(path, "utf8"), path); const container = await AstroContainer.create(); const slots: Record = {}; - for (const [key, value] of Object.entries(props)) if (typeof value === "function") slots[key === "content" ? "default" : key] = await value(); - return container.renderToString(component, { props, params: context.params, locals: context.locals, slots }); + for (const [key, value] of Object.entries(props)) { + if (typeof value === "function") { + slots[key === "content" ? "default" : key] = await value(); + } + } + return container.renderToString(component, { + props, + params: context.params, + locals: context.locals, + slots, + }); }, }; } diff --git a/package.json b/package.json index 8c3e392..b8c80cb 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { - "name": "@muddle/astro", - "type": "module", - "exports": "./index.ts", - "version": "0.1.0", - "peerDependencies": { - "muddle": "*" - }, - "dependencies": { - "@astrojs/compiler": "2", - "astro": "5", - "typescript": "5.9.3" - } + "name": "@muddle/astro", + "type": "module", + "exports": "./index.ts", + "version": "0.1.0", + "peerDependencies": { + "muddle": "*" + }, + "dependencies": { + "@astrojs/compiler": "2", + "astro": "5", + "typescript": "5.9.3" + } }