⚡ Lazy-load heavy components on FE, on-demand locales, and per-font stylesheets - #1193
Conversation
|
I made this a draft, as I think, something stinks here. I'll take second/third look at this. May be nothing. Just a gut feeling. |
|
So, what tripped me up the maths vs my older precompress PR, so did some digging:
So, yeah this is more like 12% percent improvement not 50%. Still, good? I think? I guess compression does not scale as the non-compressed file goes, so lot of resource that were cut off are exactly the stuff that were well compressed, hence why does does not bigger gains. So, in other words: lazy loading them shrinks the raw file dramatically, but their contribution to the compressed size was already minimal. GZIP "hid" the cost of those resources before, so deffering them doesn't move the needle as much post-compression. Probably locales files, i think, that contained huge amount formatting/whitespaces etc, so those are very well compressed. (best guess) Apologies for the confusion. |
That seems like a very hefty thing to be pulling in. There should be a more lightweight option, i think. (I am defo not suggesting dropping it, rather finding more suitable way to get the fonts.) |
aaronleopold
left a comment
There was a problem hiding this comment.
Thanks for working through this! I had some comments throughout.
So, yeah this is more like 12% percent improvement not 50%. Still, good? I think?
Yeah I think 12% is a good improvement, but based on what I saw I am worried that the improvement might be more artificial than practical (e.g., waterfall navigation from lazy loading routers).
| const localeLoaders = import.meta.glob<{ default: Translation }>([ | ||
| './locales/*.json', | ||
| '!./locales/en-US.json', | ||
| ]) |
There was a problem hiding this comment.
This will likely break expo. import.meta.glob is a vite API, right? Not all apps in the repo are using vite, so if we want to lazily load things we will need another mechanism for doing so
There was a problem hiding this comment.
I'll double check, I am afraid I did not think to test with expo. So whether this is breaking for it, i would need check.
Apologies.
| resources, | ||
| }) | ||
|
|
||
| export const resolveLocale = (inputLocale?: string): AllowedLocale => { |
There was a problem hiding this comment.
I'm a little conflicted about this function, at least for some of it. I think it might be overly defensive, really. I'm of the mentality that breaking fast will be easier to catch, e.g. if someone's navigator returns a locale which we don't support I'd rather bake in support for it than not realize it isn't working as expected.
There was a problem hiding this comment.
That may be, i think this is fine? I think the fallback to en-US is fine, most of (if any) problems are logged well enough, at the end of the day, I do not think this should cause any harm.
On the client, I think we would want to avoid throwing/failing on principle.
For the pt, i think you are right, i'll push commit when I am at PC again.
There was a problem hiding this comment.
Same remarks here as with the markdown preview
There was a problem hiding this comment.
I don't disagree with manual chunking in general, but I think maybe things might be a bit higher fidelity if we compare more complete paths instead of just substrings. E.g.
rollupOptions: {
output: {
manualChunks(id) {
const path = id.replaceAll('\\', '/')
if (!path.includes('/node_modules/')) return
if (path.includes('/node_modules/lucide-react/')) return 'vendor-lucide'
if (path.includes('/node_modules/@tanstack/')) return 'vendor-tanstack'
// ... etc ...
if (
path.includes('/node_modules/react/') ||
path.includes('/node_modules/react-dom/') ||
path.includes('/node_modules/scheduler/')
) {
return 'vendor-react'
}
},
},
}
I redid the benchmark now its 626.23 kB so -22.3%, I think some of more commits after I made my initial comment improved it significantly. In practice, I feel like this should be an improvement, BUT a "hard to measure" improvement though. Lot of the chunking improvement will manifest themselves with caching/etc improvement, that is not necessarily easy to get a good measurement on. Anyways, last 8 commit, I think addressed everything. |
aaronleopold
left a comment
There was a problem hiding this comment.
I think only one of my comments went missed, but otherwise I think this looks good 🙂
| let active = true | ||
| async function prepare() { | ||
| try { | ||
| await loadLocaleResources(resolvedLocale) | ||
| if (!active) { | ||
| return | ||
| } | ||
|
|
||
| await Promise.all([i18n.changeLanguage(resolvedLocale), initDateFnsLocale(resolvedLocale)]) | ||
| document.documentElement.lang = resolvedLocale | ||
| } catch (error) { | ||
| console.error('Failed to load locale resources', error) | ||
| } | ||
| } | ||
| prepare() | ||
| return () => { | ||
| active = false | ||
| } |
There was a problem hiding this comment.
I think this bit still needs to be addressed, and also will change a bit with updates from nightly which fixed things for expo
|
Hey @balazs-szucs, coming back to this and seeing it has gone a bit stale. Is this an effort you would like to revive? Or should I go ahead and close this one out? |
I can fix, no worries. Apologies for not getting to this. Burnout and all. Obviously, no pressure eitherway. |
|
Okey, i think some changes upstream make stuff here bit obselete (e.g., reader changes). Still, i think majority of the stuff here is mostly unaffected, so no big worries here. Most of the conflicts look easy enough to resolve, so yeah all good. |
Never any problems in this regard on my side, life is always first you're good
That's okay too, if it's not worth reviving it for now that's totally okay by me |
Manual vendor chunking, granular path imports, and query extraction for smaller web bundles. Load i18n locale resources and date-fns locales on demand, and lazy-load the emoji picker.
f7844a6 to
d7946d3
Compare
I can quickly re-measure, i think. (Probably tmrw, as it's midnight) But for me this looks good now 👍 I quickly retested/clicked around for a while it does looks good. Locale changes work, EPUB reader works, so no worries there i think. Also 150 LOC leaner so that's also nice. |
|
Thank you! I'll resolve the conflicts and aim to get this merged in sometime during the week |
Codecov Report❌ Patch coverage is
... and 6 files with indirect coverage changes 🚀 New features to boost your workflow:
|
91 upstream commits (v0.1.8 + v0.1.9), 79 conflicts. Upstream fixed the four authorization holes we reported (A30, A32, A33) in `:lock: Fix security audit findings`, with 12 new integration tests. Their versions are taken and ours dropped: theirs also allow MANAGE_USERS holders to administer other users and protect the server owner from being edited by them. A31 (content rules must hide BOOKS) is fork-only code and stays, guarded by `visibility_filters_are_applied`. Upstream's `cache_friendly_url` replaces our versioned thumbnail URLs (A20). Merged by hand where upstream restructured: - core crate reorg (stumpapp#1424): our files moved with it — metadata (A26/A27/A29) to metadata/media.rs, EPUB collections (A28) and cover placeholder (A7) to media/processor + image/, writeback (A5) to metadata/provider; the three job files became job/dispatch.rs, where MetadataWriteback is re-registered. - library scan: our multi-root walk (A1) and series-merge resolution (A2) now also thread upstream's oneshot discovery through every root. - patch_library (new upstream mutation) validates all roots, not just the path. - vite: upstream's chunking (stumpapp#1193) keeps react/router/i18next/date-fns and our i18n package in one chunk — split apart, rolldown builds a cyclic graph that breaks start-up (chunk_cycles.py reports 0 cycles). - i18n: upstream localized several components themselves, so their keys win; en-US is theirs plus 732 fork-only keys, ru-RU re-keyed onto it (96 new Russian strings, 1 upstream-app key lost). - tests: fork tables (content rules, library paths, series merges) are created in the in-memory test DB, without which the new upstream tests 500. 488 tests pass (415 unit + 73 server integration). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cuts initial JS payload by ~50% (4050 kB to 2039 kB gzipped).
The app was eagerly loading a lot of weight that most users never need on boot, e.g., 32 locale JSON files, the full
@emoji-martdataset + renderer, and a markdown parser.Changes are as follows:
MarkdownPreviewand@emoji-mart/reactare code-split viaReact.lazy(); emoji data fetch gated onisOpenimport.meta.glob; onlyen-USis eager, rest load vialoadLocaleResourcesresolveLocalenormalizes raw browser strings (fr,zh_CN) to a validAllowedLocale, replacing a silent bad cast (At least I think so? I was getting weird errors)fonts.cssAs for
ResizeObserveradded guard against post-teardown resize calls, it was giving also some headaches, not quite certain if I caused it somehow but 🤷♂️