-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Syntax highlighting fails in React applications when using the new highlightAll() method #3020
Comments
Yep, that's why it was deprecated (not removed)... because the world is a big place and surely there were edge cases. Thanks for the detailed bug report.
The issue is the server-side rendering I think. In a browser That's exactly what Next.js is supposed to be abstracting away for us, right? |
@jammykam Could you answer the first 5 questions of the Next.js GitHub issue report for me (which versions of everything you're using) and I'll open an issue and link to this? I'd like to understand why this can't be resolved on their end (to better simulate the browser environment). We may end up solving this as only a docfix and refer you to: https://github.com/highlightjs/highlight.js#custom-scenarios Frameworks that subvert the traditional browser loading process may simply require manual startup. The whole point of that PR was to simplify the browser usage for most - avoiding two functions with confusing names. |
Could you confirm if you are doing SSR (server side rendering) or not? |
If you aren't doing SSR could you try the following patch to our bootstrap around line 788: const LOADED_STATES = ["interactive", "complete"];
if (typeof window !== 'undefined' && window.addEventListener) {
window.addEventListener('DOMContentLoaded', boot, false);
if (LOADED_STATES.includes(document.readyState)) { domLoaded = true; }
} This should handle the case of someone adding us to the mix FAR after the page has been initialized/loaded. |
Hey @joshgoebel, thanks for the quick reply! 🙌
In this case we are running SSG (static site generation, sorry should have mentioned that), so it should be running as a regular SPA React app. There's an added complexity in our case since the content is dynamic and pulled from a CMS. In any case, I belive this to be an issue in pure React.
This not not work unfortunately 🙁 I still think that this is an eventing issue that does not work for React or SPA applications. AFAIK the https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event
For React applications this is the first page load. From there on when a user navigates to another page, the page partially reloads through the shadow update of DOM elements. Since the page is not actually reloaded, the If i comment out Alas I have however been able to get this work correctly and as expected by simply updating my call to do what the Perhaps updating the documentation with this info will be sufficient?
I can create a simple example to demonstrate this of you if you still want though (I'm a NextJS newbie and a React novice). |
Yes, I realized afterwards that this could be the issue rather than SSR, hence my follow-up post.
Could you please try the fix I posted instead? #3020 (comment) Oh you did... hmmmm... that's strange. |
A tiny Github repo that I could build that reproduces just the React/SSG portion of the issue would be great. I wonder what |
Ping. |
I am using highlight.js with Next.js 11 for a blog. It works perfectly fine for me, the only trouble I was having is regarding JSX/TSX code. That for I doubt that this is a general issue. Can you share the page where you're trying to use highlicht.js (the entire file with the imports etc. would be the most helpful) |
After checking the code you shared when opening the issue: Where are you importing a theme to style the code blocks with? |
I'm very glad, but there are a few different things that could be going on here - and potentially 3 ways you could use HLJS with a complex client-side framework/SPA:
Which of the 3 are you doing? We need to make sure there are clean paths for all 3. |
The goal here is to avoid two APIs [like we have now] (renamed for clarity):
It seems we should be able to deal with tracking page state and abstract this concern away from the implementor. But it get complex when there is no browser (ie, server-side), or when the page might have actually been loaded perhaps minutes or hours ago... I still think we can handle all these cases, but we may need some small tweaks. |
One other way would be to just give up this abstraction and update our docs to tell users to hook the event themselves (for native use, or use their framework hooks), ie for raw JS: window.addEventListener('DOMContentLoaded', () => hljs.highlightAll(), false); But I still think the abstraction is nice to have if we can find a way to make it "just work" in all cases. |
So I have the page /posts/[slug], I import the monokai theme css in the page-component via a es6 import (importing a css-file is handled by next.js). |
When doing static generation is the 'DOMContentLoaded' event fired in the pretend DOM environment? Do you know? |
Seeing as this issue is based on a Next.js site, it only makes sense to run highlightAll inside the page component lifecycyle method. Thus it should always be executed on the client. So I fail to see how SSG would make a difference. |
I don't know. But as said the highlight method would be fired in the component lifecycle not based on a browser event. |
Why - does something in Next.js prevent it from being rendered statically so that the initial load of the page can be given to the browser as static HTML? And also users often do things that make no sense. :-) [Not saying that that's the case here]
We have initialization/startup code that depends on browser events. So knowing the status of the DOM is critical for us to know the correct behavior when It's far too easy (in many environments) to call it too soon, in which case it would fail. |
I will check tomorrow if the said event is fired. In regards to it being executed on the server: |
I tested myself with a vanilla JS (but with our library lazy loaded) and indeed
Ah, then if What if someone wanted to render say a page of documentation with the highlighting all done on the server-side via SSG. How might they attempt such a feat? Is that type of thing frowned upon for some reason or unusual to see done? Is there a nicer way or doing that other than hooking say a |
I fail to see a reason why this should not be executed in the client when working with Next.js. |
I'll take your word for it... I was just assuming if someone COULD do it while we're solving this I was curious what that might look like and see if we could solve that as well. I wasn't trying to suggest "best practice" or any such thing for Next.js. If my guess is correct about I'm a big fan of server-side rendering (from the Ruby on Rails world). :) For me personally I'd prefer to ship simple HTML pages as "cooked" as possible to the client - for the fastest load times. You may be right though in that perhaps SSG with React/Next.js may not be the best tool for accomplishing such things. |
I didn't mean to say that you implied anything :) I see your point as well, but as you said Next just isn't really made for that. Perhaps it would be possible to write a component that automatically highlight it's children, but I really just can't see a need for it in a React page. (Thx for the conversation, it's nice to have fast replies 😄) |
Thanks for trying to help! Knowledgeable people on other ecosystems is always good to have. |
Sorry, i have been snowed under wth client work and will try and get something to you soon, I didn't mean to open an issue and then run away. Looking at that commit now it seems like it should have worked, and entirely possible i messed something up when trying it. |
That's what I'm wondering. I feel like @trm217 kind of rules out the SSG being the issue. So if it's not that or this then I'm left scratching my head a bit. |
Actually, I managed to reproduce the issue in my project. I am unsure why it worked before because now that I have further looked into it, this should definitely not have worked. Perhaps I was using an older version. 🤔 Anyways, I checked the code of @jammykam as well as his point on the boot function. He actually is right, if highlight JS is loaded on a component basis, the DOMContentLoaded event wouldn't fire. A simple workaround would be to enhance the highlightAll function, by also checking if This is my proposed change to the highlightAll function /**
* auto-highlights all pre>code elements on the page
*/
function highlightAll() {
console.log
// if we are called too early in the loading process
// CHANGE - don't execute the highlighting if either the domLoaded event hasn't been played or doc
if (!domLoaded && document.readyState === "loading") { wantsHighlight = true; return; }
const blocks = document.querySelectorAll('pre code');
blocks.forEach(highlightBlock);
} However, I am not certain if this will break the current expected behavior in anyway. I suppose not, since it wouldn't make a lot of sense for domLoaded to be true while document.readyState would still be loading. Here a dumbed-down version of my PostPage to showcase how I use HighlightJS import { FC, useEffect } from "react";
import ReactMarkdown from "react-markdown";
import HighlightJS from "highlight.js";
import "highlight.js/styles/monokai.css";
import Layout from "../../components/templates/Layout";
import { Post } from "../../utility/BlogPostUtility";
import SEO from "../../components/utilities/SEO";
interface PostPageProps {
post: Post;
}
const PostPage: FC<PostPageProps> = ({
post: { content, description, title },
}) => {
useEffect(() => {
HighlightJS.highlightAll();
});
return (
<Layout>
<SEO title={title} description={description} />
<article className="prose lg:prose-xl">
<h1>{title}</h1>
<div>
<ReactMarkdown escapeHtml source={content} />
</div>
</article>
</Layout>
);
}; |
Doesn't #3039 already resolve this? I see only two cases, both now handled.
Your solution may indeed be simpler though, I'll give that some thought. :) But can you confirm 3039 covers both cases already?
Every browser we care about - all modern browsers. |
I don't think it would. The problem is it's not guaranteed that the DOMContentLoaded event is fired when HighLight.js is being loaded. So defacto domLoaded would always be Taking the code from #3039, I would further adapt it to this, to make sure it works as expected even if the DOMContentLoaded event has been fired before Highlight.js was loaded. I would adapt the code to remove the domLoaded variable altogether and only check the document readystate in the highlight all function. /**
* auto-highlights all pre>code elements on the page
*/
function highlightAll() {
// if we are called too early in the loading process
if (document.readyState === "loading") { wantsHighlight = true; return; }
const blocks = document.querySelectorAll('pre code');
blocks.forEach(highlightElement);
}
function boot() {
// if a highlight was requested before DOM was loaded, do now
if (wantsHighlight) highlightAll();
}
// make sure we are in the browser environment
if (typeof window !== 'undefined' && window.addEventListener) {
window.addEventListener('DOMContentLoaded', boot, false);
} I think this would work as expected and also simplify the changed code from #3039. Would it be possible for me to propose my changes on the PR? |
Did you try swapping out the useEffect for the useLayoutEffect hook? It causes the side effect to happen synchronously after DOM manipulation has finished. This is has fixed some issues for me in this situation. Its analygous to firing in the class-component componentDidMount and componentDidUpdate life cycles. https://reactjs.org/docs/hooks-reference.html#uselayouteffect |
Describe the issue
I am trying to use highlightjs on a React project, which uses Next.js (although I don't think the issue is related). I have added highlight.js using npm (v106.0) and loaded in a style sheet for a theme.
I have a simple function component set up like this:
This is using the new
highlightAll()
method call, but when the component is loaded the highlighting fails to take effect, and the<pre><code>
block is left unstyled.Expected behavior
I would expect the code block to be styled and the language highlighted using highlightjs and the stylesheet I have selected.
Which language seems to have the issue?
The error is not language specific, but all code blocks are failing to get instantiated and highlighted, and any styling applied what so ever.
What I think the issue is
I think the issue is that the new
highlightAll()
is not compatible with React and the way that React shadow loads components. Line #772 makes a check if the dom has loaded, else it breaks out:The only place that
domLoaded
is set to true is in theboot()
method:The problem is that the only place
boot()
is called from is an event listener:Since React will not cause the fire of the
DomContentLoaded
event when a user navigates to a new page,boot()
is never called to set this variable to true, and when I am callinghighlightAll()
from my code it's is exiting too early. Theboot()
is also not exposed to be able to call directly from my code.Workaround
For now, the solution is to call
hljs.initHighlighting()
(Line 747) since that method does not make the domLoaded check.But, this method is marked as deprecated, so a fix needs to be put in place to allow this highlighter to continue to work with React applications. Perhaps allow a
true
parameter to be passed to force it to run the syntax highter code.The text was updated successfully, but these errors were encountered: