Skip to content

Commit dc6c4f2

Browse files
committed
Keychain bundle optimizations
1 parent 0f81d9f commit dc6c4f2

File tree

7 files changed

+212
-114
lines changed

7 files changed

+212
-114
lines changed

packages/keychain/package.json

-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
"fast-deep-equal": "catalog:",
3838
"graphql-request": "^5.0.0",
3939
"inapp-spy": "4.2.1",
40-
"lodash": "catalog:",
4140
"p-throttle": "^6.2.0",
4241
"posthog-js-lite": "3.2.1",
4342
"react": "catalog:",
@@ -51,7 +50,6 @@
5150
"devDependencies": {
5251
"@cartridge/eslint": "workspace:*",
5352
"@cartridge/tsconfig": "workspace:*",
54-
"@types/lodash": "catalog:",
5553
"@storybook/addon-essentials": "catalog:",
5654
"@storybook/addon-themes": "catalog:",
5755
"@storybook/blocks": "catalog:",

packages/keychain/src/components/ExecutionContainer.tsx

+25-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { Funding } from "./funding";
1414
import { DeployController } from "./DeployController";
1515
import { ErrorCode } from "@cartridge/account-wasm/controller";
1616
import { parseControllerError } from "@/utils/connection/execute";
17-
import isEqual from "lodash/isEqual";
1817

1918
interface ExecutionContainerProps {
2019
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -90,8 +89,8 @@ export function ExecutionContainer({
9089

9190
// Only estimate if transactions or details have changed
9291
if (
93-
isEqual(prevTransactionsRef.current.transactions, transactions) &&
94-
isEqual(
92+
isDeepEqual(prevTransactionsRef.current.transactions, transactions) &&
93+
isDeepEqual(
9594
prevTransactionsRef.current.transactionsDetail,
9695
transactionsDetail,
9796
)
@@ -236,3 +235,26 @@ export function ExecutionContainer({
236235
</LayoutContainer>
237236
);
238237
}
238+
239+
// Add native deep equality function
240+
function isDeepEqual<T extends Record<string, unknown>>(
241+
obj1: T,
242+
obj2: T,
243+
): boolean {
244+
if (obj1 === obj2) return true;
245+
if (typeof obj1 !== "object" || typeof obj2 !== "object") return false;
246+
if (obj1 === null || obj2 === null) return false;
247+
248+
const keys1 = Object.keys(obj1);
249+
const keys2 = Object.keys(obj2);
250+
251+
if (keys1.length !== keys2.length) return false;
252+
253+
return keys1.every((key) => {
254+
if (!Object.prototype.hasOwnProperty.call(obj2, key)) return false;
255+
return isDeepEqual(
256+
obj1[key] as Record<string, unknown>,
257+
obj2[key] as Record<string, unknown>,
258+
);
259+
});
260+
}

packages/keychain/src/components/funding/PurchaseCredits.tsx

+41-23
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { useCallback, useMemo, useState } from "react";
1+
import { useCallback, useMemo, useState, lazy, Suspense } from "react";
2+
import type { Appearance } from "@stripe/stripe-js";
23
import {
34
LayoutContainer,
45
LayoutContent,
@@ -13,8 +14,6 @@ import {
1314
import { useConnection } from "@/hooks/connection";
1415
import { AmountSelection } from "./AmountSelection";
1516
import { ErrorAlert } from "@/components/ErrorAlert";
16-
import { Elements } from "@stripe/react-stripe-js";
17-
import { Appearance, loadStripe } from "@stripe/stripe-js";
1817
import { Balance } from "./Balance";
1918
import CheckoutForm from "./StripeCheckout";
2019
import { isIframe } from "@cartridge/utils";
@@ -34,16 +33,42 @@ type PurchaseCreditsProps = {
3433
onBack?: () => void;
3534
};
3635

36+
// Lazy load stripe components
37+
const StripeElements = lazy(() =>
38+
import("@stripe/react-stripe-js").then((mod) => ({
39+
default: mod.Elements,
40+
})),
41+
);
42+
43+
const loadStripeAsync = async () => {
44+
const { loadStripe } = await import("@stripe/stripe-js");
45+
return loadStripe;
46+
};
47+
3748
export function PurchaseCredits({ isSlot, onBack }: PurchaseCreditsProps) {
3849
const { closeModal, chainId, controller } = useConnection();
3950

4051
const [clientSecret, setClientSecret] = useState("");
4152
const [isLoading, setisLoading] = useState<boolean>(false);
4253
const [state, setState] = useState<PurchaseState>(PurchaseState.SELECTION);
4354
const [creditsAmount, setCreditsAmount] = useState<number>(DEFAULT_AMOUNT);
44-
const stripePromise = useMemo(() => loadStripe(STRIPE_API_PUBKEY), []);
55+
const [stripePromise] = useState(() =>
56+
loadStripeAsync().then((load) => load(STRIPE_API_PUBKEY)),
57+
);
4558
const [error, setError] = useState<Error>();
4659

60+
const appearance = useMemo(
61+
(): Appearance => ({
62+
theme: "night" as const,
63+
variables: {
64+
colorPrimary: "#FBCB4A",
65+
colorBackground: "#161A17",
66+
focusBoxShadow: "none",
67+
},
68+
}),
69+
[],
70+
);
71+
4772
const onAmountChanged = useCallback(
4873
(amount: number) => setCreditsAmount(amount),
4974
[setCreditsAmount],
@@ -79,27 +104,20 @@ export function PurchaseCredits({ isSlot, onBack }: PurchaseCreditsProps) {
79104
}
80105
}, [controller, creditsAmount]);
81106

82-
const appearance = {
83-
theme: "night",
84-
variables: {
85-
colorPrimary: "#FBCB4A",
86-
colorBackground: "#161A17",
87-
focusBoxShadow: "none",
88-
},
89-
} as Appearance;
90-
91107
if (state === PurchaseState.STRIPE_CHECKOUT) {
92108
return (
93-
<Elements
94-
options={{ clientSecret, appearance, loader: "auto" }}
95-
stripe={stripePromise}
96-
>
97-
<CheckoutForm
98-
onBack={() => setState(PurchaseState.SELECTION)}
99-
onComplete={() => setState(PurchaseState.SUCCESS)}
100-
creditsAmount={creditsAmount}
101-
/>
102-
</Elements>
109+
<Suspense fallback={<div>Loading payment system...</div>}>
110+
<StripeElements
111+
options={{ clientSecret, appearance, loader: "auto" }}
112+
stripe={stripePromise}
113+
>
114+
<CheckoutForm
115+
onBack={() => setState(PurchaseState.SELECTION)}
116+
onComplete={() => setState(PurchaseState.SUCCESS)}
117+
creditsAmount={creditsAmount}
118+
/>
119+
</StripeElements>
120+
</Suspense>
103121
);
104122
}
105123

packages/keychain/vite.config.ts

+56-12
Original file line numberDiff line numberDiff line change
@@ -45,35 +45,79 @@ export default defineConfig(({ mode }) => ({
4545
external: [],
4646
output: {
4747
manualChunks(id) {
48-
// Chunk splitting logic
4948
if (id.includes("node_modules")) {
50-
if (id.includes("react")) {
51-
return "react-vendor";
49+
// React and related packages
50+
if (id.includes("react/") || id.includes("react-dom/")) {
51+
return "react-core";
52+
}
53+
// React ecosystem packages
54+
if (id.includes("react-router") || id.includes("react-query")) {
55+
return "react-libs";
56+
}
57+
// Stripe related code
58+
if (id.includes("@stripe")) {
59+
return "stripe";
60+
}
61+
// Web3 and crypto related code
62+
if (
63+
id.includes("wagmi") ||
64+
id.includes("viem") ||
65+
id.includes("starknet") ||
66+
id.includes("@noble/") ||
67+
id.includes("caip") ||
68+
id.includes("@starknet-react") ||
69+
id.includes("@cartridge/account-wasm") ||
70+
id.includes("@cartridge/controller") ||
71+
id.includes("cbor")
72+
) {
73+
return "web3";
74+
}
75+
// UI components and styling
76+
if (
77+
id.includes("@cartridge/ui-next") ||
78+
id.includes("embla-carousel")
79+
) {
80+
return "ui";
5281
}
53-
// Split other large dependencies into separate chunks
5482
return "vendor";
5583
}
5684
},
5785
},
5886
},
5987
target: "esnext",
60-
minify: "esbuild", // esbuild is faster than terser and almost as effective
88+
minify: "esbuild",
6189
sourcemap: mode === "development",
62-
// Reduce chunk size warnings and enable compression reporting
6390
chunkSizeWarningLimit: 1000,
6491
reportCompressedSize: true,
65-
// Optimize build speed and output
6692
cssCodeSplit: true,
6793
modulePreload: {
68-
polyfill: false, // Reduces polyfill size if you don't need older browser support
94+
polyfill: false,
95+
},
96+
timeout: 120000,
97+
assetsInlineLimit: 4096,
98+
commonjsOptions: {
99+
include: [/node_modules/],
100+
extensions: [".js", ".cjs"],
101+
strictRequires: true,
102+
transformMixedEsModules: true,
69103
},
70-
// Add a longer timeout for builds
71-
timeout: 120000, // 2 minutes
72104
},
73105
optimizeDeps: {
74-
include: ["react", "react-dom"], // Pre-bundle common dependencies
106+
include: [
107+
"react",
108+
"react-dom",
109+
"react-router-dom",
110+
"react-query",
111+
"@cartridge/ui-next",
112+
],
113+
exclude: ["@cartridge/account-wasm"],
114+
esbuildOptions: {
115+
target: "esnext",
116+
supported: {
117+
"top-level-await": true,
118+
},
119+
},
75120
},
76-
// Add this to ensure polyfills are properly included
77121
define: {
78122
global: "globalThis",
79123
},

packages/profile/package.json

+11-13
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
"@cartridge/penpal": "catalog:",
2424
"@cartridge/ui-next": "workspace:*",
2525
"@cartridge/utils": "workspace:*",
26+
"@starknet-io/types-js": "catalog:",
27+
"@wagmi/core": "^1.4.12",
2628
"compare-versions": "^6.1.1",
27-
"lodash": "catalog:",
29+
"graphql-request": "^5.0.0",
2830
"react": "catalog:",
2931
"react-dom": "catalog:",
3032
"react-query": "catalog:",
@@ -33,19 +35,14 @@
3335
"starknet": "catalog:",
3436
"viem": "catalog:",
3537
"vite-plugin-top-level-await": "catalog:",
36-
"vite-plugin-wasm": "catalog:"
38+
"vite-plugin-wasm": "catalog:",
39+
"wagmi": "^1.4.12"
3740
},
3841
"devDependencies": {
3942
"@cartridge/eslint": "workspace:*",
40-
"@storybook/addon-essentials": "catalog:",
41-
"@storybook/addon-themes": "catalog:",
42-
"@storybook/blocks": "catalog:",
43-
"@storybook/react": "catalog:",
44-
"@storybook/react-vite": "catalog:",
45-
"@storybook/test": "catalog:",
46-
"@storybook/test-runner": "catalog:",
47-
"@types/lodash": "catalog:",
48-
"@types/jest-image-snapshot": "catalog:",
43+
"@cartridge/tsconfig": "workspace:*",
44+
"@testing-library/jest-dom": "^6.5.0",
45+
"@testing-library/react": "^13.4.0",
4946
"@types/node": "catalog:",
5047
"@types/react": "catalog:",
5148
"@types/react-dom": "catalog:",
@@ -55,15 +52,16 @@
5552
"globals": "catalog:",
5653
"http-server": "catalog:",
5754
"jest-image-snapshot": "catalog:",
55+
"jsdom": "^25.0.1",
5856
"postcss": "catalog:",
59-
"postcss-import": "catalog:",
6057
"prettier": "catalog:",
6158
"start-server-and-test": "catalog:",
6259
"storybook": "catalog:",
6360
"tailwindcss": "catalog:",
6461
"typescript": "catalog:",
6562
"typescript-eslint": "catalog:",
6663
"vite": "catalog:",
67-
"vite-tsconfig-paths": "catalog:"
64+
"vite-tsconfig-paths": "catalog:",
65+
"vitest": "^2.1.8"
6866
}
6967
}

packages/profile/src/components/modules/recipient.tsx

+38-10
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,36 @@ import {
1313
cn,
1414
} from "@cartridge/ui-next";
1515
import { formatAddress } from "@cartridge/utils";
16-
import { useCallback, useEffect, useMemo, useState } from "react";
17-
import { debounce } from "lodash";
16+
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
1817
import { Wallet } from "@/hooks/wallet";
1918

19+
function useDebounce<T extends (...args: any[]) => any>(
20+
callback: T,
21+
delay: number,
22+
): (...args: Parameters<T>) => void {
23+
const timeoutRef = useRef<NodeJS.Timeout>();
24+
25+
useEffect(() => {
26+
return () => {
27+
if (timeoutRef.current) {
28+
clearTimeout(timeoutRef.current);
29+
}
30+
};
31+
}, []);
32+
33+
return useCallback(
34+
(...args: Parameters<T>) => {
35+
if (timeoutRef.current) {
36+
clearTimeout(timeoutRef.current);
37+
}
38+
timeoutRef.current = setTimeout(() => {
39+
callback(...args);
40+
}, delay);
41+
},
42+
[callback, delay],
43+
);
44+
}
45+
2046
export const Recipient = ({
2147
to,
2248
setTo,
@@ -61,15 +87,10 @@ export const Recipient = ({
6187
return getIcon(selectedWallet);
6288
}, [selectedWallet, getIcon]);
6389

64-
useEffect(() => {
90+
const handleDebounce = useDebounce((value: string) => {
6591
setIsLoading(true);
66-
const handler = debounce(() => {
67-
setIsLoading(false);
68-
setNameOrAddress(value);
69-
}, 500);
70-
handler();
71-
return () => handler.cancel();
72-
}, [value, setNameOrAddress]);
92+
setNameOrAddress(value);
93+
}, 500);
7394

7495
const handleClear = useCallback(() => {
7596
setValue("");
@@ -119,6 +140,13 @@ export const Recipient = ({
119140
setWarning,
120141
]);
121142

143+
useEffect(() => {
144+
handleDebounce(value);
145+
return () => {
146+
// Cleanup handled by useDebounce hook
147+
};
148+
}, [value, handleDebounce]);
149+
122150
return (
123151
<div className="flex flex-col gap-y-px">
124152
<div className="flex items-center justify-between">

0 commit comments

Comments
 (0)