Skip to content

Documentation: recommended method of adding Marked extensions may cause recursion-related error #3701

Closed
@MaddyGuthridge

Description

@MaddyGuthridge

Marked version:
[email protected]

Describe the bug

When I use the recommended method of adding extensions to Marked, the code eventually gets a recursion-related error.

This happens due to a Markdown wrapper I built in Svelte. While I add the extensions in the global scope of my Markdown.svelte file, this scope is rerun whenever the page is re-rendered on my server. Once the page is reloaded enough times, the number of extensions grows so large that Node gives an error.

minifolio-1  | RangeError: Maximum call stack size exceeded
minifolio-1  | Please report this to https://github.com/markedjs/marked.
minifolio-1  |     at _Hooks.preprocess (file:///home/node/app/node_modules/marked-gfm-heading-id/src/index.js:27:17)
minifolio-1  |     at hooks.<computed> (file:///home/node/app/node_modules/marked/lib/marked.esm.js:2394:51)
minifolio-1  |     at hooks.<computed> (file:///home/node/app/node_modules/marked/lib/marked.esm.js:2395:45)
minifolio-1  |     at hooks.<computed> (file:///home/node/app/node_modules/marked/lib/marked.esm.js:2395:45)
minifolio-1  |     at hooks.<computed> (file:///home/node/app/node_modules/marked/lib/marked.esm.js:2395:45)
minifolio-1  |     at hooks.<computed> (file:///home/node/app/node_modules/marked/lib/marked.esm.js:2395:45)
minifolio-1  |     at hooks.<computed> (file:///home/node/app/node_modules/marked/lib/marked.esm.js:2395:45)
minifolio-1  |     at hooks.<computed> (file:///home/node/app/node_modules/marked/lib/marked.esm.js:2395:45)
minifolio-1  |     at hooks.<computed> (file:///home/node/app/node_modules/marked/lib/marked.esm.js:2395:45)
minifolio-1  |     at hooks.<computed> (file:///home/node/app/node_modules/marked/lib/marked.esm.js:2395:45)
minifolio-1  | 2025-05-28T09:06:45.281Z GET /: 500 (54 ms)

To Reproduce

In my Svelte component, I have the following code:

<script lang="ts">
  /** Markdown.svelte */
  import { marked } from 'marked';
  import { gfmHeadingId } from 'marked-gfm-heading-id';

  type Props = {
    source: string;
    article?: boolean;
  };

  const { source, article = false }: Props = $props();

  // I am intentionally creating a huge list of extensions here to make the issue 
  // happen faster. If I don't do this, it takes days of requests before the issue occurs
  let exts = [gfmHeadingId(), gfmHeadingId(), gfmHeadingId(), gfmHeadingId()];
  // 20 extensions
  exts = [...exts, ...exts, ...exts, ...exts, ...exts];
  // 100 extensions
  exts = [...exts, ...exts, ...exts, ...exts, ...exts];
  // 500 extensions
  exts = [...exts, ...exts, ...exts, ...exts, ...exts];
  // 2500 extensions
  exts = [...exts, ...exts, ...exts, ...exts, ...exts];
  // 10000 extensions
  exts = [...exts, ...exts, ...exts, ...exts];
  marked.use(...exts);
</script>

<div>
  <!--
    We only render markdown specifically from the `data/` directory, so
    this is safe.
  -->
  <!-- eslint-disable-next-line svelte/no-at-html-tags -->
  {@html rendered}
</div>

Once you add this to a simple Svelte app, simply request a page containing some a few times Markdown, and you'll quickly get an error. For me, it happens on the 5th request. I'm using this simple Python script to do the requests.

# /// script
# dependencies = [
#   "requests<3",
# ]
# ///
import requests

i = 1

while True:
    res = requests.get('http://localhost:5096')
    print(f"{i:6} | {res.status_code}")
    if res.status_code == 500:
        break
    i += 1

Expected behavior

Marked's documentation should inform users of this risk, and encourage them to create a Marked object if their web framework reruns the code every time the component is used.

const marked = new Marked(...exts);

// Then rendering requires you to add
const rendered = marked.parse(inputString);
//                     ^^^^^^

Alternatively, these extensions can be added in a wrapper module, so that the code is only executed once.

import { marked } from 'marked';
import { gfmHeadingId } from 'marked-gfm-heading-id';

marked.use(gfmHeadingId());

export default marked;

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions