Description
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;