Skip to content

Commit 64d91e9

Browse files
committed
Keychain bundle optimizations
1 parent 0f81d9f commit 64d91e9

File tree

6 files changed

+152
-65
lines changed

6 files changed

+152
-65
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

+19-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,20 @@ export function ExecutionContainer({
236235
</LayoutContainer>
237236
);
238237
}
238+
239+
// Add native deep equality function
240+
function isDeepEqual(obj1: any, obj2: any): boolean {
241+
if (obj1 === obj2) return true;
242+
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
243+
if (obj1 === null || obj2 === null) return false;
244+
245+
const keys1 = Object.keys(obj1);
246+
const keys2 = Object.keys(obj2);
247+
248+
if (keys1.length !== keys2.length) return false;
249+
250+
return keys1.every(key => {
251+
if (!Object.prototype.hasOwnProperty.call(obj2, key)) return false;
252+
return isDeepEqual(obj1[key], obj2[key]);
253+
});
254+
}

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

+34-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,35 @@ type PurchaseCreditsProps = {
3433
onBack?: () => void;
3534
};
3635

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

4049
const [clientSecret, setClientSecret] = useState("");
4150
const [isLoading, setisLoading] = useState<boolean>(false);
4251
const [state, setState] = useState<PurchaseState>(PurchaseState.SELECTION);
4352
const [creditsAmount, setCreditsAmount] = useState<number>(DEFAULT_AMOUNT);
44-
const stripePromise = useMemo(() => loadStripe(STRIPE_API_PUBKEY), []);
53+
const [stripePromise] = useState(() => loadStripeAsync().then(load => load(STRIPE_API_PUBKEY)));
4554
const [error, setError] = useState<Error>();
4655

56+
const appearance = useMemo((): Appearance => ({
57+
theme: "night" as const,
58+
variables: {
59+
colorPrimary: "#FBCB4A",
60+
colorBackground: "#161A17",
61+
focusBoxShadow: "none",
62+
},
63+
}), []);
64+
4765
const onAmountChanged = useCallback(
4866
(amount: number) => setCreditsAmount(amount),
4967
[setCreditsAmount],
@@ -79,27 +97,20 @@ export function PurchaseCredits({ isSlot, onBack }: PurchaseCreditsProps) {
7997
}
8098
}, [controller, creditsAmount]);
8199

82-
const appearance = {
83-
theme: "night",
84-
variables: {
85-
colorPrimary: "#FBCB4A",
86-
colorBackground: "#161A17",
87-
focusBoxShadow: "none",
88-
},
89-
} as Appearance;
90-
91100
if (state === PurchaseState.STRIPE_CHECKOUT) {
92101
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>
102+
<Suspense fallback={<div>Loading payment system...</div>}>
103+
<StripeElements
104+
options={{ clientSecret, appearance, loader: "auto" }}
105+
stripe={stripePromise}
106+
>
107+
<CheckoutForm
108+
onBack={() => setState(PurchaseState.SELECTION)}
109+
onComplete={() => setState(PurchaseState.SUCCESS)}
110+
creditsAmount={creditsAmount}
111+
/>
112+
</StripeElements>
113+
</Suspense>
103114
);
104115
}
105116

packages/keychain/vite.config.ts

+53-14
Original file line numberDiff line numberDiff line change
@@ -45,35 +45,74 @@ export default defineConfig(({ mode }) => ({
4545
external: [],
4646
output: {
4747
manualChunks(id) {
48-
// Chunk splitting logic
49-
if (id.includes("node_modules")) {
50-
if (id.includes("react")) {
51-
return "react-vendor";
48+
if (id.includes('node_modules')) {
49+
// React and related packages
50+
if (id.includes('react/') || id.includes('react-dom/')) {
51+
return 'react-core';
5252
}
53-
// Split other large dependencies into separate chunks
54-
return "vendor";
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 (id.includes('wagmi') ||
63+
id.includes('viem') ||
64+
id.includes('starknet') ||
65+
id.includes('@noble/') ||
66+
id.includes('caip') ||
67+
id.includes('@starknet-react') ||
68+
id.includes('@cartridge/account-wasm') ||
69+
id.includes('@cartridge/controller') ||
70+
id.includes('cbor')) {
71+
return 'web3';
72+
}
73+
// UI components and styling
74+
if (id.includes('@cartridge/ui-next') || id.includes('embla-carousel')) {
75+
return 'ui';
76+
}
77+
return 'vendor';
5578
}
5679
},
5780
},
5881
},
5982
target: "esnext",
60-
minify: "esbuild", // esbuild is faster than terser and almost as effective
83+
minify: "esbuild",
6184
sourcemap: mode === "development",
62-
// Reduce chunk size warnings and enable compression reporting
6385
chunkSizeWarningLimit: 1000,
6486
reportCompressedSize: true,
65-
// Optimize build speed and output
6687
cssCodeSplit: true,
6788
modulePreload: {
68-
polyfill: false, // Reduces polyfill size if you don't need older browser support
89+
polyfill: false,
90+
},
91+
timeout: 120000,
92+
assetsInlineLimit: 4096,
93+
commonjsOptions: {
94+
include: [/node_modules/],
95+
extensions: ['.js', '.cjs'],
96+
strictRequires: true,
97+
transformMixedEsModules: true,
6998
},
70-
// Add a longer timeout for builds
71-
timeout: 120000, // 2 minutes
7299
},
73100
optimizeDeps: {
74-
include: ["react", "react-dom"], // Pre-bundle common dependencies
101+
include: [
102+
'react',
103+
'react-dom',
104+
'react-router-dom',
105+
'react-query',
106+
'@cartridge/ui-next'
107+
],
108+
exclude: ['@cartridge/account-wasm'],
109+
esbuildOptions: {
110+
target: 'esnext',
111+
supported: {
112+
'top-level-await': true
113+
},
114+
}
75115
},
76-
// Add this to ensure polyfills are properly included
77116
define: {
78117
global: "globalThis",
79118
},

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

+35-10
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,33 @@ 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((...args: Parameters<T>) => {
34+
if (timeoutRef.current) {
35+
clearTimeout(timeoutRef.current);
36+
}
37+
timeoutRef.current = setTimeout(() => {
38+
callback(...args);
39+
}, delay);
40+
}, [callback, delay]);
41+
}
42+
2043
export const Recipient = ({
2144
to,
2245
setTo,
@@ -61,15 +84,10 @@ export const Recipient = ({
6184
return getIcon(selectedWallet);
6285
}, [selectedWallet, getIcon]);
6386

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

7492
const handleClear = useCallback(() => {
7593
setValue("");
@@ -119,6 +137,13 @@ export const Recipient = ({
119137
setWarning,
120138
]);
121139

140+
useEffect(() => {
141+
handleDebounce(value);
142+
return () => {
143+
// Cleanup handled by useDebounce hook
144+
};
145+
}, [value, handleDebounce]);
146+
122147
return (
123148
<div className="flex flex-col gap-y-px">
124149
<div className="flex items-center justify-between">

0 commit comments

Comments
 (0)