Open jakedee opened 3 years ago
Hmm - this is sortof a fluke in both libraries TBH. React wipes out Prism's injected HTML because they use .textContent
to assign the text of Elements with a single Text child. We use a different technique that is faster because it doesn't wipe content.
I believe the fix here is to set dangerouslySetInnerHTML={{ }}
(empty) on the <code>
element. Or, wrap it in a Component and add shouldComponentUpdate(){return false}
to prevent diffing (since it's just thrown away when highlightAll() is called anyway):
globalThis.Prism = globalThis.Prism || {};
globalThis.Prism.manual = true;
import Prism from 'prism';
import { useEffect, useRef } from 'preact';
const SKIP_DIFFING = typeof document !== 'undefined' ? {} : undefined;
function Prism({ code, language }) {
const ref = useRef();
useEffect(() => {
ref.current.textContent = code;
Prism.highlightElement(ref.current);
}, [code, language]);
return (
<pre className={'language-'+language'}>
<code ref={ref} className={'language-'+language'} dangerouslySetInnerHTML={SKIP_DIFFING}>
{code}
</code>
</pre>
);
}
// usage:
<Prism code={code} language="js" />
There's also a much nicer way to use Prism with (p)react, if you're interested - they provide a string-to-string API that avoids the DOM stuff entirely. Let me know if you want an example.
Hi Jason, the string-to-string API sounds interesting, an example would be great!
In the meantime, I'll try implementing the changes you've suggested. Thanks!
Use the low level highlight
function from the Prism API to convert your code to highlighted code:
const highlightedCode = Prism.highlight(
code,
Prism.languages.javascript,
"javascript"
);
Then display the highlighted code inside your component. The variable highlightedCode
now contains the necessary html tags to do the highlighting, and for security reasons Preact automatically escapes html tags inside variables. So you'll need to explicitly opt out of this security mechanism.
Use dangerouslySetInnerHTML
to have the content of the variable highlightedCode
not escaped.
Do so only if the content comes from a trusted source!
return (
<>
<pre className="language-js">
<code
className="language-js"
dangerouslySetInnerHTML={{ __html: highlightedCode }}
></code>
</pre>
<button onClick={handleClick}>Trigger re-render</button>
<div>Re-renders: {rerenders}</div>
</>
);
Since the code is already hightlighted, you can now get rid of the useEffect call. Adapted version of your original code, using Prism's string-to-string API can be found here: https://codesandbox.io/s/preact-prism-forked-h8lm1?file=/pages/index.js
Code highlighted by PrismJS is duplicated after the first re-render
Steps to reproduce the behaviour:
Expected behaviour Codesandbox - React