Repository navigation
perf: set initial-route and scanner-worker bundle budgets - #229
truthixify merged 3 commits into
Conversation
|
@JafetCHVDev is attempting to deploy a commit to the truthixify's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@JafetCHVDev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
There was a problem hiding this comment.
Retry does not retry a failed route import. React.lazy caches the rejected import promise; clearing the error boundary state simply renders the same lazy component and throws the same cached rejection again. This is especially visible for the transient-network case the UI says it handles. Please recreate or remount a fresh lazy loader and import attempt on retry, and add a test covering rejected-first then successful-second chunk loading. Using reload as the only recovery is also coherent if that is the intended behavior.
|
Thanks for catching that. You were right: Send/Receive/etc. were lazy() singletons at module scope in App.tsx, so React.lazy's rejected-promise cache on that one object persisted for the tab's whole lifetime. Retry's setState({error: null}) just re-rendered the same cached-rejected lazy component, so it re-threw immediately. Fixed in src/components/RouteErrorBoundary.tsx: added a LazyPage component that builds the lazy() inside useMemo(() => lazy(loader), [loader]), tied to that component instance rather than module scope. RouteErrorBoundary's retry already swaps its rendered output between the fallback and children (a structural change at that position), which remounts the children subtree from scratch, so LazyPage's useMemo cache is naturally discarded and a fresh lazy component (and fresh import()) is built. App.tsx's 13 lazy() consts became plain loader functions, each route now renders . Added RouteErrorBoundary.test.tsx: a loader that rejects until the test flips a flag and clicks Retry, then resolves; asserts the boundary renders the loaded content rather than getting stuck replaying the rejection (verified this fails against both the original code and a bare lazy() with no LazyPage). Also merged upstream/develop to pick up #230 and #227 (resolved a real conflict in App.tsx, both touching the effects block; auto-merged cleanly) and reran the full verification: pnpm format:check, pnpm test:unit (348 passing, 2 pre-existing unrelated portfolio.test.ts date-bucketing failures, same as on develop), clean pnpm build, and pnpm build:budget-check, all green. |
truthixify
left a comment
There was a problem hiding this comment.
Retry now remounts LazyPage and creates a fresh React.lazy instance, so a rejected dynamic import is not replayed from React\047s cached promise. The route and bundle-budget suites (12 tests) and production build pass locally.
Summary
Closes #219
The production build had no route or worker size budgets: the entry chunk was 3,701.60 kB and the Stellar scanner worker was 982 kB, both unbounded and eagerly shipped regardless of which chain or page the user actually needed.
App.tsx): all 13 page components now load viaReact.lazy()behind aSuspenseboundary instead of being statically imported into the entry.pages/Send.tsx,pages/Receive.tsx): each chain's wallet SDK (wagmi/RainbowKit for Horizen, Freighter +@stellar/stellar-sdkfor Stellar,@solana/wallet-adapter-*for Solana,@ckb-ccc/*for CKB) now loads only when that chain is actually selected, instead of all four loading up front.RouteErrorBoundary+RouteFallback: a lazy chunk'simport()can reject (stale deploy, flaky network); the boundary catches that and offers Retry (re-attempt the same import) or Reload (pick up the current deploy's asset manifest), with a loading fallback for the normal case.scripts/bundle-budget-gate.mjs, wired into.github/workflows/ci.ymlviapnpm build:budget-check): checks the true initial-load set (parsed fromdist/index.html's<script type="module">/<link rel="modulepreload">tags, so anything only reachable via a dynamicimport()is correctly excluded), the scanner worker chunk, and every other (lazy route / lazy chain component / third-party vendor) chunk against three budgets. Has its own unit tests (scripts/bundle-budget-gate.test.mjs).Result: the initial entry chunk went from 3,743.93 kB to 3,015.65 kB (~19% smaller) on this machine's build.
StellarSend(384 kB),StellarReceive(107 kB),HorizenReceive(24 kB),Vault(36 kB), andStellarSplit(32 kB) are now separate, on-demand chunks instead of being baked into the entry.What I tried for the scanner worker that did not help (documented so it isn't retried)
I expected
@stellar/stellar-sdk/minimal(or importing@stellar/stellar-basedirectly, bypassing the SDK wrapper) to shrink the worker, since the worker only parses already-fetched XDR and never touches Horizon/RPC/axios. Empirically, with a fully cleared build cache, both made the worker larger (1.77 MB and 1.42 MB respectively, up from 982 kB), not smaller —@stellar/stellar-sdk's root and/minimalentry files are byte-identical in this SDK version, and switching the import specifier only cost tree-shaking benefit without removing any real code. I reverted both attempts and left the worker's import as-is; its budget here (1.1 MB) reflects that measured floor rather than a further reduction.Not done in this PR (flagged as a clear next step, not silently dropped)
The remaining ~3 MB in the entry is dominated by
main.tsxunconditionally mounting all four chains' wallet provider trees (WagmiProvider/RainbowKitProvider, Solana'sConnectionProvider/WalletProvider,ccc.Provider) regardless of which chain is active. Deferring provider mounting until a chain is selected would be the next lever here, but it's a correctness-sensitive change to the app's root provider tree (several components read wallet hooks that assume their provider is always mounted) that deserves its own PR and review rather than being bundled into this one.Test plan
pnpm build(clean, withnode_modules/.viteanddist/cleared first)pnpm build:budget-checkpasses against the resultingdist/pnpm format:checkpasses repo-widepnpm test:unit— same 4 pre-existing failures as ondevelop(unrelated: a date-bucketing test inportfolio.test.tsand a jsdom env issue innameWatchlistStore.test.ts, neither file touched here), all other 339 tests pass including the 11 new tests for the budget gatepnpm devand drove it in a real browser: loaded/sendand/receive, cycled through all four chains (Horizen, Stellar, Solana, CKB) on each via the chain switcher, confirmed the correct wallet-connect control renders for each (Freighter for Stellar, Phantom's "Select Wallet" for Solana, etc.) and that no console errors or failed chunk loads appeared at any point