perf(web): load the wallet and payment SDKs on demand - #14567
Open
dylanjeffers wants to merge 1 commit into
Open
perf(web): load the wallet and payment SDKs on demand#14567dylanjeffers wants to merge 1 commit into
dylanjeffers wants to merge 1 commit into
Conversation
ReownAppKitModal runs `new WagmiAdapter(...)`, `new SolanaAdapter()` and `createAppKit(...)` at module scope, so a single static import anywhere in the eager graph pinned @reown/*, @walletconnect/*, wagmi and @solana/web3.js into the entry chunk -- for every visitor, including everyone who never opens a wallet. @coinflowlabs/react (which bundles the nsure-ai fraud SDK) was there for the same reason. Together these were ~1.9 MB of the entry chunk. The surprising edge was auth: services/audius-sdk/auth.ts dereferenced `wagmiAdapter.wagmiConfig` at *module scope*, so every email/password user loaded the entire wallet stack to discover they did not need it. It now returns to Hedgehog before loading AppKit unless localStorage shows a persisted wallet connection. That probe deliberately errs toward loading: a false negative would silently downgrade an external-wallet user to Hedgehog, while a false positive only costs a chunk fetch. WagmiProvider stays mounted with a lightweight bootstrap config and swaps in the adapter's config once AppKit loads. Making the provider itself conditional would remount the entire app the moment a wallet appeared; swapping a context value does not. The bootstrap config sets `storage: null` so it cannot clobber the real config's persisted `wagmi.store` entry. The modals are registered lazily. This is safe because nice-modal-react's NiceModalPlaceholder filters the registry by *currently visible* ids, so a registered-but-never-shown modal never mounts and its hooks never run -- the cost was always the static import graph, not runtime. Registration moved out of each modal module on purpose: if they still self-registered, the dynamic import would overwrite MODAL_REGISTRY mid-flight and React would swap the element type under an open modal, remounting it and losing its state. The Suspense boundary is local because NiceModal.Provider mounts its placeholder outside the only boundary in routes.tsx. CoinflowPurchaseProtection in routes.tsx is now lazy too. It still renders unconditionally, so the chunk is still fetched at startup -- it is off the entry chunk's parse path rather than deferred outright. Rendering it only on purchase-capable routes would defer it properly, but that is a call for whoever owns payments, since early initialization may be deliberate. NEEDS QA: external wallet connect / disconnect / sign-in, which could not be exercised without a real wallet. The Hedgehog (email/password) path is verified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
3 of 4 in a stack reducing the web entry chunk. Based on #14566. This is the one that needs real review — it touches the auth path and the root provider.
What
ReownAppKitModalrunsnew WagmiAdapter(...),new SolanaAdapter()andcreateAppKit(...)at module scope, so a single static import anywhere in the eager graph pinned@reown/*,@walletconnect/*, wagmi and@solana/web3.jsinto the entry chunk — for every visitor, including everyone who never opens a wallet.@coinflowlabs/react(which bundles the nsure-ai fraud SDK) was there for the same reason.Together, ~1.9 MB of the entry chunk.
The surprising part: this was on the auth path
services/audius-sdk/auth.tsdereferencedwagmiAdapter.wagmiConfigat module scope, so every email/password user loaded the entire wallet stack to discover they didn't need it.It now returns to Hedgehog before loading AppKit unless localStorage shows a persisted wallet connection. That probe deliberately errs toward loading: a false negative would silently downgrade an external-wallet user to Hedgehog (a correctness bug), while a false positive only costs a chunk fetch.
Why WagmiProvider stays mounted
It keeps a lightweight bootstrap config and swaps in the adapter's config once AppKit loads. Making the provider itself conditional would remount the entire app the moment a wallet appeared; swapping a context value does not. The bootstrap config sets
storage: nullso it can't clobber the real config's persistedwagmi.storeentry.Why lazy modal registration is safe
nice-modal-react's
NiceModalPlaceholderfilters the registry by currently visible ids:So a registered-but-never-shown modal never mounts and its hooks never run — the cost was always the static import graph, not runtime.
Registration moved out of each modal module on purpose. If they still self-registered, the dynamic import would overwrite
MODAL_REGISTRYmid-flight and React would swap the element type under an open modal, remounting it and losing its state. The Suspense boundary is local becauseNiceModal.Providermounts its placeholder outside the only boundary inroutes.tsx.Known limitation
CoinflowPurchaseProtectioninroutes.tsxis now lazy but still renders unconditionally, so the chunk is still fetched at startup — it's off the entry chunk's parse path rather than deferred outright. Rendering it only on purchase-capable routes would defer it properly, but that's a call for whoever owns payments, since early initialization may be deliberate.External wallet connect / disconnect / sign-in could not be exercised without a real wallet. The Hedgehog (email/password) path is verified: app boots, no console errors, AppKit chunk confirmed not fetched on load, and confirmed to load on demand when a wallet modal opens.
🤖 Generated with Claude Code