Skip to content

perf(web): keep react-virtualized out of the entry chunk - #14569

Merged
dylanjeffers merged 1 commit into
mainfrom
perf/lazy-react-virtualized
Aug 19, 2026
Merged

dylanjeffers merged 1 commit into
mainfrom
perf/lazy-react-virtualized

Conversation

@dylanjeffers

Copy link
Copy Markdown
Contributor

Replaces #14566, which GitHub closed when a force-push (rebasing off the now-merged #14565) orphaned the head SHA it had recorded. Same branch, same content — now a clean 6-file diff against main.

2 of 4 in a stack reducing the web entry chunk. #14565 is merged; this is next.

What

react-virtualized is ~638 KB of source and was landing in the entry chunk for every visitor through two unrelated edges, neither of which needs it at first paint.

1. FilterButton imported it statically

It only renders VirtualizedOptionsList behind the opt-in virtualized prop — 3 call sites in the whole web app. Split into its own module, loaded on demand behind a Suspense boundary. The menu only renders while open, so the import starts when a virtualized filter menu is actually opened.

FilterButton is reachable from the eager app shell via PaymentMethod, which is why this landed in the entry chunk rather than a lazy page.

Note the as typeof import(...) on the lazy const: React.lazy erases the component's <Value extends string> generic, so it would otherwise resolve to the string instantiation and lose type safety at call sites.

2. A five-line predicate

TransactionDetailsContent imported isChangePositive — a function that checks two enum values — from AudioTransactionsTable. That module imports components/table, which imports react-virtualized. Because the TransactionDetails modal is registered eagerly in registerNiceModals, the entire virtualized table came along for this:

export const isChangePositive = (tx: TransactionDetails) => {
  return (
    tx.transactionType === TransactionType.PURCHASE ||
    tx.method === TransactionMethod.RECEIVE
  )
}

Moved to its own module, re-exported from the table for backwards compatibility.

Verification

react-virtualized confirmed absent from the entry chunk via sourcemap attribution. Web tests pass (152), typecheck and lint clean.

🤖 Generated with Claude Code

react-virtualized is ~638 KB of source and was landing in the entry chunk for
every visitor through two unrelated edges, neither of which needs it at first
paint.

1. harmony's FilterButton imported it statically, but only renders
   VirtualizedOptionsList behind the opt-in `virtualized` prop -- 3 call sites
   across the whole web app. Split into its own module and loaded on demand
   behind a Suspense boundary. The menu only renders while open, so the import
   starts when a virtualized filter menu is actually opened.

   Note the `as typeof import(...)` on the lazy const: React.lazy erases the
   component's `<Value extends string>` generic, so it would otherwise resolve
   to the `string` instantiation and lose type safety at call sites.

2. TransactionDetailsContent imported `isChangePositive` -- a five-line
   predicate over two enum values -- from AudioTransactionsTable. That module
   imports components/table, which imports react-virtualized. Because the
   TransactionDetails modal is registered eagerly in registerNiceModals, the
   whole virtualized table came along for a function that checks whether a
   transaction increases a balance. Moved the predicate to its own module and
   re-exported it from the table for backwards compatibility.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: e7eb45c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@dylanjeffers
dylanjeffers merged commit de6dc3d into main Aug 19, 2026
17 checks passed
@dylanjeffers
dylanjeffers deleted the perf/lazy-react-virtualized branch August 19, 2026 16:51
dylanjeffers added a commit that referenced this pull request Sep 24, 2026
Follow-ups from reviewing #14561, #14562, #14567, #14568, #14569,
#14570, #14571 and #14572.

**Logic fixes**
- **Wallet auth, reconnect race (#14567):** a lazily loaded wagmi config
starts out `disconnected`, and only WagmiProvider's re-render kicks off
`reconnect()`. If the SDK read the status first, an external-wallet user
was cached with an empty Hedgehog client. `getAudiusWalletClient` now
starts the reconnect itself.
- **Wallet auth, failed chunk load (#14567):** if the AppKit chunk
failed to load, `initSdk` threw and left `inProgress` stuck, so every
later `audiusSdk()` call hung. It now falls back to Hedgehog, and the
memoized import resets so it can be retried.
- **Direct AppKit imports (#14567):** importing `ReownAppKitModal`
directly (Buy/Sell, sign-in) never marked AppKit as loaded. wagmi hooks
kept reading the placeholder config, and sign-out skipped the wallet
disconnect. The module now registers itself when it is evaluated.
- **Placeholder wagmi config (#14567):** it silently reconnected any
injected wallet the site had been authorized for, because
injected-wallet discovery was on and there was no storage. Discovery is
now off and it has no connectors.
- **Duplicate chain definition (#14567):** `audiusChain` was defined
twice; there is now one definition.
- **Upload seed (#14561):** the upload form re-applied `initialMetadata`
(contest genre/remix/artwork, coin-gate conditions) on every
reinitialize. Example: switching a coin-gated upload to Public,
cancelling the confirm modal and continuing again brought the gate back.
The seed is now applied once, when leaving the select step.
- **Lazy Lottie playback (#14568):** effects that call
`lottieRef.current.play()` ran before the lazy chunk loaded and never
re-ran, so the play-bar loading spinner could stay frozen. `LazyLottie`
now reports when the ref is ready (callers re-run their effect) and
renders a sized placeholder while the chunk loads.
- **Unavailable tracks (#14570/#14572):** owners are exempt from the
non-streamable redirect, so an artist can reach their own track once the
API also flags no-audio tracks (api#1032). The redirect to 404 now
replaces history, so Back doesn't loop.
- **Empty-feed suggestions (#14562):** mobile web now uses the
personalized follow suggestions, and the native empty feed switches its
copy when suggestions are personalized.

**Comment cleanup:** shortened or removed long history-style comments
from these PRs, and dropped the no-`track_cid` claim, since api#1032
isn't merged.

**Tests**
- `tsc` passes for web, common, harmony, mobile and sdk.
- vitest passes: `UploadTrackForm.test.ts`, the new `SelectPage.test.ts`
and `trackAvailability.test.ts`, `FanClubDetailPage.test.tsx`,
`ProfilePage.test.tsx`.
- eslint passes on changed files.
- Local dev server:
  - A signed-out visitor with no persisted wallet doesn't load AppKit.
- With a fake persisted `wagmi.store`, AppKit loads, the reconnect runs,
and the SDK initializes (falls back to Hedgehog, no hang).
  - Importing `ReownAppKitModal` directly marks AppKit as loaded.
- Not tested with a real external wallet.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant