logo

JavaScript - Markdown

Use unifed and remark / rehype plugins:

Example:

const markdown = await unified()
  .use(remarkParse)
  .use(remarkMath)
  .use(remarkRehype)
  .use(remarkGfm)
  .use(rehypeKatex)
  .use(rehypeFormat)
  .use(rehypeStringify)
  .use(rehypePrism)
  .process(raw || ''); // raw is the raw markdown text.

// content is the generated HTML.
const content = markdown.toString();

// ...

// render the HTML.
<div dangerouslySetInnerHTML={{ __html: content }} />;

You can also use remark which is a shorthand for unified, remark-parse and remark-stringify.

import { remark } from 'remark';

const file = await remark().process(doc);

Some of the commonly used plugins:

  • remark-rehype: turn markdown into HTML (converts Markdown AST mdast to HTML AST hast).
    • operations on mdast should be before remark-rehype; operations on hast should be after remark-rehype.
  • remark-gfm: add support for GFM (GitHub flavored markdown)
  • remark-toc: generate a table of contents
  • remark-math: support math ( C L C_L ).
  • remark-mdx: support MDX syntax (JSX, export/import, expressions).
  • remark-ruby: new syntax for ruby characters {[紳][士]}^([へん][たい]).
  • rehype-parse: parse HTML

Full list can be found in https://github.com/remarkjs/remark/blob/main/doc/plugins.md

remark-directive

  • ::: - containerDirective, analogous to divs containing further blocks
  • :: - leafDirective, analogous to empty divs
  • : - textDirective, analogous to spans

Syntax:

# textDirective
:name[content]{key=val}

# leafDirective
:: name [content] {key=val}

# containerDirective
::: name [inline-content] {key=val}
contents, which are sometimes further block elements
:::

Example

To create a styled box:

// Create your own plugin
function StyledAlert() {
  return function (tree) {
    visit(tree, (node) => {
      if (
        node.type === 'containerDirective' ||
        node.type === 'leafDirective' ||
        node.type === 'textDirective'
      ) {
        if (node.name !== 'alert') return;

        const data = node.data || (node.data = {});
        const tagName = node.type === 'textDirective' ? 'span' : 'div';

        data.hName = tagName;
        data.hProperties = h(tagName, node.attributes || {}).properties;

        node.children.unshift(h('span', node.attributes.class.toUpperCase()));
      }
    });
  };
}

// Use the plugin in unified:
const markdown = await unified()
  .use(remarkParse)
  .use(remarkDirective)
  .use(StyledAlert)
  //...
  .process(page.content || '');

In markdown:

:::alert{.warning}
`remark-directive` must be before `remark-rehype`
:::

In CSS:

.warning {
  background: rgb(255, 244, 229);
  color: rgb(102, 60, 0);
  padding: 16px;
  border-radius: 4px;
  margin-bottom: 20px;
}

This will be rendered like this:

WARNING

remark-directive must be before remark-rehype