Skip to content

codehilite.css not loaded in single_page_app example #4774

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

Open
3 tasks done
platinops opened this issue May 21, 2025 · 8 comments · May be fixed by #4778
Open
3 tasks done

codehilite.css not loaded in single_page_app example #4774

platinops opened this issue May 21, 2025 · 8 comments · May be fixed by #4778
Labels
analysis Status: Requires team/community input bug Type/scope: A problem that needs fixing 🟠 major Priority: Important, but not urgent
Milestone

Comments

@platinops
Copy link
Contributor

platinops commented May 21, 2025

First Check

  • I added a very descriptive title here.
  • This is not a Q&A. I am sure something is wrong with NiceGUI or its documentation.
  • I used the GitHub search to find a similar issue and came up empty.

Example Code

#!/usr/bin/env python3
from router import Router

from nicegui import ui


@ui.page("/")  # normal index page (e.g. the entry point of the app)
@ui.page(
    "/{_:path}"
)  # all other pages will be handled by the router but must be registered to also show the SPA index page
def main():
    router = Router()

    @router.add("/")
    def show_one():
        ui.label("Content One").classes("text-2xl")

    @router.add("/two")
    def show_two():
        ui.label("Content Two").classes("text-2xl")

    @router.add("/three")
    def show_three():
        ui.label("Content Three").classes("text-2xl")
        ui.markdown("""
                    print("hello world")
                    """)

    # adding some navigation buttons to switch between the different pages
    with ui.row():
        ui.button("One", on_click=lambda: router.open(show_one)).classes("w-32")
        ui.button("Two", on_click=lambda: router.open(show_two)).classes("w-32")
        ui.button("Three", on_click=lambda: router.open(show_three)).classes("w-32")

    # this places the content which should be displayed
    router.frame().classes("w-full p-4 bg-gray-100")


ui.run()

Description

Above is a small variation on the single_page_app example that uses the ui.markdown element.

When you load the page "three", the code is not properly highlighted. The JS console shows an error:
The stylesheet http://localhost:8080/_nicegui/2.17.0/codehilite.css was not loaded because its MIME type, "text/html", is not "text/css".

At first sight, this only occurs for /codehilite.css, not for /static/... or /libraries/....

Is this due to the way how/when codehilite.css is mounted?

NiceGUI Version

2.17.0

Python Version

3.11.9

Browser

Firefox

Operating System

Windows

Additional Context

If I add ui.markdown() just after from nicegui import ui, the issue is resolved. I guess that forces the codehilite.css route to be added before {_:path}, thereby getting higher priority.

Can this be fixed in the source, so this workaround is not necessary? E.g. add markdown: bool to ui.run() which is default True.

@falkoschindler
Copy link
Contributor

Thanks for reporting this issue, @platinops!

I just mentioned a related observation a few hours ago: #4539 (review). For whatever reason codehilite.css is served under an unusual route which seems to be handled by the @ui.page('/{_:path}') decorator.

@falkoschindler falkoschindler added bug Type/scope: A problem that needs fixing analysis Status: Requires team/community input 🟠 major Priority: Important, but not urgent labels May 21, 2025
@falkoschindler falkoschindler added this to the 2.x milestone May 21, 2025
@evnchn
Copy link
Collaborator

evnchn commented May 21, 2025

Hmm interesting.

I have to ask: Why was codehilite CSS dynamically generated on-the-fly for every NiceGUI app launch?

It has always been like this since 6d67e9c

I'd imagine we can pre-generate the CSS and then toss it to lib/codehilite/codehilite.css in the distribution, and use the normal self.add_resource method for serving the CSS?

@falkoschindler
Copy link
Contributor

@evnchn I can't remember the exact reasoning behind 6d67e9c, but dynamic generation

  • is cheap,
  • doesn't require an extra utility script, and
  • avoids a potential mismatch of NiceGUI's static codehilite.css and the markdown2 (or pygments) version installed on the user's machine. Imagine updating markdown2 and suddenly the static CSS doesn't match class names used in the generated HTML.

Therefore I would keep generating the CSS dynamically, but serve it via add_resource. Simply writing the file to lib/codehilite/codehilite.css at runtime should work, shouldn't it?

@evnchn
Copy link
Collaborator

evnchn commented May 21, 2025

Simply writing the file to lib/codehilite/codehilite.css at runtime should work, shouldn't it?

I was contemplating that too, but it isn't a perfect solution. Check if you agree:

  1. Simply writing the file to lib/codehilite/codehilite.css (what you mentioned)
    • Pros: Dead simple
    • Cons: May have conflict between server instances (different styling? Simultaneous write collision?)
      • Also self-writing to the NiceGUI install may not be recommended behaviour.
  2. Utility script to generate the CSS once
    • Pros: Drop pygments as a runtime dependency
    • Cons: User cannot customize
  3. Introduce class ResourceFromCallable and register_resource_from_callable, where def _get_resource(key: str, path: str) -> FileResponse: route will call the callable to get the result on every access
    • Pros: Faithful to the original code execution intent
    • Cons: Add complexity to NiceGUI core code
  4. Write the CSS to some random temporary directory
    • Pros: Simple and avoid conflict too
    • Cons: We have to clean it up on server shutdown, which may not occur if the server was takkilled...

@falkoschindler
Copy link
Contributor

I mostly agree. Some more thoughts:

Option 1: Writing to lib/codehilite/codehilite.css at runtime could also be difficult due to missing permissions.
Option 2: I think dropping pygments does not work, because markdown2 uses it internally to generate the HTML. This is how HTML and CSS match: By generating the HTML with the same pygments version like the CSS, both are guaranteed to match. If we decouple HTML and CSS generation, we risk conflicts.
Option 3: I'd like to do it in memory, but it indeed adds some complexity.
Option 4: Is it always possible to create temporary files? Probably yes.

Thinking about codehilite.css more broadly: I just noticed it defines a line height for <pre> tags globally:

ui.add_css('pre { line-height: 5.0; }')

ui.html('''
<pre>
Foo
Bar
</pre>
''')

ui.markdown('Hello')  # Removing this line has impact on the distance between "Foo" and "Bar".

I wonder if we could convert markdown.js into a SFC markdown.vue inlining codehilite.css and scoping its style defitions. But as outlined above, it would be better to generate the CSS at runtime to guarantee compatibility with the HTML code.

@evnchn
Copy link
Collaborator

evnchn commented May 21, 2025

I guess we can agree that we eliinate option 1 and 2, and establish that the HTML is best to be generated dynamically to avoid any mismatch possibilities (since it cheap anyways)

I think ResourceFromCallable is easiest to implement so far. Not sure about the temporary file, since:

  • If we generate 1 single temporary file and use that across all serves, then we would miss runtime customizations to pygments if the user applies any, deviating from the original intent
  • If we generate a separate temporary file per serve, then we need to call some code eventually per serve so as to populate the temporary file, and at that point the ResourceFromCallable makes much more sense

@evnchn
Copy link
Collaborator

evnchn commented May 21, 2025

@falkoschindler https://github.com/evnchn/nicegui/tree/dynamic-resources

Works on my machine. PR coming later. Supermarket 5pm has discount, let me grab that first!

@evnchn
Copy link
Collaborator

evnchn commented May 21, 2025

Actually, this issue boils down to the match-all @ui.page("/{_:path}").

single_page_app example used it, but it itself isn't the root-cause of the bug.

This code breaks on main but works on #4778

@ui.page("/")
@ui.page("/{_:path}")
def main():
    ui.markdown("""```python
print("hello world")
```""")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
analysis Status: Requires team/community input bug Type/scope: A problem that needs fixing 🟠 major Priority: Important, but not urgent
Projects
None yet
3 participants