diff --git a/src/main.tsx b/src/main.tsx index fe9bf50..ec8b30a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,14 +1,38 @@ -import React from "react"; +import React, { Suspense, lazy } from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import { WalletProvider } from "./contexts/WalletContext"; import { i18nReady } from "./i18n"; import App from "./App"; -import Help from "./pages/Help"; -import Ranking from "./pages/Ranking"; import "./App.css"; import "./styles/theme.css"; +// Heavy routes (Mapbox GL, ZK/WASM prover, Stellar RPC) are code-split so they +// are only fetched when the user actually navigates to them, keeping the +// initial bundle and Time-To-Interactive low. +const Help = lazy(() => import("./pages/Help")); +const Ranking = lazy(() => import("./pages/Ranking")); + +function RouteFallback() { + return ( +
+ Loading… +
+ ); +} + function render() { ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( @@ -20,11 +44,13 @@ function render() { */} - - } /> - } /> - } /> - + }> + + } /> + } /> + } /> + + , diff --git a/src/pages/Help.tsx b/src/pages/Help.tsx index be2146e..5ef3490 100644 --- a/src/pages/Help.tsx +++ b/src/pages/Help.tsx @@ -32,11 +32,6 @@ import { subscribeToContractEvents, clearWalletAddress, } from "../lib/contract"; -import { - buildLocationProofZone, - generateLocationProof, - shortProofId, -} from "../lib/zk"; import useDocumentTitle from "../lib/useDocumentTitle"; import Button from "../components/ui/Button"; import Input from "../components/ui/Input"; @@ -46,6 +41,25 @@ import "./Help.css"; const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_TOKEN; +// The ZK prover (Noir + Barretenberg WASM, several MB) is only fetched once a +// proof is actually requested. Everything else in the module (zone building, +// proof-id truncation) is cheap, but keeping the whole module out of the +// initial bundle avoids pulling @stellar/stellar-sdk and WASM into the route +// chunk until proof generation begins. +let _zkModule: typeof import("../lib/zk") | null = null; +function loadZk() { + if (!_zkModule) { + _zkModule = import("../lib/zk"); + } + return _zkModule; +} + +function shortProofId(value: unknown): string { + const text = String(value || ""); + if (text.length <= 18) return text; + return `${text.slice(0, 10)}...${text.slice(-6)}`; +} + const MAP_STYLES = [ { id: "satellite", @@ -2714,11 +2728,13 @@ export default function Help() { address, radiusMeters = 3000, }) { - const zone = buildLocationProofZone({ lat, lng, radiusMeters }); dispatchZk({ type: "SET_STATUS", payload: "proving" }); dispatchZk({ type: "SET_ERROR", payload: "" }); dispatchZk({ type: "PUSH_LOG", payload: "Preparing private witness" }); - const proof = await generateLocationProof({ + // Load the ZK prover only now that a proof is actually requested. + const zk = await loadZk(); + const zone = zk.buildLocationProofZone({ lat, lng, radiusMeters }); + const proof = await zk.generateLocationProof({ lat, lng, campaignId, diff --git a/vite.config.ts b/vite.config.ts index 94536e7..e6cd430 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -140,6 +140,7 @@ export default defineConfig({ rollupOptions: { output: { manualChunks(id) { + if (!id.includes("node_modules")) return undefined; if (id.includes("mapbox-gl") || id.includes("react-map-gl")) return "mapbox"; if ( @@ -149,6 +150,18 @@ export default defineConfig({ return "stellar"; if (id.includes("@noir-lang") || id.includes("@aztec/bb.js")) return "zk"; + if ( + id.includes("react") || + id.includes("react-dom") || + id.includes("react-router") || + id.includes("scheduler") || + id.includes("react-i18next") || + id.includes("i18next") + ) + return "react-vendor"; + if (id.includes("@supabase")) return "supabase"; + if (id.includes("buffer")) return "buffer"; + return "vendor"; }, }, },