I decided to try out Astro by building a web development blog integrated with contentful. I got everything working except for the code blocks. I create an embedded entry for the code block in my content model, which returns the following nodeType:
{
nodeType: 'embedded-entry-block',
data: {
target: {
metadata: [Object],
sys: [Object],
fields: {
code:
'```html\n<!DOCTYPE html>\n<html>\n<head>\n<title>My First Astro App</title>\n</head>\n <body>\n<h1>Hello, World!</h1>\n</body>\n</html>\n```'
}
}
},
content: []
}
I cannot figure out how to use documentToHtmlString
from "@contentful/rich-text-html-renderer"
to display the code block in my blog with a themed Prism
component.
I passed a custom rendering component for the embedded entry to the documentToHtmlString
options, and extracted the lang and HTML code, but then I could not figure out how to return a themed Prism code block. However, I did manage to get it working without styling using the runHighlighterWithAstro
function from the @astrojs/prism
library.
---
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import { BLOCKS } from "@contentful/rich-text-types";
import ContentLayout from "@layouts/ContentLayout.astro";
import BlogPostMeta from "@components/BlogPostMeta.astro";
import { BlogPost, contentfulClient } from "@lib/contentful";
import { Prism } from "@astrojs/prism";
import { runHighlighterWithAstro } from "@astrojs/prism/dist/highlighter";
export async function getStaticPaths() {
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
function extractCode(str: string): { lang: string; code: string } | null {
const match = str?.match(/```(\w+)\n([\s\S]+)\n```/);
if (match) {
return {
lang: match[1],
code: match[2],
};
}
return null;
}
const renderOptions = {
renderNode: {
[BLOCKS.EMBEDDED_ENTRY]: (node: any) => {
const { code } = node.data.target.fields;
const extractedCode = extractCode(code);
if (extractedCode) {
const { lang, code } = extractedCode;
const { classLanguage, html } = runHighlighterWithAstro(lang, code);
return `<pre class=${classLanguage} set:html=${html}></pre>`;
}
return "";
},
},
};
const pages = entries.items.map((item) => ({
params: { slug: item.fields.slug },
props: {
title: item.fields.title,
description: item.fields.description,
content: documentToHtmlString(item.fields.content, renderOptions),
date: new Date(item.fields.date).toLocaleDateString(),
},
}));
return pages;
}
const { slug } = Astro.params;
if (typeof slug !== "string") {
throw Error(`Slug should be a string. Received: ${slug}`);
}
const { description, content, title, date } = Astro.props;
---
<ContentLayout title={title} date={date}>
<BlogPostMeta
title={title}
description={description}
publishDate={date}
pagePath={`/posts/${slug}`}
slot='meta'
/>
<body>
<h1>{title}</h1>
<time>{date}</time>
<article slot='content' set:html={content} />
</body>
</ContentLayout>
I wanted to use a component, CodeBlock
that contains my styled Prism element. Unfortunately, I may be misunderstanding something about Astro, coming from React land.
---
import { Prism } from "@astrojs/prism";
const { language, code } = Astro.props;
---
<Prism lang={language} code={code} />
<style is:global>...
</style>