code is not displayed correctly
Dr-kyle opened this issue · comments
my code is:
// 18.2.0
import { useEffect } from "react"
// 11.8.0
import hljs from 'highlight.js'
// 5.0.5
import { marked } from "marked";
// 1.0.2
import { markedHighlight } from "marked-highlight";
// 1.0.1
import { mangle } from "marked-mangle";
export default function Index() {
useEffect(() => {
marked.use({ headerIds: false,}, mangle(), markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
}));
let res = marked.parse('# Marked in the browser\n\nRendered by **marked**.\n```java\nInteger a = 3;\n```\n');
console.log(res)
document.getElementById('test-m').innerHTML = res;
}, [])
return (
<>
<div id="test-m"></div>
</>
)
}
Can you help me point out where I am wrong?
Seems like it is something with react. Removing react seems to make it work.
The same problem exists with Svelte. The deprecated way still works perfectly fine:
marked.setOptions({
highlight: function(code, language) {
const validLanguage = hljs.getLanguage(language) ? language : 'plaintext';
return hljs.highlight(code, { language: validLanguage }).value;
}
});
Content:
```elixir
IO.puts("Hello, world!")
```
Generates:
<pre><code class="language-elixir"><span class="hljs-title class_">IO</span>.puts(<span class="hljs-string">"Hello, world!"</span>)
</code></pre>
The new way:
marked.use(markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
}));
Content:
```elixir
IO.puts("Hello, world!")
```
Generates:
<pre><code class="hljs language-elixir"><span class=<span class="hljs-string">"hljs-title class_"</span>><span class="hljs-title class_">IO</span></span>.puts <span class=<span class="hljs-string">"hljs-string"</span>>&quot;<span class="hljs-title class_">Hello</span>, world!&quot;</span>
</code></pre>
Can you create a repo that reproduces this?
So, it's a weird thing. While trying to reproduce it in a new repo, I realized where it's happening, and it appears to be unrelated to the new way or the legacy way.
The code is here: https://github.com/icebaker/myapp
Here's what's going on: If you update your code while your server is in development mode, the page automatically updates to reflect your changes. But for some reason, this seems to mess up the code. A simple page refresh brings things back to normal. I've captured this in a video:
behavior.mp4
Note that, for some reason, this only happens with marked (legacy or new way). If I'm using highlight.js purely, it does not mess up the rendering.
This looks like an issue in svelte. marked returns the correct html so there is nothing we can change in marked or marked-highlight to fix this.
Alright, digging deeper into debugging, I've discerned the root cause. Indeed, it's a problem linked to the new technique, yet it doesn't pertain to marked-highlight
. Instead, the issue stems from the shift from setOptions
to use
.
With setOptions
, we have the capability to invoke it multiple times, and it will invariably replace the existing option.
On the other hand, with use
, if it's invoked a second time, it will append the extension twice. If invoked yet again, it's added a third time, and so forth.
That's the reason it "incorrectly" renders the code—it applies a highlight onto an already highlighted portion.
When dealing with setOptions
:
marked.setOptions({highlight: ...});
marked.parse(code);
// IO.puts("Hello, world!")
marked.setOptions({highlight: ...});
marked.parse(code);
// IO.puts("Hello, world!")
marked.setOptions({highlight: ...});
marked.parse(code);
// IO.puts("Hello, world!")
When dealing with marked.use
:
marked.use(markedHighlight({...}));
marked.parse(code);
// IO.puts("Hello, world!")
marked.use(markedHighlight({...}));
marked.parse(code);
// <span class="hljs-title class_">IO</span>.puts(<span class="hljs-string">"Hello, world!"</span>)
marked.use(markedHighlight({...}));
marked.parse(code);
// <span class=<span class="hljs-string">"hljs-title class_"</span>><span class="hljs-title class_">IO</span></span>.puts(<span class=<span class="hljs-string">"hljs-string"</span>>&quot;<span class="hljs-title class_">Hello</span>, world!&quot;</span>)
The easiest way to simulate this behavior:
import hljs from 'highlight.js';
import * as marked from 'marked';
import { markedHighlight } from 'marked-highlight';
const extension = markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
});
marked.use(extension);
marked.use(extension);
marked.use(extension);
marked.parse(code);
I believe that most modern reactive frameworks like React and Svelte reported here, have this tendency of calling things multiple times. Just using the same component twice on the same page, or hopping from one page to another in a Single Page Application, could trigger this accumulation of extensions because of the use
behavior.
So, my suggestion would be to tweak this approach to make it more compatible with modern frameworks. I think we're likely to see more of this type of issue in the future.
Here are some potential approaches that come to mind:
-
We could mention explicitly in the documentation that invoking
use
twice will register your extension twice, leading to its invocation twice over the same content recursively. However, I don't see this as a viable long-term solution. It might prompt people to devise various workarounds to manage this in reactive applications, such as developing a kind of singleton or global control to ensureuse
is registered only once. -
We could make marked instantiable. Currently, marked is global, meaning if two areas import it and set different extensions for the same task, but with varying configurations, both will be invoked, creating conflicting outputs. I think this could be a solid long-term approach. It provides isolation and flexibility for utilizing different instances of marked in the same application, configured differently for distinct purposes. Implementing this, while ensuring backward compatibility, may be a challenge.
-
We could provide a mechanism for users to easily identify if an extension is already registered (globally). Could we offer identifiers to extensions to prevent them from being added more than once? For example:
marked.use('extension-a', highlightExtension);
marked.use('extension-b', highlightExtension);
Or:
marked.use({...highlightExtension, ...{id: 'extension-a'}});
marked.use({...highlightExtension, ...{id: 'extension-b'}});
Thoughts?
Thanks for sharing; this is great context!
So, for those struggling to get this working with reactive frameworks, the simple workaround is just to encapsulate marked into a singleton, and you're good to go:
import * as marked from 'marked';
import { markedHighlight } from 'marked-highlight';
import hljs from 'highlight.js';
const Marked = {
instantiated: false,
setup: () => {
marked.setOptions({ mangle: false, headerIds: false });
marked.use(
markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
})
);
Marked.instantiated = true;
},
instance: () => {
if (!Marked.instantiated) Marked.setup();
return marked;
}
};
export default Marked;
And then:
import Marked from 'Marked';
const html = Marked.instance().parse(code);
Here's the "fix" in the demo repo: commit d59f1a6
Ya or use marked.setOptions(marked.getDefaults())
to reset the options at the beginning.
yah, first render is correct, but rerender is not because marked-highlight decodes the part of the code that has already been decrypted. LoL
my crazy solution. This not working if markdown has class 'hljs'
marked.use(
markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
if(code.includes('class="hljs') return // 🤣🤣🤣 that crazy
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
})
);
This should be fixed with markedjs/marked#2831
Should be fixed in marked v5.1.0