markdown-to-jsx

repository·main·Indexed 25 days ago

https://github.com/quantizor/markdown-to-jsx

A high-performance, GFM and CommonMark compliant markdown parser and compiler for JavaScript and TypeScript. It converts markdown into JSX or HTML without using dangerouslySetInnerHTML. It provides specialized entry points for React (including RSC support), React Native, SolidJS, Vue.js, HTML strings for SSR, and markdown normalization.

Tokens
50.3K
Snippets
109
Records
250
Agent score
80%

What's inside markdown-to-jsx

  1. What is GitHub Flavored Markdown (GFM)?

    main

    GitHub Flavored Markdown (GFM) is a dialect of Markdown used for user content on GitHub.com and GitHub Enterprise. It is a strict superset of the CommonMark Spec. Features in GFM that are not part of the original CommonMark Spec are considered extensions.

    Note that GitHub.com and GitHub Enterprise perform additional post-processing and sanitization on the HTML generated from GFM to ensure security and consistency.

  2. Handling special HTML tags (script, style, pre)

    main

    Tags designed to contain literal content, such as <script>, <style>, and <pre>, behave differently than standard HTML blocks. Instead of ending at the first blank line, they only end when a corresponding end tag is found. This allows them to contain blank lines internally.

    If no matching end tag is found, the block continues until the end of the document or the end of the enclosing block (like a blockquote or list item).

    <script type="text/javascript">
    // JavaScript example
    
    document.getElementById("demo").innerHTML = "Hello JavaScript!";
    </script>
  3. Rules for `_` (Underscore) Emphasis and Strong Emphasis

    main

    The parser uses specific rules for underscore-based delimiters:

    Emphasis (_)

    • Whitespace: An opening _ followed by whitespace is not emphasis.
    • Punctuation: An opening _ preceded by an alphanumeric and followed by punctuation is not part of a left-flanking delimiter run.
    • Intraword: Unlike asterisks, intraword emphasis with _ is generally disallowed (e.g., foo_bar_ is not emphasis).
    • Closing Delimiter: A closing _ preceded by whitespace is not valid.

    Strong Emphasis (__)

    • Whitespace: An opening __ followed by whitespace is not strong emphasis.
    • Punctuation: An opening __ preceded by an alphanumeric and followed by punctuation is not part of a left-flanking delimiter run.
    • Intraword: Intraword strong emphasis with __ is forbidden (e.g., foo__bar__ is not strong emphasis).
    • Closing Delimiter: A closing __ preceded by whitespace is not valid.
  4. Syntax and rules for block quotes

    main

    A block quote is created using a block quote marker, which consists of 0-3 spaces of initial indent, followed by either:

    1. The character > and a following space.
    2. A single character > not followed by a space.

    Key Rules:

    • Basic Case: Prepending a marker to each line in a sequence of blocks creates a block quote.
    • Laziness: You can omit the > marker on subsequent lines if the next non-whitespace character is 'paragraph continuation text'. However, you cannot omit the marker if the line would otherwise start a new block type (like a list or a code block).
    • Consecutiveness: Two block quotes appearing in a row without a blank line between them are treated as a single block quote.
    • Separation: A blank line is required to separate two distinct block quotes or to separate a block quote from a following paragraph if laziness is being used.
    • Nesting: For nested block quotes, any number of initial > characters may be omitted on a continuation line due to the laziness rule.
    • Indented Code in Block Quotes: To include an indented code block inside a block quote, you must account for the block quote marker. If the marker is > (two characters), you need at least five spaces of indentation after the > to trigger a code block.
    > # Foo
    > bar
    > baz
    > # Foo
    >bar
    > baz
       > # Foo
       > bar
     > baz
    > # Foo
    > bar
    baz
    >     code
    
    >    not code
  5. Security warning: evalUnserializableExpressions

    main

    The evalUnserializableExpressions option uses eval() to attempt to evaluate expressions in JSX props that cannot be serialized as JSON (like functions or variables).

    ⚠️ WARNING: This is extremely dangerous if used with untrusted or user-generated content.

    Recommended approach: Instead of enabling this, use the renderRule option to selectively handle specific nodes and expressions safely. This allows you to use a lookup table of trusted handlers or a controlled eval with an allowlist.

    // Instead of eval'ing arbitrary expressions, handle them selectively in renderRule:
    const handlers = {
      handleClick: () => console.log('clicked'),
      handleSubmit: () => console.log('submitted'),
    }
    
    compiler(markdown, {
      renderRule(next, node) {
        if (
          node.type === RuleType.htmlBlock &&
          typeof node.attrs?.onClick === 'string'
        ) {
          // Option 1: Named handler lookup (safest)
          const handler = handlers[node.attrs.onClick]
          if (handler) {
            return <button onClick={handler}>{/* ... */}</button>
          }
    
    // Option 2: Selective eval with allowlist (still risky)
          if (
            node.tag === 'TrustedComponent' &&
            node.attrs.onClick.startsWith('() =>')
          ) {
            try {
              const fn = eval(`(${node.attrs.onClick})`)
              return <button onClick={fn}>{/* ... */}</button>
            } catch (e) {
              // Handle error
            }
          }
        }
        return next()
      },
    })
  6. Full Reference Links

    main

    A full reference link consists of a [link text] immediately followed by a [link label] that matches a [link reference definition] elsewhere in the document.

    Syntax and Matching:

    • Syntax: [link text][link label]
    • Matching: Labels are matched using a normalized form: strip brackets, perform Unicode case folding, strip leading/trailing whitespace, and collapse consecutive internal whitespace into a single space. Matching is case-insensitive.
    • Precedence: If multiple matching reference definitions exist, the first one in the document is used.
    • Constraints:
      • No whitespace is allowed between the [link text] and the [link label].
      • Link labels cannot contain unescaped square brackets.
      • Link labels must contain at least one non-whitespace character.
    • Link Text: The contents of the link text are parsed as inlines.
    [foo][bar]
    
    [bar]: /url "title"
    // Result: <p><a href="/url" title="title">foo</a></p>
  7. Optimize Compiler Passes and VM Design

    main

    Advanced optimization strategies for compilers and interpreters:

    • Iterative Optimization: Run constant folding, propagation, and Dead Code Elimination (DCE) iteratively, as they create opportunities for one another.
    • SSA (Static Single Assignment) Form: Use SSA to enable optimizations like Common Subexpression Elimination (CSE) and constant propagation.
    • Modular Pass Architecture: Separate analysis passes (which compute data) from transform passes (which mutate the AST).
    • Deferred Error Analysis: Keep the hot parsing path clean by delegating complex error handling to a separate semantic analyzer.
    • NaN Boxing: Pack value types into 64-bit IEEE 754 NaN payloads to reduce memory usage and improve speed in interpreters.
    • Register-based Bytecode: Prefer register-based bytecode over stack-based bytecode to reduce instruction count and memory traffic.
  8. How lists interact with paragraphs

    main

    In GFM (following CommonMark principles), a list can interrupt a paragraph. This means you do not need a blank line to separate a paragraph from a following list.

    Example:

    Foo
    - bar
    - baz

    This is permitted to support natural writing patterns and to maintain the principle of uniformity, where text maintains its meaning even when placed inside container blocks like lists.

  9. How lazy continuation lines work

    main
    Lazy continuation lines allow for the deletion of some or all indentation from lines that contain paragraph continuation text. If a line is part of a list item's content but is unindented, it is treated as a 'lazy continuation line' and the parser will re-attach it to the preceding list item content.
  10. Fenced code blocks in GFM

    main

    A fenced code block is created using a sequence of at least three consecutive backticks (`) or tildes (~). Backticks and tildes cannot be mixed. The block begins with a fence indented by no more than three spaces.

    Key Rules:

    • Info String: The line containing the opening fence can include an optional info string. The first word is typically used as the language identifier (e.g., ruby), which is often rendered as a class="language-ruby" on the <code> tag.
    • Closing Fence: Must use the same character as the opening fence and must be at least as long as the opening fence. It can be indented up to three spaces.
    • Indentation: If the opening fence is indented $N$ spaces, up to $N$ spaces of indentation are removed from each line of the content.
    • Unclosed Blocks: If no closing fence is found, the block continues until the end of the document or the end of the containing container (like a list item or blockquote).
    • Paragraphs: Fenced code blocks can interrupt paragraphs and do not require blank lines before or after them.
    def foo(x)
      return 3
    end