initial commit

This commit is contained in:
aspizu
2025-03-10 02:58:09 +05:30
commit 8137b02b1e
10 changed files with 953 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
import type {AstroIntegration, ContentEntryType, HookParameters} from "astro"
import {fileURLToPath} from "url"
import gemtextVitePlugin from "./vitePlugin.js"
export const ext = "gmi"
type SetupHookParams = HookParameters<"astro:config:setup"> & {
// `addPageExtension` and `contentEntryType` are not a public APIs
// Add type defs here
addPageExtension: (extension: string) => void
addContentEntryType: (contentEntryType: ContentEntryType) => void
}
export default function gemtext(): AstroIntegration {
return {
name: "astro-gemtext",
hooks: {
"astro:config:setup": async (_params) => {
const params = _params as SetupHookParams
params.addRenderer({
name: "astro-gemtext",
serverEntrypoint: fileURLToPath(
new URL("./renderer.js", import.meta.url),
),
})
params.addPageExtension(ext)
params.addContentEntryType({
extensions: [`.${ext}`],
getEntryInfo: async (params) => {
throw new Error("Not implemented")
},
})
params.updateConfig({
vite: {
plugins: [gemtextVitePlugin()],
},
})
},
},
}
}
+77
View File
@@ -0,0 +1,77 @@
import type {NamedSSRLoadedRendererValue} from "astro"
import {AstroError} from "astro/errors"
import {AstroJSX, jsx} from "astro/jsx-runtime"
import {renderJSX} from "astro/runtime/server/index.js"
const slotName = (str: string) =>
str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase())
// NOTE: In practice, MDX components are always tagged with `__astro_tag_component__`, so the right renderer
// is used directly, and this check is not often used to return true.
export async function check(
Component: any,
props: any,
{default: children = null, ...slotted} = {},
) {
if (typeof Component !== "function") return false
const slots: Record<string, any> = {}
for (const [key, value] of Object.entries(slotted)) {
const name = slotName(key)
slots[name] = value
}
try {
const result = await Component({...props, ...slots, children})
return result[AstroJSX]
} catch (e) {
throwEnhancedErrorIfMdxComponent(e as Error, Component)
}
return false
}
export async function renderToStaticMarkup(
this: any,
Component: any,
props = {},
{default: children = null, ...slotted} = {},
) {
const slots: Record<string, any> = {}
for (const [key, value] of Object.entries(slotted)) {
const name = slotName(key)
slots[name] = value
}
const {result} = this
try {
const html = await renderJSX(
result,
jsx(Component, {...props, ...slots, children}),
)
return {html}
} catch (e) {
throwEnhancedErrorIfMdxComponent(e as Error, Component)
throw e
}
}
function throwEnhancedErrorIfMdxComponent(error: Error, Component: any) {
// if the exception is from an mdx component
// throw an error
if (Component[Symbol.for("mdx-component")]) {
// if it's an existing AstroError, we don't need to re-throw, keep the original hint
if (AstroError.is(error))
return // Mimic the fields of the internal `AstroError` class (not from `astro/errors`) to
// provide better title and hint for the error overlay
;(error as any).title = error.name
;(error as any).hint =
`This issue often occurs when your MDX component encounters runtime errors.`
throw error
}
}
const renderer: NamedSSRLoadedRendererValue = {
name: "astro:jsx",
check,
renderToStaticMarkup,
}
export default renderer
+50
View File
@@ -0,0 +1,50 @@
import {HTMLRenderer, parse} from "gemtext"
import {pathToFileURL} from "url"
import type {Plugin} from "vite"
import {ext} from "./index.js"
function transform(code: string, id: string) {
const result = parse(code)
const html = result.generate(HTMLRenderer)
return `
import {
createComponent,
render,
renderComponent,
unescapeHTML,
} from "astro/runtime/server/index.js";
import { Fragment, jsx as h } from "astro/jsx-runtime";
import Layout from "../layouts/Layout.astro";
export const name = "TypstComponent";
export const html = ${JSON.stringify(html)};
export const frontmatter = undefined;
export const file = ${JSON.stringify(id)};
export const url = ${JSON.stringify(pathToFileURL(id))};
export function compiledContent() {
return html;
}
export function getHeadings() {
return undefined;
}
export async function Content() {
const content = h(Fragment, {"set:html": html});
return h(Layout, {title: url, children: content});
}
export default Content;
`
}
export default function gemtextVitePlugin(): Plugin {
// let server: ViteDevServer
return {
name: "astro-gemtext",
enforce: "pre",
transform(code, id) {
if (!id.endsWith(`.${ext}`)) return
return {
code: transform(code, id),
map: null,
}
},
}
}