summaryrefslogtreecommitdiff
path: root/packages/markdown-support/src/index.ts
blob: 08f171c3ce744a517db52d5e4b6c53505e442143 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import type { AstroMarkdownOptions } from './types';

import createCollectHeaders from './rehype-collect-headers.js';
import scopedStyles from './remark-scoped-styles.js';
import { remarkCodeBlock, rehypeCodeBlock } from './codeblock.js';
import raw from 'rehype-raw';

import unified from 'unified';
import markdown from 'remark-parse';
import markdownToHtml from 'remark-rehype';
// import smartypants from '@silvenon/remark-smartypants';
import rehypeStringify from 'rehype-stringify';

export interface MarkdownRenderingOptions extends Partial<AstroMarkdownOptions> {
  $?: {
    scopedClassName: string | null;
  };
  mode: 'md' | 'astro-md';
}

/** Internal utility for rendering a full markdown file and extracting Frontmatter data */
export async function renderMarkdownWithFrontmatter(contents: string, opts?: MarkdownRenderingOptions | null) {
  // Dynamic import to ensure that "gray-matter" isn't built by Snowpack
  const { default: matter } = await import('gray-matter');
  const { data: frontmatter, content } = matter(contents);
  const value = await renderMarkdown(content, { ...opts, mode: 'md' });
  return { ...value, frontmatter };
}

/** Shared utility for rendering markdown */
export async function renderMarkdown(content: string, opts?: MarkdownRenderingOptions | null) {
  const { $: { scopedClassName = null } = {}, mode = 'astro-md', footnotes: useFootnotes = true, gfm: useGfm = true } = opts ?? {};
  const { headers, rehypeCollectHeaders } = createCollectHeaders();

  let parser = unified().use(markdown).use(remarkCodeBlock());

  if (scopedClassName) {
    parser = parser.use(scopedStyles(scopedClassName));
  }

  if (useGfm) {
    const { default: gfm } = await import('remark-gfm');
    parser = parser.use(gfm);
  }

  if (useFootnotes) {
    const { default: footnotes } = await import('remark-footnotes');
    parser = parser.use(footnotes);
  }

  let result: string;
  try {
    const vfile = await parser
      .use(markdownToHtml, { allowDangerousHtml: true, passThrough: ['raw'] })
      .use(raw)
      .use(rehypeCollectHeaders)
      .use(rehypeCodeBlock())
      .use(rehypeStringify)
      .process(content);
    result = vfile.contents.toString();
  } catch (err) {
    throw err;
  }

  return {
    astro: { headers, source: content },
    content: result.toString(),
  };
}