import type { AstroMarkdownOptions, MarkdownRenderingOptions } from './types'; import createCollectHeaders from './rehype-collect-headers.js'; import scopedStyles from './remark-scoped-styles.js'; import { remarkExpressions, loadRemarkExpressions } from './remark-expressions.js'; import rehypeExpressions from './rehype-expressions.js'; import rehypeIslands from './rehype-islands.js'; import { remarkJsx, loadRemarkJsx } from './remark-jsx.js'; import rehypeJsx from './rehype-jsx.js'; import remarkPrism from './remark-prism.js'; import remarkUnwrap from './remark-unwrap.js'; import { loadPlugins } from './load-plugins.js'; import { unified } from 'unified'; import markdown from 'remark-parse'; import markdownToHtml from 'remark-rehype'; import rehypeStringify from 'rehype-stringify'; import rehypeRaw from 'rehype-raw'; import matter from 'gray-matter'; export { AstroMarkdownOptions, MarkdownRenderingOptions }; /** Internal utility for rendering a full markdown file and extracting Frontmatter data */ export async function renderMarkdownWithFrontmatter(contents: string, opts?: MarkdownRenderingOptions | null) { const { data: frontmatter, content } = matter(contents); const value = await renderMarkdown(content, opts); return { ...value, frontmatter }; } export const DEFAULT_REMARK_PLUGINS = [ 'remark-gfm', 'remark-footnotes', // TODO: reenable smartypants! // '@silvenon/remark-smartypants' ] export const DEFAULT_REHYPE_PLUGINS = [ // empty ] /** Shared utility for rendering markdown */ export async function renderMarkdown(content: string, opts?: MarkdownRenderingOptions | null) { const { remarkPlugins = DEFAULT_REMARK_PLUGINS, rehypePlugins = DEFAULT_REHYPE_PLUGINS } = opts ?? {}; const scopedClassName = opts?.$?.scopedClassName; const mode = opts?.mode ?? "mdx"; const isMDX = mode === 'mdx'; const { headers, rehypeCollectHeaders } = createCollectHeaders(); await Promise.all([loadRemarkExpressions(), loadRemarkJsx()]); // Vite bug: dynamically import() these because of CJS interop (this will cache) let parser = unified() .use(markdown) .use(isMDX ? [remarkJsx] : []) .use(isMDX ? [remarkExpressions] : []) .use([remarkUnwrap]) const loadedRemarkPlugins = await Promise.all(loadPlugins(remarkPlugins)); const loadedRehypePlugins = await Promise.all(loadPlugins(rehypePlugins)); loadedRemarkPlugins.forEach(([plugin, opts]) => { parser.use([[plugin, opts]]); }); if (scopedClassName) { parser.use([scopedStyles(scopedClassName)]); } parser.use([remarkPrism(scopedClassName)]); parser.use([[markdownToHtml as any, { allowDangerousHtml: true, passThrough: ['raw', 'mdxTextExpression', 'mdxJsxTextElement', 'mdxJsxFlowElement']}]]); loadedRehypePlugins.forEach(([plugin, opts]) => { parser.use([[plugin, opts]]); }); parser .use(isMDX ? [rehypeJsx] : []) .use(isMDX ? [rehypeExpressions] : []) .use(isMDX ? [] : [rehypeRaw]) .use(rehypeIslands) let result: string; try { const vfile = await parser .use([rehypeCollectHeaders]) .use(rehypeStringify, { allowDangerousHtml: true }) .process(content); result = vfile.toString(); } catch (err) { console.error(err); throw err; } return { metadata: { headers, source: content, html: result.toString() }, code: result.toString(), }; } export default renderMarkdownWithFrontmatter;