perf: cache Intl formatters - #1957
Conversation
Signed-off-by: Ruslan Sayfutdinov <ruslan@sayfutdinov.com>
Bundle size report (gzip)
119 chunks compared, 26 changed. Sizes are gzip, matching what nginx serves. |
There was a problem hiding this comment.
🟡 Changes recommended
The formatDateTime locale comparison is reference-based (making the single-formatter path effectively unreachable for array locales) and the cache key is order-sensitive, both undermining the intended performance gains.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Improves date/time formatting hot paths in Fluidd’s UI by reusing Intl.DateTimeFormat instances instead of rebuilding them per render/cell, reducing main-thread stalls in date-heavy tables.
Changes:
- Add a keyed cache for
Intl.DateTimeFormatinstances and routeformatDate/formatTime/formatDateTimethrough the cached formatter. - Preserve prior behavior for invalid dates by falling back to
Date.toLocaleString(...)whenIntl.DateTimeFormat.format(...)would throw. - Add unit tests covering caching behavior and invalid-date formatting.
File summaries
| File | Description |
|---|---|
| src/util/date-time-formatters.ts | Introduces cached Intl.DateTimeFormat creation + shared formatting helper, used by date/time formatter APIs. |
| src/util/tests/date-time-formatters.spec.ts | Adds tests validating formatter reuse, distinct formats, and invalid date rendering. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Hi @KapJI, thank you for submitting this PR. I admit I’m a bit torn on this one; while I can see the impact, it doesn’t seem to be particularly significant according to your own metrics. |
|
I think the improvement is significant. Here is the fuller picture, measured on the same printer (507 gcode files, 328 printed, 265 jobs back-filled) with the CPU profiler over 30 s from a cold dashboard load, cache and service worker cleared:
On the released build, rebuilding 284 ms to 130 ms is what is left after #1956 removes the render storm, and it is the longest single main-thread stall (measured by timer drift), not CPU time. |
Moves the Intl formatter cache into a shared intl-format-cache module and reuses it for Intl.RelativeTimeFormat and the currency Intl.NumberFormat. formatDateTime always joins date and time, and secondsAsRange drops redundant checks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
|
By the way, this PR probably needs a label and a milestone assigned. |

Found while investigating #1956, but independent of it.
formatDate,formatTimeandformatDateTimepass a fresh locales array and options object toDate.toLocaleDateStringandDate.toLocaleTimeString, so the browser builds a newIntl.DateTimeFormaton every call. Data tables format a date per cell on every render, which made those two functions 57% of a dashboard CPU profile on my printer.Intl formatters are now reused through a small shared cache (
src/util/intl-format-cache.ts), keyed by locales and options, so it holds one entry per distinct format:Intl.DateTimeFormat:formatDate,formatTime,formatDateTimeIntl.RelativeTimeFormat:formatRelativeTime(e.g. per row in the Spoolman spool selection dialog and per notification)Intl.NumberFormat:getReadableCurrencyString(the price column of the spool selection dialog)Invalid dates still render as
Invalid Date:Intl.DateTimeFormat.formatthrows on them, so they're handled explicitly. An unknown or missing currency still falls back to the plain value.formatDateTimenow always joins the date and time output. The previous combined path was only reachable when both formats were ISO, and its output was identical.Per-call cost (Node microbenchmark):
Measured on a dashboard with 507 file rows, with #1956 applied: the worst main-thread stall after a history load drops from 284 ms to 130 ms. The gain applies to any view that renders many dates.