Skip to content

Monorepo optimizations#1990

Draft
arentant wants to merge 16 commits into
dev-monorepofrom
dev-nonorepo-optimized
Draft

Monorepo optimizations#1990
arentant wants to merge 16 commits into
dev-monorepofrom
dev-nonorepo-optimized

Conversation

@arentant
Copy link
Copy Markdown
Collaborator

No description provided.

babkenmes and others added 12 commits May 14, 2026 17:54
Lab-only Lighthouse + bundle measurements comparing dev (single app)
against dev-monorepo-1.3.0, plus an audit of which chain SDKs land on
the critical path eagerly. Source of truth for the perf rollout this
branch is about to land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layer A (per perf-audit-eager-imports.md): React.lazy + Suspense around
EVMProviderWrapper so wagmi setup defers until first mount.
Layer B: useEVMConnectors dynamic-imports @wagmi/connectors via
useState/useEffect, exposing browserInjected + hiddenWalletConnect
immediately and appending MetaMask/Coinbase/WalletConnect factories
once the chunk resolves. Wagmi's reconnect-on-mount handles the
connector-list update.
Plus /*#__PURE__*/ on the module-scope new QueryClient() so minifiers
can drop it when unused.

No public API change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
React.lazy + Suspense around TronProviderWrapper to defer tronweb and
@tronweb3/* until the wrapper mounts. Both createTronProvider() and the
deprecated TronProvider use the same Suspense-wrapped lazy ref so the
public API is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
React.lazy + Suspense around TonProviderWrapper to defer @tonconnect/ui-react,
@ton/ton and @ton/core until the wrapper mounts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
React.lazy + Suspense around SVMProviderWrapper to defer
@solana/web3.js and @solana/wallet-adapter-base until the wrapper mounts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
React.lazy + Suspense around BitcoinProvider wrapper (named export
re-mapped to default via .then(m => ({ default: m.BitcoinProvider })))
to defer @bigmi/* and bitcoinjs-lib until the wrapper mounts.
Plus /*#__PURE__*/ on the module-scope new QueryClient().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
React.lazy + Suspense around FuelProviderWrapper to defer @fuels/react
and @fuel-ts/* until the wrapper mounts. Plus /*#__PURE__*/ on the
module-scope new QueryClient().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
React.lazy + Suspense around StarknetProviderWrapper to defer
@starknet-react/core and starknet until the wrapper mounts. Both
createStarknetProvider() and the deprecated StarknetProvider use the
same Suspense-wrapped lazy ref.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the static ParadexBalanceProvider import (which pulled
@paradex/sdk eagerly) with a LazyBalanceProvider whose factory does
import("./paradexBalanceProvider"). createParadexProvider() now returns
the lazy variant by default; the deprecated ParadexProvider keeps its
existing shape with a comment pointing at the lazy form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…verrides

Adds framer-motion: 12.26.2 to the pnpm catalog and pins it via both
pnpm.overrides and resolutions so transitive deps from wagmi /
@WalletConnect / etc. can't bring a second major back in. widget,
bridge and explorer all switch to "framer-motion": "catalog:".

v10 was attempted first to match the apps' floor but its types fall
through to unknown generics on React 19; v12 is the lowest version
that compiles cleanly across the whole tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New exports/transactions.ts re-exports just TransactionsHistory,
inflateSettings, LayerswapProvider, useSettingsState and the theme
tokens — enough for any consumer that renders the transactions view
without the swap form. package.json adds the matching "./transactions"
exports entry; the root barrel is untouched, so existing
@layerswap/widget imports keep working.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extracts the getDefaultProviders({…}) call out of WidgetWrapper into a
new defaultWalletProviders.ts module that's never imported statically.
WidgetWrapper falls back to walletProviders={[]} when no prop is
passed, so routes that don't render the swap form (/transactions,
/campaigns, /campaigns/[campaign], /imtblRedirect already narrow)
ship zero wallet-bundle bytes.

The swap-flow pages (/ via Pages/Swap and /swap/[swapId]) dynamic-
import defaultWalletProviders in a useEffect and feed the result back
via setState; wagmi / @TonConnect / @bigmi / etc. handle the
late-registering provider list via their reconnect-on-mount lifecycle.

Also adds @layerswap/widget + @layerswap/wallets to
optimizePackageImports in next.config.js — neutral here since both are
in transpilePackages, but correct for external consumers and harmless.

Route-level First Load JS, pre vs post this commit:
  /              2.07 MB -> 667 KB   (target ≤ 1.77 MB)
  /swap/[swapId] 2.02 MB -> 611 KB
  /transactions  1.96 MB -> 551 KB   (target ≤ 1.45 MB)
  /campaigns     1.96 MB -> 551 KB
  /imtblRedirect 1.81 MB -> 506 KB

Bridge-only change — packages/ stay framework-neutral; the constraints
on next/dynamic / next/image / next/script / require.ensure /
require.context apply to packages/ only, and import() is standard ES2020.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
layerswapapp Error Error May 15, 2026 5:12pm

Request Review

babkenmes and others added 3 commits May 14, 2026 21:45
Each chain package now exports a preloadXxxProvider() async function
that imports its heavy chunk. The wrapper component checks a
module-scope XxxProviderImpl cache: if the preload has already filled
it, the wrapper renders the implementation synchronously and skips the
React.lazy + Suspense path entirely. Without a cache hit it still
falls back to the lazy wrapper unchanged, so the behavior is purely
additive.

This is what lets the bridge avoid a Suspense fallback flash between
empty walletProviders={[]} and the populated list: caller awaits
preload*, then setState the providers, and the wrapper has its module
ready when React.lazy is invoked.

No public API removal — adds preload* named exports only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-exports each chain's preloadXxxProvider and adds a new
preloadDefaultProviders() that awaits every chain's preload in
parallel via Promise.all, tolerating individual chunk failures
(failed chain stays on the existing Suspense path on render).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
buildDefaultWalletProviders is now async — it constructs the providers
via getDefaultProviders() and awaits preloadDefaultProviders() so every
chain's lazy chunk is in the module cache by the time the array is
returned. When LayerswapProvider mounts the chains, React.lazy resolves
synchronously and the Suspense fallback doesn't flash.

Swap-flow callers (Pages/Swap and /swap/[swapId]) chain the import +
buildDefaultWalletProviders() promises before calling setState.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the runtime-built walletProviders array with per-chain JSX
shells composed statically by the app. The array model had two latent
Rules-of-Hooks landmines (calling N hooks across renders where N varied
with array length) and forced a full subtree remount whenever the array
transitioned from [] to populated — which is exactly what caused the
Easy-deposit tab to flash empty on cold loads after the deferred-import
perf work.

What changes:
- New connection registry (Zustand) in @layerswap/widget that each
  chain's shell writes into via a single useEffect; consumers
  (WalletProvidersProvider, ResolverProviders, Paradex) read from it.
- defineWalletProvider(): author-facing helper whose input matches the
  legacy WalletProvider shape + an `order` field. Emits a Shell React
  component that nests the chain's wrapper, a generated Registrar
  (calls the connection hook once, registers the result), and children.
- Each chain (EVM, Starknet, SVM, Fuel, Bitcoin, TON, Tron, Paradex,
  IMX-Passport) ships a createXxxShell() factory alongside its legacy
  createXxxProvider(). Default `order` values mirror the legacy
  getDefaultProviders array order so resolution priority is preserved.
- Paradex's ActiveParadexAccountProvider + useParadexConnection no
  longer call EVM/Starknet's connection hooks inline; they read those
  providers from the registry via useWalletConnectionProviderById and
  null-check the brief first-render gap before they register.
- LayerswapProvider drops the walletProviders prop. WalletsProviders
  drops DynamicProviderWrapper (the source of the shape-flip remount).
- Bridge: new DefaultChainShells composes all chains; SwapPage,
  /swap/[swapId], and imtblRedirect render <DefaultChainShells>
  (or hand-composed shells) as JSX children. The async setState
  pattern in apps/bridge/components/Pages/Swap/index.tsx and
  pages/swap/[swapId].tsx is gone; defaultWalletProviders.ts deleted.
- Widget-playground: same migration on both demo apps.

Why this fixes the flash: the chain wrappers sit at fixed JSX positions
from the first render, so no subsequent state change reshapes the tree.
Connection state still resolves asynchronously (wagmi reconnect, etc.)
but writes into a Zustand store outside the React tree — consumers
re-render without subtree remounts.

EVM's full lazy-chunk split (moving useEVMConnection into a
React.lazy'd registrar to keep wagmi off the critical path) is left
for a focused follow-up so its bundle delta can be measured cleanly.

Type-check: @layerswap/widget, all wallet sub-packages, @layerswap/bridge
all green. Two pre-existing typecheck errors in widget-playground/
useCustomEvm.ts and apps/explorer are unchanged and unrelated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arentant arentant marked this pull request as draft May 19, 2026 14:49
Base automatically changed from dev-monorepo-1.3.0 to dev-monorepo May 26, 2026 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants