Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keychain bundle optimizations #1376

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/keychain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"fast-deep-equal": "catalog:",
"graphql-request": "^5.0.0",
"inapp-spy": "4.2.1",
"lodash": "catalog:",
"p-throttle": "^6.2.0",
"posthog-js-lite": "3.2.1",
"react": "catalog:",
Expand All @@ -51,7 +50,6 @@
"devDependencies": {
"@cartridge/eslint": "workspace:*",
"@cartridge/tsconfig": "workspace:*",
"@types/lodash": "catalog:",
"@storybook/addon-essentials": "catalog:",
"@storybook/addon-themes": "catalog:",
"@storybook/blocks": "catalog:",
Expand Down
28 changes: 25 additions & 3 deletions packages/keychain/src/components/ExecutionContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { Funding } from "./funding";
import { DeployController } from "./DeployController";
import { ErrorCode } from "@cartridge/account-wasm/controller";
import { parseControllerError } from "@/utils/connection/execute";
import isEqual from "lodash/isEqual";

interface ExecutionContainerProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -90,8 +89,8 @@ export function ExecutionContainer({

// Only estimate if transactions or details have changed
if (
isEqual(prevTransactionsRef.current.transactions, transactions) &&
isEqual(
isDeepEqual(prevTransactionsRef.current.transactions, transactions) &&
isDeepEqual(
prevTransactionsRef.current.transactionsDetail,
transactionsDetail,
)
Expand Down Expand Up @@ -236,3 +235,26 @@ export function ExecutionContainer({
</LayoutContainer>
);
}

// Add native deep equality function
function isDeepEqual<T extends Record<string, unknown>>(
obj1: T,
obj2: T,
): boolean {
if (obj1 === obj2) return true;
if (typeof obj1 !== "object" || typeof obj2 !== "object") return false;
if (obj1 === null || obj2 === null) return false;

const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);

if (keys1.length !== keys2.length) return false;

return keys1.every((key) => {
if (!Object.prototype.hasOwnProperty.call(obj2, key)) return false;
return isDeepEqual(
obj1[key] as Record<string, unknown>,
obj2[key] as Record<string, unknown>,
);
});
}
64 changes: 41 additions & 23 deletions packages/keychain/src/components/funding/PurchaseCredits.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useCallback, useMemo, useState } from "react";
import { useCallback, useMemo, useState, lazy, Suspense } from "react";
import type { Appearance } from "@stripe/stripe-js";
import {
LayoutContainer,
LayoutContent,
Expand All @@ -13,8 +14,6 @@ import {
import { useConnection } from "@/hooks/connection";
import { AmountSelection } from "./AmountSelection";
import { ErrorAlert } from "@/components/ErrorAlert";
import { Elements } from "@stripe/react-stripe-js";
import { Appearance, loadStripe } from "@stripe/stripe-js";
import { Balance } from "./Balance";
import CheckoutForm from "./StripeCheckout";
import { isIframe } from "@cartridge/utils";
Expand All @@ -34,16 +33,42 @@ type PurchaseCreditsProps = {
onBack?: () => void;
};

// Lazy load stripe components
const StripeElements = lazy(() =>
import("@stripe/react-stripe-js").then((mod) => ({
default: mod.Elements,
})),
);

const loadStripeAsync = async () => {
const { loadStripe } = await import("@stripe/stripe-js");
return loadStripe;
};

export function PurchaseCredits({ isSlot, onBack }: PurchaseCreditsProps) {
const { closeModal, chainId, controller } = useConnection();

const [clientSecret, setClientSecret] = useState("");
const [isLoading, setisLoading] = useState<boolean>(false);
const [state, setState] = useState<PurchaseState>(PurchaseState.SELECTION);
const [creditsAmount, setCreditsAmount] = useState<number>(DEFAULT_AMOUNT);
const stripePromise = useMemo(() => loadStripe(STRIPE_API_PUBKEY), []);
const [stripePromise] = useState(() =>
loadStripeAsync().then((load) => load(STRIPE_API_PUBKEY)),
);
const [error, setError] = useState<Error>();

const appearance = useMemo(
(): Appearance => ({
theme: "night" as const,
variables: {
colorPrimary: "#FBCB4A",
colorBackground: "#161A17",
focusBoxShadow: "none",
},
}),
[],
);

const onAmountChanged = useCallback(
(amount: number) => setCreditsAmount(amount),
[setCreditsAmount],
Expand Down Expand Up @@ -79,27 +104,20 @@ export function PurchaseCredits({ isSlot, onBack }: PurchaseCreditsProps) {
}
}, [controller, creditsAmount]);

const appearance = {
theme: "night",
variables: {
colorPrimary: "#FBCB4A",
colorBackground: "#161A17",
focusBoxShadow: "none",
},
} as Appearance;

if (state === PurchaseState.STRIPE_CHECKOUT) {
return (
<Elements
options={{ clientSecret, appearance, loader: "auto" }}
stripe={stripePromise}
>
<CheckoutForm
onBack={() => setState(PurchaseState.SELECTION)}
onComplete={() => setState(PurchaseState.SUCCESS)}
creditsAmount={creditsAmount}
/>
</Elements>
<Suspense fallback={<div>Loading payment system...</div>}>
<StripeElements
options={{ clientSecret, appearance, loader: "auto" }}
stripe={stripePromise}
>
<CheckoutForm
onBack={() => setState(PurchaseState.SELECTION)}
onComplete={() => setState(PurchaseState.SUCCESS)}
creditsAmount={creditsAmount}
/>
</StripeElements>
</Suspense>
);
}

Expand Down
68 changes: 56 additions & 12 deletions packages/keychain/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,35 +45,79 @@ export default defineConfig(({ mode }) => ({
external: [],
output: {
manualChunks(id) {
// Chunk splitting logic
if (id.includes("node_modules")) {
if (id.includes("react")) {
return "react-vendor";
// React and related packages
if (id.includes("react/") || id.includes("react-dom/")) {
return "react-core";
}
// React ecosystem packages
if (id.includes("react-router") || id.includes("react-query")) {
return "react-libs";
}
// Stripe related code
if (id.includes("@stripe")) {
return "stripe";
}
// Web3 and crypto related code
if (
id.includes("wagmi") ||
id.includes("viem") ||
id.includes("starknet") ||
id.includes("@noble/") ||
id.includes("caip") ||
id.includes("@starknet-react") ||
id.includes("@cartridge/account-wasm") ||
id.includes("@cartridge/controller") ||
id.includes("cbor")
) {
return "web3";
}
// UI components and styling
if (
id.includes("@cartridge/ui-next") ||
id.includes("embla-carousel")
) {
return "ui";
}
// Split other large dependencies into separate chunks
return "vendor";
}
},
},
},
target: "esnext",
minify: "esbuild", // esbuild is faster than terser and almost as effective
minify: "esbuild",
sourcemap: mode === "development",
// Reduce chunk size warnings and enable compression reporting
chunkSizeWarningLimit: 1000,
reportCompressedSize: true,
// Optimize build speed and output
cssCodeSplit: true,
modulePreload: {
polyfill: false, // Reduces polyfill size if you don't need older browser support
polyfill: false,
},
timeout: 120000,
assetsInlineLimit: 4096,
commonjsOptions: {
include: [/node_modules/],
extensions: [".js", ".cjs"],
strictRequires: true,
transformMixedEsModules: true,
},
// Add a longer timeout for builds
timeout: 120000, // 2 minutes
},
optimizeDeps: {
include: ["react", "react-dom"], // Pre-bundle common dependencies
include: [
"react",
"react-dom",
"react-router-dom",
"react-query",
"@cartridge/ui-next",
],
exclude: ["@cartridge/account-wasm"],
esbuildOptions: {
target: "esnext",
supported: {
"top-level-await": true,
},
},
},
// Add this to ensure polyfills are properly included
define: {
global: "globalThis",
},
Expand Down
24 changes: 11 additions & 13 deletions packages/profile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
"@cartridge/penpal": "catalog:",
"@cartridge/ui-next": "workspace:*",
"@cartridge/utils": "workspace:*",
"@starknet-io/types-js": "catalog:",
"@wagmi/core": "^1.4.12",
"compare-versions": "^6.1.1",
"lodash": "catalog:",
"graphql-request": "^5.0.0",
"react": "catalog:",
"react-dom": "catalog:",
"react-query": "catalog:",
Expand All @@ -33,19 +35,14 @@
"starknet": "catalog:",
"viem": "catalog:",
"vite-plugin-top-level-await": "catalog:",
"vite-plugin-wasm": "catalog:"
"vite-plugin-wasm": "catalog:",
"wagmi": "^1.4.12"
},
"devDependencies": {
"@cartridge/eslint": "workspace:*",
"@storybook/addon-essentials": "catalog:",
"@storybook/addon-themes": "catalog:",
"@storybook/blocks": "catalog:",
"@storybook/react": "catalog:",
"@storybook/react-vite": "catalog:",
"@storybook/test": "catalog:",
"@storybook/test-runner": "catalog:",
"@types/lodash": "catalog:",
"@types/jest-image-snapshot": "catalog:",
"@cartridge/tsconfig": "workspace:*",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^13.4.0",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
Expand All @@ -55,15 +52,16 @@
"globals": "catalog:",
"http-server": "catalog:",
"jest-image-snapshot": "catalog:",
"jsdom": "^25.0.1",
"postcss": "catalog:",
"postcss-import": "catalog:",
"prettier": "catalog:",
"start-server-and-test": "catalog:",
"storybook": "catalog:",
"tailwindcss": "catalog:",
"typescript": "catalog:",
"typescript-eslint": "catalog:",
"vite": "catalog:",
"vite-tsconfig-paths": "catalog:"
"vite-tsconfig-paths": "catalog:",
"vitest": "^2.1.8"
}
}
48 changes: 38 additions & 10 deletions packages/profile/src/components/modules/recipient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,36 @@
cn,
} from "@cartridge/ui-next";
import { formatAddress } from "@cartridge/utils";
import { useCallback, useEffect, useMemo, useState } from "react";
import { debounce } from "lodash";
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
import { Wallet } from "@/hooks/wallet";

function useDebounce<T extends (...args: any[]) => any>(

Check failure on line 19 in packages/profile/src/components/modules/recipient.tsx

View workflow job for this annotation

GitHub Actions / ts-lint

Unexpected any. Specify a different type

Check failure on line 19 in packages/profile/src/components/modules/recipient.tsx

View workflow job for this annotation

GitHub Actions / ts-lint

Unexpected any. Specify a different type
callback: T,
delay: number,
): (...args: Parameters<T>) => void {
const timeoutRef = useRef<NodeJS.Timeout>();

useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);

return useCallback(
(...args: Parameters<T>) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
},
[callback, delay],
);
}

export const Recipient = ({
to,
setTo,
Expand Down Expand Up @@ -61,15 +87,10 @@
return getIcon(selectedWallet);
}, [selectedWallet, getIcon]);

useEffect(() => {
const handleDebounce = useDebounce((value: string) => {
setIsLoading(true);
const handler = debounce(() => {
setIsLoading(false);
setNameOrAddress(value);
}, 500);
handler();
return () => handler.cancel();
}, [value, setNameOrAddress]);
setNameOrAddress(value);
}, 500);

const handleClear = useCallback(() => {
setValue("");
Expand Down Expand Up @@ -119,6 +140,13 @@
setWarning,
]);

useEffect(() => {
handleDebounce(value);
return () => {
// Cleanup handled by useDebounce hook
};
}, [value, handleDebounce]);

return (
<div className="flex flex-col gap-y-px">
<div className="flex items-center justify-between">
Expand Down
Loading
Loading