Skip to content

Conversation

@evnchn
Copy link
Collaborator

@evnchn evnchn commented Nov 23, 2025

Motivation

NiceGUI can benefit from per-element caching to boost page load speeds on the second load afterwards, assuming element definitions remain constant.

There are previous attempts #4796 and #4900, which I will mainly compare the differences at the end.

Implementation

Python-land:

  • Used "named element" approach (see Browser Data Store: Retransmission Avoidance (-40% for docs page alone) #4796 (comment))
  • Element's caching policy defined via default_cache_keys, which is a list of nested keys.
    • May be customized by the user if they know what they are doing.
  • Name and (optionally) caching policy passed to .cache() to enable caching.
  • _to_dict expects a hash from cookie (3), and:
    • Indicate whethere there is a cache hit (1)
    • Add caching metadata, including the caching policy (2)
    • Drop data if cache hit, or leave it in-place if not
  • Cookie is stored per element to avoid making one exceptionally long cookie string
    • Later can more easily implement cache last-use to enable LRU cache eviction in case too many cookie / localStorage entries.

JS-land:

  • If there is no cache hit, we know that the data must have been left in place (1), and so with the caching policy (2), the client can infer how to populate the data into cache, with no meaningful changes to the original element dictionary.
  • If there is a cache hit, we know that the data must have been dropped (1), we simply take the cache and shove it into element, and in edge case (should not occur) the original element's data take precedence.
  • Cache status is actively reflected in the cookies (3)

Progress

  • I chose a meaningful title that completes the sentence: "If applied, this PR will..."
  • The implementation is complete.
  • Pytests have been added (or are not necessary).
  • Documentation has been added (or is not necessary).

Past todo list entries:

Inherited from #4796

  • Cookies have 4KB size limits; could the hash data exceed this? Not in this PR since we use each cookie to store 1 element
  • But we bump into the max cookies count, then. And if we lose the session then things break massively.
    • Old single-cookie method may still have its merits. Not sure.
  • Synchronous localStorage` operations might freeze UI In short - just use it. It takes no time. 0.0007 of 1 millisecond.
  • Could the async hash computation lead to inconsistencies?
    • While yes, worst case is that the cached entry is not used. Sync hash computation is (1) unavailable from browser and (2) even worse of an idea in performance...
  • We need tests to ensure caching works as expected
  • How can we help developers to understand / manage cache size limits

Comparison with past PRs

#4796

Boy is that an old PR. My coding style is much more drastic back then.

  • The API is much more clean in this PR.
  • User would not need any knowledge of NiceGUI inner workings if they stick with the default cache policy (and possibly curated cache policies)
  • A much smaller diff.
  • Accomplishes the basic suggested improvements in Browser Data Store: Retransmission Avoidance (-40% for docs page alone) #4796 (review):
    • Less members per element introduced.
    • No _to_dict_internal and _to_dict division - handled by _to_dict entirely

#4900

It's promising but several issues still:

  • "CACHE_" prefix lacks robustness: Inherent limitation of in-band communication for caching - there will always exist a user data which triggers the detection.
  • The "special treatment for elements which need it" leaks into the element's definition, which requires explicit isinstance checks to restore CachedStr class
    • It makes it impossible to cache an element with logic which does not have a default cache policy yet.
  • Requires maintenance of known_hashes of client, whereas this is done by the cookies in this PR.
  • Caches per item rather than per element, higher overhead.

@evnchn evnchn added feature Type/scope: New or intentionally changed behavior in progress Status: Someone is working on it labels Nov 23, 2025
@evnchn evnchn marked this pull request as draft November 23, 2025 15:17
github-merge-queue bot pushed a commit that referenced this pull request Dec 1, 2025
…ng) (#5497)

### Motivation

While working on #5493 I also noticed that, full cached, NiceGUI
documentation reaches out to:

- `index.html` (obviously)
- `codehilite.css`

The latter we should be able to get rid of? 

### Implementation

- Filename may change so we do `${this.filename}` with the accompanying
props.
- Compute the file name and return the response using
`_generate_codehilite_css()`, which is shared.
- [ ] (Questionable) Apply `@lru_cache(maxsize=1)` to
`_generate_codehilite_css()` because it doesn't ever change in runtime
(an asusmption, is this right?)

### Progress

- [x] I chose a meaningful title that completes the sentence: "If
applied, this PR will..."
- [x] The implementation is complete.
- [x] Pytests have been added (or are not necessary).
- [x] Documentation has been added (or is not necessary).

### Results

Slow 4G no CPU throttling (because network is the bottleneck)

Before: 1.46s load
After: 1.00s load

---------

Co-authored-by: Falko Schindler <[email protected]>
github-merge-queue bot pushed a commit that referenced this pull request Dec 18, 2025
…nflicts (#5495)

### Motivation

**TL-DR: `dependency.py` isn't coded as tightly as it should, silently
letting (possibly critical) errors slide into client-side instead of
stopping them right away and preventing the server from launching in a
verbose and attention-grabbing manner.**

I was working on #5493 and looking for space savings, when I found some
interesting behaviour with regards to our dependency.py implementation.

1. We set key for `imports` at 2 places, libraries and ESM modules


https://github.com/zauberzeug/nicegui/blob/eea9a48071f274657e65409571c255b9b35830e8/nicegui/dependencies.py#L180-L186

If ESM modules are to have the same name as a library, the library would
be shadowed.

2. We append JS `import`s into `js_imports` at 2 places, one for `.js`
components, one for `.vue` components


https://github.com/zauberzeug/nicegui/blob/eea9a48071f274657e65409571c255b9b35830e8/nicegui/dependencies.py#L193-L207

This one is a bit more serious: If you run import twice with same name,
browser JS errors out entirely, and the page grinds to a halt (white
screen)

`Uncaught SyntaxError: Identifier 'BLAH' has already been declared`

### Implementation

- 2 sets: `import_names` and `component_names`
- Do assertations before registration
- Update the set before returning

### Progress

- [x] I chose a meaningful title that completes the sentence: "If
applied, this PR will..."
- [x] The implementation is complete.
- [ ] Pytests have been added (or are not necessary).
- [x] Documentation has been added (or is not necessary).

### Final notes

May break existing code which works (barely) in case 1, but I think we
should break it, because the code isn't valid in the first place.

---------

Co-authored-by: Falko Schindler <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Type/scope: New or intentionally changed behavior in progress Status: Someone is working on it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant