remark-rehype

repository·main·Indexed 18 days ago

https://github.com/remarkjs/remark-rehype

A unified plugin that transforms a Markdown Abstract Syntax Tree (mdast) into an HTML Abstract Syntax Tree (hast), serving as the bridge between the remark and rehype ecosystems. It supports configurable HTML handling, footnote customization via remark-gfm, and custom node processing through handlers and passThrough options. Version 11.1.2 is ESM only and requires Node.js 16+.

Tokens
4.1K
Snippets
12
Records
17
Agent score
63%

What's inside remark-rehype

  1. Handle unknown nodes in remark-rehype

    main

    By default, remark-rehype handles unknown nodes (nodes not in handlers or passThrough) as follows:

    1. If the node has a value (and lacks data.hName, data.hProperties, or data.hChildren), it creates a hast text node.
    2. Otherwise, it creates a <div> element (the tag name can be customized via data.hName) with children mapped from mdast to hast.

    You can override this behavior by providing an unknownHandler in the plugin options.

  2. Understand the Syntax Tree transformation

    main

    This project transforms mdast (Markdown Abstract Syntax Tree) nodes into hast (HTML Abstract Syntax Tree) nodes.

    Key details:

    • It extends mdast by supporting data fields on nodes to specify how they should be transformed (see mdast-util-to-hast).
    • It extends hast by using semistandard raw nodes for raw HTML.
  3. How remark-rehype works: mdast to hast transformation

    main

    The remark-rehype plugin acts as a bridge between the remark (markdown) ecosystem and the rehype (HTML) ecosystem. It transforms the markdown syntax tree (mdast) into an HTML syntax tree (hast).

    Ecosystem Boundaries

    • remark plugins: Operate on mdast (e.g., linting, formatting markdown).
    • rehype plugins: Operate on hast (e.g., minifying HTML, sanitizing, syntax highlighting).
    • remark-rehype: The transition point. Once this plugin is called, subsequent plugins in the unified pipeline must be rehype plugins.

    Transformation Modes

    When calling unified().use(remarkRehype[, destination][, options]):

    • Mutate mode (Default): Returns a hast tree. Plugins used after remark-rehype are treated as rehype plugins.
    • Bridge mode: If a destination (a Processor) is provided, it runs the rehype plugins on a hast tree and then discards the result. This is rarely used.
  4. Convert Markdown to HTML using remark-rehype

    main

    To transform markdown into HTML, use remark-rehype within a unified pipeline. Typically, you will use remark-parse to parse the markdown, remark-rehype to switch from the markdown (mdast) ecosystem to the HTML (hast) ecosystem, and rehype-stringify to generate the final HTML string.

    Note that any plugins used after remark-rehype in the pipeline must be rehype plugins.

    import rehypeDocument from 'rehype-document'
    import rehypeFormat from 'rehype-format'
    import rehypeStringify from 'rehype-stringify'
    import remarkParse from 'remark-parse'
    import remarkRehype from 'remark-rehype'
    import {read} from 'to-vfile'
    import {unified} from 'unified'
    import {reporter} from 'vfile-reporter'
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkRehype)
      .use(rehypeDocument)
      .use(rehypeFormat)
      .use(rehypeStringify)
      .process(await read('example.md'))
    
    console.error(reporter(file))
    console.log(String(file))
  5. Install remark-rehype in Deno or Browsers

    main

    For Deno environments, use esm.sh to import the package. For browser environments, use a module script with the ?bundle query parameter.

    Deno:

    import remarkRehype from 'https://esm.sh/remark-rehype@11'

    Browser:

    <script type="module">
      import remarkRehype from 'https://esm.sh/remark-rehype@11?bundle'
    </script>
  6. Configure remark-rehype options

    main

    The remarkRehype plugin accepts an options object to control HTML handling, footnote behavior, and node processing.

    HTML Handling

    • allowDangerousHtml (boolean, default: false): If true, persists raw HTML (as raw nodes) in the hast tree. This is useful when paired with rehype-raw to parse embedded HTML.

    Footnote Configuration

    Footnotes are supported via remark-gfm. Options include:

    • footnoteLabel (string, default: 'Footnotes'): The label for the footnotes section (used by screen readers).
    • footnoteLabelTagName (string, default: 'h2'): The HTML tag for the footnote label.
    • footnoteLabelProperties (Properties, default: {className: ['sr-only']}): CSS properties for the label. By default, it uses sr-only to hide the label from sighted users while keeping it accessible.
    • clobberPrefix (string, default: 'user-content-'): A prefix added to footnote id attributes to prevent DOM clobbering (where IDs conflict with global window properties).
    • footnoteBackLabel and footnoteBackContent: Define the natural language/content for backreferences.

    Node Processing

    • handlers (Handlers, optional): Extra handlers for specific node types.
    • passThrough (Array<Nodes['type']>, optional): A list of custom mdast node types to keep as-is in the hast tree.
    • unknownHandler (Handler, optional): A custom function to handle nodes that don't match known types or passThrough.
  7. Support HTML in markdown properly (Secure approach)

    main

    If you do not trust the authors or want to ensure rehype plugins can process embedded HTML, use rehype-raw to parse the HTML into proper nodes and rehype-sanitize to strip dangerous elements.

    This workflow involves:

    1. Passing allowDangerousHtml: true to remark-rehype.
    2. Using rehype-raw to turn raw HTML into nodes.
    3. Using rehype-sanitize to clean the tree.
    4. Using rehype-stringify to output the result.
    import rehypeSanitize from 'rehype-sanitize'
    import rehypeStringify from 'rehype-stringify'
    import rehypeRaw from 'rehype-raw'
    import remarkParse from 'remark-parse'
    import remarkRehype from 'remark-rehype'
    import {unified} from 'unified'
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkRehype, {allowDangerousHtml: true})
      .use(rehypeRaw)
      .use(rehypeSanitize)
      .use(rehypeStringify)
      .process('<a href="/wiki/Dysnomia_(moon)" onclick="alert(1)">Dysnomia</a>')
    
    console.log(String(file))
  8. Style footnotes with CSS

    main

    The HTML generated for footnotes (especially when using remark-gfm) follows GitHub's structure. You can use the following CSS to style the .footnotes section and the .sr-only class (used for accessible headings) to match GitHub's appearance.

    /* Style the footnotes section. */
    .footnotes {
      font-size: smaller;
      color: #8b949e;
      border-top: 1px solid #30363d;
    }
    
    /* Hide the section label for visual users. */
    .sr-only {
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      word-wrap: normal;
      border: 0;
    }
    
    /* Place `[` and `]` around footnote calls. */
    [data-footnote-ref]::before {
      content: '[';
    }
    
    [data-footnote-ref]::after {
      content: ']';
    }
  9. Translate footnote labels for accessibility

    main

    When working with non-English markdown (e.g., German), you can customize the footnote labels and back-reference labels via remark-rehype options to ensure screen readers pronounce them correctly.

    Use the following options:

    • footnoteLabel: A string to replace the default 'Footnotes' heading.
    • footnoteBackLabel(referenceIndex, rereferenceIndex): A function that returns the label for the back-reference link (the 'return to reference' link).
    const file = await unified()
      .use(remarkParse)
      .use(remarkGfm)
      .use(remarkRehype, {
        footnoteBackLabel(referenceIndex, rereferenceIndex) {
          return (
            'Hochspringen nach: ' +
            (referenceIndex + 1) +
            (rereferenceIndex > 1 ? '-' + rereferenceIndex : '')
          )
        },
        footnoteLabel: 'Fußnoten'
      })
      .use(rehypeStringify)
      .process(doc)
  10. Support HTML in markdown (Naive approach)

    main

    If you completely trust the input markdown authors and want to allow embedded HTML to pass through, you can enable allowDangerousHtml in both remark-rehype and rehype-stringify.

    ⚠️ Danger: This approach is vulnerable to XSS attacks (e.g., via onclick attributes) because it does not sanitize the HTML.

    import rehypeStringify from 'rehype-stringify'
    import remarkParse from 'remark-parse'
    import remarkRehype from 'remark-rehype'
    import {unified} from 'unified'
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkRehype, {allowDangerousHtml: true})
      .use(rehypeStringify, {allowDangerousHtml: true})
      .process('<a href="/wiki/Dysnomia_(moon)" onclick="alert(1)">Dysnomia</a>')
    
    console.log(String(file))
  11. Configure TypeScript types for mdast and hast

    main

    The package is fully typed with TypeScript. You can use the types from mdast-util-to-hast to register data fields with @types/mdast and Raw nodes with @types/hast for better type safety when visiting nodes.

    /**
     * @import {Root as HastRoot} from 'hast'
     * @import {Root as MdastRoot} from 'mdast'
     * @import {}\nfrom 'mdast-util-to-hast'
     */
    
    import {visit} from 'unist-util-visit'
    
    const mdastNode = /** @type {MdastRoot} */ ({/* … */})
    console.log(mdastNode.data?.hName) // Typed as `string | undefined`.
    
    const hastNode = /** @type {HastRoot} */ ({/* … */})
    
    visit(hastNode, function (node) {
      // `node` can now be `raw`.
    })