diff --git a/package-lock.json b/package-lock.json index 6944e5ad10..809fbe6a81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "@hookform/resolvers": "^3.9.1", "@monaco-editor/react": "^4.6.0", "@netlify/edge-functions": "^2.11.1", - "@types/lodash": "^4.17.13", "@types/pako": "^2.0.3", "@uiw/react-json-view": "^2.0.0-alpha.30", "axios": "^1.9.0", @@ -33,7 +32,6 @@ "js-cookie": "^3.0.5", "js-yaml": "^4.1.0", "jszip": "^3.10.1", - "lodash": "^4.17.21", "mermaid": "^11.8.1", "monaco-editor-textmate": "^4.0.0", "monaco-textmate": "^3.0.1", @@ -42,6 +40,7 @@ "pako": "^2.1.0", "pdfjs-dist": "^5.4.394", "psl": "^1.15.0", + "radash": "^12.1.1", "randomatic": "^3.1.1", "react": "^18.3.1", "react-apexcharts": "^1.7.0", @@ -103,6 +102,7 @@ "@typescript-eslint/eslint-plugin": "^8.21.0", "@typescript-eslint/parser": "^8.18.2", "@vitejs/plugin-react": "^4.3.4", + "@vitejs/plugin-react-swc": "^4.2.2", "@vitest/ui": "^3.2.4", "autoprefixer": "^10.4.20", "babel-eslint": "^10.1.0", @@ -134,6 +134,7 @@ "openai": "^4.77.0", "postcss": "^8.4.49", "rollup": "^4.34.6", + "rollup-plugin-visualizer": "^6.0.5", "semantic-release-slack-bot": "^4.0.2", "storybook": "^8.6.3", "tailwind-config-viewer": "^2.0.4", @@ -5430,6 +5431,232 @@ "@svgr/core": "*" } }, + "node_modules/@swc/core": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.3.tgz", + "integrity": "sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.25" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.3", + "@swc/core-darwin-x64": "1.15.3", + "@swc/core-linux-arm-gnueabihf": "1.15.3", + "@swc/core-linux-arm64-gnu": "1.15.3", + "@swc/core-linux-arm64-musl": "1.15.3", + "@swc/core-linux-x64-gnu": "1.15.3", + "@swc/core-linux-x64-musl": "1.15.3", + "@swc/core-win32-arm64-msvc": "1.15.3", + "@swc/core-win32-ia32-msvc": "1.15.3", + "@swc/core-win32-x64-msvc": "1.15.3" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.3.tgz", + "integrity": "sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.3.tgz", + "integrity": "sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.3.tgz", + "integrity": "sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.3.tgz", + "integrity": "sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.3.tgz", + "integrity": "sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.3.tgz", + "integrity": "sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.3.tgz", + "integrity": "sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.3.tgz", + "integrity": "sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.3.tgz", + "integrity": "sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.3.tgz", + "integrity": "sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -5994,12 +6221,6 @@ "license": "MIT", "peer": true }, - "node_modules/@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", - "license": "MIT" - }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -6914,6 +7135,30 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.2.2.tgz", + "integrity": "sha512-x+rE6tsxq/gxrEJN3Nv3dIV60lFflPj94c90b+NNo6n1QV1QQUTLoL0MpaOVasUZ0zqVBn7ead1B5ecx1JAGfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.47", + "@swc/core": "^1.13.5" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6 || ^7" + } + }, + "node_modules/@vitejs/plugin-react-swc/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", + "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitest/expect": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", @@ -14836,6 +15081,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, "license": "MIT" }, "node_modules/lodash-es": { @@ -20652,6 +20898,15 @@ ], "license": "MIT" }, + "node_modules/radash": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/radash/-/radash-12.1.1.tgz", + "integrity": "sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA==", + "license": "MIT", + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -22015,6 +22270,47 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-visualizer": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.5.tgz", + "integrity": "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "open": "^8.0.0", + "picomatch": "^4.0.2", + "source-map": "^0.7.4", + "yargs": "^17.5.1" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "rolldown": "1.x || ^1.0.0-beta", + "rollup": "2.x || 3.x || 4.x" + }, + "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.46.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", diff --git a/package.json b/package.json index c94cf1a6fe..69966a6870 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "version": "2.239.1", "scripts": { "build": "node scripts/validateRuleConsistency.mjs && vite build", + "build:analyze": "ANALYZE=true npm run build", "build-storybook": "storybook build", "build:prod": "node scripts/fetchTemplates && node scripts/verifyTourStepIdsUniqueness.mjs && node scripts/validateRuleConsistency.mjs && NODE_ENV=production npm run build", "fetch-templates": "node scripts/fetchTemplates", @@ -79,7 +80,6 @@ "@hookform/resolvers": "^3.9.1", "@monaco-editor/react": "^4.6.0", "@netlify/edge-functions": "^2.11.1", - "@types/lodash": "^4.17.13", "@types/pako": "^2.0.3", "@uiw/react-json-view": "^2.0.0-alpha.30", "axios": "^1.9.0", @@ -93,7 +93,6 @@ "js-cookie": "^3.0.5", "js-yaml": "^4.1.0", "jszip": "^3.10.1", - "lodash": "^4.17.21", "mermaid": "^11.8.1", "monaco-editor-textmate": "^4.0.0", "monaco-textmate": "^3.0.1", @@ -102,6 +101,7 @@ "pako": "^2.1.0", "pdfjs-dist": "^5.4.394", "psl": "^1.15.0", + "radash": "^12.1.1", "randomatic": "^3.1.1", "react": "^18.3.1", "react-apexcharts": "^1.7.0", @@ -163,6 +163,7 @@ "@typescript-eslint/eslint-plugin": "^8.21.0", "@typescript-eslint/parser": "^8.18.2", "@vitejs/plugin-react": "^4.3.4", + "@vitejs/plugin-react-swc": "^4.2.2", "@vitest/ui": "^3.2.4", "autoprefixer": "^10.4.20", "babel-eslint": "^10.1.0", @@ -194,6 +195,7 @@ "openai": "^4.77.0", "postcss": "^8.4.49", "rollup": "^4.34.6", + "rollup-plugin-visualizer": "^6.0.5", "semantic-release-slack-bot": "^4.0.2", "storybook": "^8.6.3", "tailwind-config-viewer": "^2.0.4", @@ -231,4 +233,4 @@ "rimraf": "^4.0.0", "micromatch": "^4.0.8" } -} +} \ No newline at end of file diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index 84f8858b9f..ba723decf1 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -31,4 +31,5 @@ export { RadioButton } from "@components/atoms/radioButton"; export { DesignedForDesktopBanner } from "@components/atoms/designedForDesktopTopBanner"; export { CodeFixMessage } from "@components/atoms/codeFixMessage"; export { DeleteFileConfirmation } from "@components/atoms/deleteFileConfirmation"; +export { PageLoader } from "@components/atoms/pageLoader"; export { StatusIndicator } from "@components/atoms/statusIndicator"; diff --git a/src/components/atoms/pageLoader.tsx b/src/components/atoms/pageLoader.tsx new file mode 100644 index 0000000000..c867eb0b57 --- /dev/null +++ b/src/components/atoms/pageLoader.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +export const PageLoader = () => ( +
+
+
+); diff --git a/src/components/molecules/copyButton.tsx b/src/components/molecules/copyButton.tsx index 89ae343d63..8545bd0a98 100644 --- a/src/components/molecules/copyButton.tsx +++ b/src/components/molecules/copyButton.tsx @@ -1,6 +1,6 @@ import React, { forwardRef, useImperativeHandle, useRef } from "react"; -import { debounce } from "lodash"; +import { debounce } from "radash"; import { useTranslation } from "react-i18next"; import { useToastStore } from "@src/store"; @@ -53,14 +53,14 @@ export const CopyButton = forwardRef< const addToast = useToastStore((state) => state.addToast); const copyTextToClipboardRef = useRef( - debounce(async (text: string) => { + debounce({ delay: 300 }, async (text: string) => { const { isError, message } = await copyToClipboard(text); addToast({ message: successMessage && !isError ? successMessage : message, type: isError ? "error" : "success", }); - }, 300) + }) ); const copyTextToClipboard = copyTextToClipboardRef.current; diff --git a/src/components/molecules/popover/popoverListContent.tsx b/src/components/molecules/popover/popoverListContent.tsx index c478b27a4f..1cc81fa171 100644 --- a/src/components/molecules/popover/popoverListContent.tsx +++ b/src/components/molecules/popover/popoverListContent.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; -import { debounce } from "lodash"; +import { debounce } from "radash"; import { PopoverContentBase } from "./popoverContentBase"; import { usePopoverListContext } from "@contexts/usePopover"; @@ -76,7 +76,7 @@ export const PopoverListContent = React.forwardRef< const debouncedFilter = useMemo( () => - debounce((filterTerm: string) => { + debounce({ delay: searchByTermDebounceTime }, (filterTerm: string) => { const filteredItems = items.filter((item) => { if (typeof item.label === "string") { return item.label.toLowerCase().includes(filterTerm); @@ -84,16 +84,10 @@ export const PopoverListContent = React.forwardRef< return item.id.toLowerCase().includes(filterTerm); }); setPopoverItems(filteredItems); - }, searchByTermDebounceTime), + }), [items] ); - useEffect(() => { - return () => { - debouncedFilter.cancel(); - }; - }, [debouncedFilter]); - const filterItemsBySearchTerm = (event: React.ChangeEvent) => { const filterTerm = event.target.value.toLowerCase(); setSearchTerm(filterTerm); diff --git a/src/components/organisms/deployments/sessions/table/table.tsx b/src/components/organisms/deployments/sessions/table/table.tsx index 2c28f58df3..3b8b81cac9 100644 --- a/src/components/organisms/deployments/sessions/table/table.tsx +++ b/src/components/organisms/deployments/sessions/table/table.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from "react"; -import { debounce, isEqual } from "lodash"; +import { debounce, isEqual } from "radash"; import { useTranslation } from "react-i18next"; import { Outlet, useParams, useSearchParams } from "react-router-dom"; import { ListOnItemsRenderedProps } from "react-window"; @@ -76,7 +76,6 @@ export const SessionsTable = () => { const firstTimeLoadingRef = useRef(true); const refreshDataRef = useRef<(forceRefresh?: boolean) => Promise>(); const fetchSessionsRef = useRef<(nextPageToken?: string, forceRefresh?: boolean) => Promise>(); - const debouncedFetchSessionsRef = useRef>>(); const isCompactMode = leftSideWidth < 25; const hideSourceColumn = leftSideWidth < 35; const hideActionsColumn = leftSideWidth < 27; @@ -179,7 +178,7 @@ export const SessionsTable = () => { return fetchedDeployments; } - if (isEqual(sessionsCountByState, sessionStats)) return; + if (isEqual(sessionsCountByState, sessionStats.sessionStats)) return; setSessionStats({ sessionStats: sessionsCountByState, totalDeployments, @@ -255,8 +254,7 @@ export const SessionsTable = () => { [projectId, deploymentId, urlSessionStateFilter, addToast, tErrors, lastSeenSession, navigateWithSettings] ); - const debouncedFetchSessions = useMemo(() => debounce(fetchSessions, 100), [fetchSessions]); - debouncedFetchSessionsRef.current = debouncedFetchSessions; + const debouncedFetchSessions = useMemo(() => debounce({ delay: 100 }, fetchSessions), [fetchSessions]); const refreshData = useCallback( async (forceRefresh = false) => { @@ -298,10 +296,6 @@ export const SessionsTable = () => { }; loadData(); - - return () => { - debouncedFetchSessionsRef.current?.cancel(); - }; }, [deployments]); const closeSessionLog = useCallback(() => { diff --git a/src/components/organisms/editorTabs.tsx b/src/components/organisms/editorTabs.tsx index 3185a9b774..c897d6e57a 100644 --- a/src/components/organisms/editorTabs.tsx +++ b/src/components/organisms/editorTabs.tsx @@ -2,8 +2,8 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" import Editor, { Monaco } from "@monaco-editor/react"; import dayjs from "dayjs"; -import { debounce, last } from "lodash"; import * as monaco from "monaco-editor"; +import { debounce, last, throttle } from "radash"; import { useTranslation } from "react-i18next"; import Markdown from "react-markdown"; import { Document, Page, pdfjs } from "react-pdf"; @@ -457,21 +457,17 @@ export const EditorTabs = () => { // eslint-disable-next-line react-hooks/exhaustive-deps const debouncedManualSave = useCallback( - debounce( - () => { - const currentContent = editorRef.current?.getValue(); - if (currentContent !== undefined) { - updateContent(currentContent); - } - }, - 1500, - { leading: true, trailing: false } - ), + throttle({ interval: 1500 }, () => { + const currentContent = editorRef.current?.getValue(); + if (currentContent !== undefined) { + updateContent(currentContent); + } + }), [projectId, activeEditorFileName] ); // eslint-disable-next-line react-hooks/exhaustive-deps - const debouncedAutosave = useCallback(debounce(updateContent, 1500), [projectId, activeEditorFileName]); + const debouncedAutosave = useCallback(debounce({ delay: 1500 }, updateContent), [projectId, activeEditorFileName]); const saveFileWithContent = async (fileName: string, content: string): Promise => { if (!projectId) { diff --git a/src/components/organisms/files/fileTree.tsx b/src/components/organisms/files/fileTree.tsx index feab53d742..17fb66e74d 100644 --- a/src/components/organisms/files/fileTree.tsx +++ b/src/components/organisms/files/fileTree.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef, useState } from "react"; -import { debounce } from "lodash"; +import { debounce } from "radash"; import { Tree, TreeApi } from "react-arborist"; import { useTranslation } from "react-i18next"; @@ -40,18 +40,11 @@ export const FileTree = ({ const [treeHeight, setTreeHeight] = useState(600); const debouncedSetSearchTerm = useRef( - debounce((value: string) => { + debounce({ delay: fileTreeTiming.SEARCH_DEBOUNCE_MS }, (value: string) => { setSearchTerm(value); - }, fileTreeTiming.SEARCH_DEBOUNCE_MS) + }) ).current; - useEffect(() => { - return () => { - debouncedSetSearchTerm.cancel(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - useEffect(() => { const updateHeight = () => { if (containerRef.current) { diff --git a/src/components/organisms/settings/organization/settings.tsx b/src/components/organisms/settings/organization/settings.tsx index f91c04c5c3..e8701cb0c7 100644 --- a/src/components/organisms/settings/organization/settings.tsx +++ b/src/components/organisms/settings/organization/settings.tsx @@ -1,7 +1,6 @@ import React, { useMemo, useState } from "react"; -import debounce from "lodash/debounce"; -import omit from "lodash/omit"; +import { debounce, omit } from "radash"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; @@ -55,7 +54,7 @@ export const OrganizationSettings = () => { } setNameError(""); setOrganizationDisplayName(displayName); - const { error } = await updateOrganization({ ...omit(organization, "currentMember"), displayName }, [ + const { error } = await updateOrganization({ ...omit(organization!, ["currentMember"]), displayName }, [ "display_name", ]); if (error) { @@ -70,7 +69,7 @@ export const OrganizationSettings = () => { setDisplaySuccess(false); }, 3000); }; - const debouncedRename = debounce(renameOrganization, 2000); + const debouncedRename = debounce({ delay: 2000 }, renameOrganization); if (!organization) { return null; @@ -79,7 +78,7 @@ export const OrganizationSettings = () => { const onDelete = async () => { const deletingCurrentOrganization = organization.id === currentOrganization?.id; - const { error } = await deleteOrganization(omit(organization, "currentMember")); + const { error } = await deleteOrganization(omit(organization, ["currentMember"])); closeModal(ModalName.deleteOrganization); if (error) { diff --git a/src/components/organisms/settings/user/organizations/table.tsx b/src/components/organisms/settings/user/organizations/table.tsx index d8b675ec85..5515823d43 100644 --- a/src/components/organisms/settings/user/organizations/table.tsx +++ b/src/components/organisms/settings/user/organizations/table.tsx @@ -1,6 +1,6 @@ import React from "react"; -import omit from "lodash/omit"; +import { omit } from "radash"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; @@ -33,7 +33,7 @@ export const UserOrganizationsTable = () => { const onDelete = async (organization: EnrichedOrganization) => { const deletingCurrentOrganization = organization.id === currentOrganization?.id; - const { error } = await deleteOrganization(omit(organization, "currentMember")); + const { error } = await deleteOrganization(omit(organization, ["currentMember"])); closeModal(ModalName.deleteOrganization); if (error) { addToast({ diff --git a/src/components/organisms/settings/user/profile.tsx b/src/components/organisms/settings/user/profile.tsx index a4341ce98c..0d12dca3f8 100644 --- a/src/components/organisms/settings/user/profile.tsx +++ b/src/components/organisms/settings/user/profile.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; -import debounce from "lodash/debounce"; +import { debounce } from "radash"; import { useTranslation } from "react-i18next"; import { version } from "@constants"; @@ -61,7 +61,7 @@ export const Profile = () => { setDisplaySuccess(false); }, 3000); }; - const debouncedRename = debounce(renameUser, 2000); + const debouncedRename = debounce({ delay: 2000 }, renameUser); return (
diff --git a/src/components/organisms/topbar/project/buttons.tsx b/src/components/organisms/topbar/project/buttons.tsx index 107e06a9bb..659c6f9785 100644 --- a/src/components/organisms/topbar/project/buttons.tsx +++ b/src/components/organisms/topbar/project/buttons.tsx @@ -1,6 +1,6 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useMemo, useState } from "react"; -import { debounce } from "lodash"; +import { throttle } from "radash"; import { useTranslation } from "react-i18next"; import { useNavigate, useParams } from "react-router-dom"; @@ -272,15 +272,8 @@ export const ProjectTopbarButtons = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [projectId]); - const debouncedBuild = useMemo(() => debounce(build, 1000, { leading: true, trailing: false }), [build]); - const debouncedDeploy = useMemo(() => debounce(deploy, 1000, { leading: true, trailing: false }), [deploy]); - - useEffect(() => { - return () => { - debouncedBuild.cancel(); - debouncedDeploy.cancel(); - }; - }, [debouncedBuild, debouncedDeploy]); + const throttledBuild = useMemo(() => throttle({ interval: 1000 }, build), [build]); + const throttledDeploy = useMemo(() => throttle({ interval: 1000 }, deploy), [deploy]); const isDeployAndBuildDisabled = Object.values(actionInProcess).some((value) => value); @@ -292,7 +285,7 @@ export const ProjectTopbarButtons = () => { ariaLabel={t("topbar.buttons.ariaBuildProject")} className="group h-8 whitespace-nowrap px-3.5 text-white maxScreenWidth-1600:px-2" disabled={isDeployAndBuildDisabled} - onClick={debouncedBuild} + onClick={throttledBuild} title={isValid ? t("topbar.buttons.build") : projectErrors} variant="outline" > @@ -321,7 +314,7 @@ export const ProjectTopbarButtons = () => { className="group h-8 items-center whitespace-nowrap px-3.5 text-white maxScreenWidth-1600:px-2" disabled={isDeployAndBuildDisabled} id={tourStepsHTMLIds.deployButton} - onClick={debouncedDeploy} + onClick={throttledDeploy} title={isValid ? t("topbar.buttons.deploy") : projectErrors} variant="outline" > diff --git a/src/components/organisms/topbar/project/manualRun/manualRunButtons.tsx b/src/components/organisms/topbar/project/manualRun/manualRunButtons.tsx index 92a87901fe..d775a43afc 100644 --- a/src/components/organisms/topbar/project/manualRun/manualRunButtons.tsx +++ b/src/components/organisms/topbar/project/manualRun/manualRunButtons.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect } from "react"; -import { isEqual } from "lodash"; +import { isEqual } from "radash"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; diff --git a/src/hooks/useSort.tsx b/src/hooks/useSort.tsx index 9669d59d41..dbca7938b1 100644 --- a/src/hooks/useSort.tsx +++ b/src/hooks/useSort.tsx @@ -1,6 +1,6 @@ import { useCallback, useMemo, useState } from "react"; -import { orderBy } from "lodash"; +import { sort } from "radash"; import { initialSortConfig } from "@constants"; import { SortDirectionVariant } from "@enums/components"; @@ -16,21 +16,18 @@ export const useSort = (items: T[], initialSortKey?: keyof T) => { return [...items]; } - return orderBy( - items, - [ - (item) => { - const value = sortConfig.key ? item[sortConfig.key] : undefined; - - if (value === undefined || value === null) { - return sortConfig.direction === SortDirectionVariant.ASC ? Number.MAX_VALUE : Number.MIN_VALUE; - } - - return typeof value === "string" ? value.toLowerCase() : value; - }, - ], - [sortConfig.direction] - ); + const getValue = (item: T) => { + const value = sortConfig.key ? item[sortConfig.key] : undefined; + + if (value === undefined || value === null) { + return sortConfig.direction === SortDirectionVariant.ASC ? Number.MAX_VALUE : Number.MIN_VALUE; + } + + return typeof value === "string" ? value.toLowerCase() : value; + }; + + const sorted = sort(items, getValue as (item: T) => number); + return sortConfig.direction === SortDirectionVariant.DESC ? sorted.reverse() : sorted; }, [items, sortConfig]); const requestSort = useCallback((key: keyof T) => { diff --git a/src/routes.tsx b/src/routes.tsx index 42e1fca929..cf2ddf2725 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -1,62 +1,154 @@ -import React from "react"; +import React, { Suspense, lazy } from "react"; import { Navigate } from "react-router-dom"; import { featureFlags } from "./constants"; import { MemberRole } from "@enums"; -import { EventsList } from "@shared-components"; import { legacyRoutes } from "@src/routes.legacy"; -import { - DeploymentsTable, - EventViewer, - ProtectedRoute, - SessionsTable, - GlobalConnectionsTable, -} from "@components/organisms"; -import { AddConnection, EditConnection } from "@components/organisms/configuration/connections"; -import { TemplatesCatalog } from "@components/organisms/dashboard/templates"; -import { SessionViewer } from "@components/organisms/deployments"; -import { ActivityList, SessionOutputs } from "@components/organisms/deployments/sessions/tabs"; -import { - AddOrganization, - OrganizationMembersTable, - OrganizationSettings, - SwitchOrganization, -} from "@components/organisms/settings/organization"; -import { OrganizationBilling } from "@components/organisms/settings/organization/billing"; -import { ClientConfiguration, Profile, UserOrganizationsTable } from "@components/organisms/settings/user"; -import { WelcomePage } from "@components/organisms/welcome"; -import { - AiLandingPage, - ChatPage, - CustomError, - Dashboard, - Internal404, - Intro, - Project, - TemplateLanding, -} from "@components/pages"; +import { PageLoader } from "@components/atoms"; +import { ProtectedRoute } from "@components/organisms"; import { AppLayout, EventsLayout, GlobalConnectionsLayout } from "@components/templates"; import { ProjectWrapper } from "@components/templates/projectWrapper"; import { SettingsLayout } from "@components/templates/settingsLayout"; -const sessionViewRoutes = [ - { index: true, element: }, - { path: "executionflow", element: }, - { path: "settings/*", element: }, - { path: "executionflow/settings/*", element: }, -]; +const LazyDashboard = lazy(() => import("@components/pages/dashboard").then((m) => ({ default: m.Dashboard }))); +const LazyProject = lazy(() => import("@components/pages/project").then((m) => ({ default: m.Project }))); +const LazyAiLandingPage = lazy(() => + import("@components/pages/aiLandingPage").then((m) => ({ default: m.AiLandingPage })) +); +const LazyChatPage = lazy(() => import("@components/pages/chat").then((m) => ({ default: m.ChatPage }))); +const LazyIntro = lazy(() => import("@components/pages/intro").then((m) => ({ default: m.Intro }))); +const LazyTemplateLanding = lazy(() => + import("@components/pages/templateLanding").then((m) => ({ default: m.TemplateLanding })) +); +const LazyCustomError = lazy(() => import("@components/pages/customError").then((m) => ({ default: m.CustomError }))); +const LazyInternal404 = lazy(() => import("@components/pages/internal404").then((m) => ({ default: m.Internal404 }))); + +const LazyDeploymentsTable = lazy(() => + import("@components/organisms/deployments/table").then((m) => ({ default: m.DeploymentsTable })) +); +const LazySessionsTable = lazy(() => + import("@components/organisms/deployments/sessions/table/table").then((m) => ({ default: m.SessionsTable })) +); +const LazySessionViewer = lazy(() => + import("@components/organisms/deployments/sessions/viewer").then((m) => ({ default: m.SessionViewer })) +); +const LazyEventViewer = lazy(() => + import("@components/organisms/events/viewer").then((m) => ({ default: m.EventViewer })) +); +const LazyEventsList = lazy(() => + import("@components/organisms/shared/events").then((m) => ({ default: m.EventsList })) +); +const GlobalConnectionsTable = lazy(() => + import("@components/organisms/globalConnections/table").then((m) => ({ + default: m.GlobalConnectionsTable, + })) +); + +const LazyProjectConfigurationDrawer = lazy(() => + import("@components/organisms/configuration/configrationDrawer").then((m) => ({ + default: m.ProjectConfigurationDrawer, + })) +); +const LazyProjectConfigurationView = lazy(() => + import("@components/organisms/configuration/configurationView").then((m) => ({ + default: m.ConfigurationView, + })) +); +const LazyAddConnection = lazy(() => + import("@components/organisms/configuration/connections/add").then((m) => ({ default: m.AddConnection })) +); +const LazyEditConnection = lazy(() => + import("@components/organisms/configuration/connections/edit").then((m) => ({ + default: m.EditConnection, + })) +); +const LazyAddTrigger = lazy(() => + import("@components/organisms/configuration/triggers/add").then((m) => ({ default: m.AddTrigger })) +); +const LazyEditTrigger = lazy(() => + import("@components/organisms/configuration/triggers/edit").then((m) => ({ default: m.EditTrigger })) +); +const LazyAddVariable = lazy(() => + import("@components/organisms/configuration/variables/add").then((m) => ({ default: m.AddVariable })) +); +const LazyEditVariable = lazy(() => + import("@components/organisms/configuration/variables/edit").then((m) => ({ default: m.EditVariable })) +); + +const LazyTemplatesCatalog = lazy(() => + import("@components/organisms/dashboard/templates/catalog").then((m) => ({ default: m.TemplatesCatalog })) +); +const LazyWelcomePage = lazy(() => import("@components/organisms/welcome").then((m) => ({ default: m.WelcomePage }))); -const sessionRouteConfig = [ +const LazyActivityList = lazy(() => + import("@components/organisms/deployments/sessions/tabs/activities").then((m) => ({ default: m.ActivityList })) +); +const LazySessionOutputs = lazy(() => + import("@components/organisms/deployments/sessions/tabs/outputs").then((m) => ({ + default: m.SessionOutputs, + })) +); + +const LazyProfile = lazy(() => + import("@components/organisms/settings/user/profile").then((m) => ({ default: m.Profile })) +); +const LazyClientConfiguration = lazy(() => + import("@components/organisms/settings/user/clientConfiguration").then((m) => ({ + default: m.ClientConfiguration, + })) +); +const LazyUserOrganizationsTable = lazy(() => + import("@components/organisms/settings/user/organizations/table").then((m) => ({ + default: m.UserOrganizationsTable, + })) +); +const LazyAddOrganization = lazy(() => + import("@components/organisms/settings/organization/add").then((m) => ({ + default: m.AddOrganization, + })) +); +const LazyOrganizationSettings = lazy(() => + import("@components/organisms/settings/organization/settings").then((m) => ({ default: m.OrganizationSettings })) +); +const LazyOrganizationMembersTable = lazy(() => + import("@components/organisms/settings/organization/members/table").then((m) => ({ + default: m.OrganizationMembersTable, + })) +); +const LazySwitchOrganization = lazy(() => + import("@components/organisms/settings/organization/switchOrganization").then((m) => ({ + default: m.SwitchOrganization, + })) +); +const LazyOrganizationBilling = lazy(() => + import("@components/organisms/settings/organization/billing/organizationBilling").then((m) => ({ + default: m.OrganizationBilling, + })) +); + +const withSuspense = (Component: React.ReactNode) => }>{Component}; + +const settingsRouteConfig = [ + { index: true, element: withSuspense() }, + { path: "connections/new", element: withSuspense() }, + { path: "connections", element: withSuspense() }, { - path: ":sessionId", - element: , - children: sessionViewRoutes, + path: "connections/:id/edit", + element: withSuspense(), }, + { path: "variables", element: withSuspense() }, + { path: "variables/new", element: withSuspense() }, + { path: "variables/:name/edit", element: withSuspense() }, + { path: "triggers", element: withSuspense() }, + { path: "triggers/new", element: withSuspense() }, + { path: "triggers/:id/edit", element: withSuspense() }, ]; -const noProjectHome = featureFlags.displayChatbot ? : ; +const noProjectHome = featureFlags.displayChatbot + ? withSuspense() + : withSuspense(); const globalConnectionsRoutes = featureFlags.displayGlobalConnections ? [ @@ -65,14 +157,19 @@ const globalConnectionsRoutes = featureFlags.displayGlobalConnections children: [ { path: "connections", - element: , + element: withSuspense(), children: [ - { path: "new", element: }, + { + path: "new", + element: withSuspense( + + ), + }, { path: ":id", element: null }, { path: ":id/edit", - element: ( - + element: withSuspense( + ), }, ], @@ -88,13 +185,13 @@ export const mainRoutes = [ path: "/", element: , children: [ - { index: true, element: }, + { index: true, element: withSuspense() }, { path: "ai", element: noProjectHome }, { path: "welcome", element: noProjectHome }, - { path: "intro", element: }, - { path: "templates-library", element: }, - { path: "404", element: }, - { path: "chat", element: }, + { path: "intro", element: withSuspense() }, + { path: "templates-library", element: withSuspense() }, + { path: "404", element: withSuspense() }, + { path: "chat", element: withSuspense() }, { path: "*", element: }, ], }, @@ -102,7 +199,7 @@ export const mainRoutes = [ path: "/template", element: , children: [ - { index: true, element: }, + { index: true, element: withSuspense() }, { path: "*", element: }, ], }, @@ -115,8 +212,17 @@ export const mainRoutes = [ children: [ { index: true, element: }, { path: "code", element: }, - { path: "explorer", element: }, - { path: "explorer/settings/*", element: }, + { + path: "explorer", + element: withSuspense(), + children: [ + { + path: "settings", + element: withSuspense(), + children: settingsRouteConfig, + }, + ], + }, ], }, ], @@ -128,12 +234,52 @@ export const mainRoutes = [ { element: , children: [ - { index: true, element: }, - { path: "settings/*", element: }, + { index: true, element: withSuspense() }, + { + path: "settings", + element: withSuspense( + <> + + + + ), + children: settingsRouteConfig, + }, + + { + path: ":deploymentId/sessions/settings", + element: withSuspense( + <> + + + + ), + children: settingsRouteConfig, + }, + { path: ":deploymentId/sessions", - element: , - children: sessionRouteConfig, + element: withSuspense(), + children: [ + { + path: ":sessionId", + element: withSuspense(), + children: [ + { index: true, element: withSuspense() }, + { path: "executionflow", element: withSuspense() }, + { + path: "settings", + element: withSuspense( + <> + + + + ), + children: settingsRouteConfig, + }, + ], + }, + ], }, { path: "*", element: }, ], @@ -147,11 +293,22 @@ export const mainRoutes = [ { element: , children: [ - { index: true, element: }, - { path: "settings/*", element: }, + { index: true, element: withSuspense() }, + { path: "settings/*", element: withSuspense() }, { - element: , - children: sessionRouteConfig, + element: withSuspense(), + children: [ + { + path: ":sessionId", + element: withSuspense(), + children: [ + { index: true, element: withSuspense() }, + { path: "executionflow", element: withSuspense() }, + { path: "settings/*", element: withSuspense() }, + { path: "executionflow/settings/*", element: withSuspense() }, + ], + }, + ], }, { path: "*", element: }, ], @@ -166,10 +323,10 @@ export const mainRoutes = [ ), children: [ - { index: true, element: }, - { path: "client-configuration", element: }, - { path: "organizations", element: }, - { path: "add-organization", element: }, + { index: true, element: withSuspense() }, + { path: "client-configuration", element: withSuspense() }, + { path: "organizations", element: withSuspense() }, + { path: "add-organization", element: withSuspense() }, { path: "*", element: }, ], }, @@ -185,7 +342,7 @@ export const mainRoutes = [ index: true, element: ( - + {withSuspense()} ), }, @@ -193,11 +350,11 @@ export const mainRoutes = [ path: "billing", element: ( - + {withSuspense()} ), }, - { path: "members", element: }, + { path: "members", element: withSuspense() }, { path: "*", element: }, ], }, @@ -206,8 +363,8 @@ export const mainRoutes = [ children: [ { path: "events", - element: , - children: [{ path: ":eventId", element: }], + element: withSuspense(), + children: [{ path: ":eventId", element: withSuspense() }], }, { path: "*", element: }, ], @@ -216,12 +373,12 @@ export const mainRoutes = [ { path: "switch-organization/:organizationId", element: , - children: [{ index: true, element: }], + children: [{ index: true, element: withSuspense() }], }, { path: "error", element: , - children: [{ index: true, element: }], + children: [{ index: true, element: withSuspense() }], }, ...legacyRoutes, { path: "*", element: }, diff --git a/src/services/sessions.service.ts b/src/services/sessions.service.ts index 882cfef74d..5da9cc6e41 100644 --- a/src/services/sessions.service.ts +++ b/src/services/sessions.service.ts @@ -1,5 +1,5 @@ import { t } from "i18next"; -import { omit } from "lodash"; +import { omit } from "radash"; import { Session as ProtoSession, @@ -177,7 +177,7 @@ export class SessionsService { projectId: string ): Promise> { try { - const sessionToStart = { ...omit(startSessionArgs, "jsonInputs"), projectId }; + const sessionToStart = { ...omit(startSessionArgs, ["jsonInputs"]), projectId }; const sessionAsStartRequest = { session: sessionToStart, jsonObjectInput: startSessionArgs.jsonInputs, diff --git a/src/store/cache/useCacheStore.ts b/src/store/cache/useCacheStore.ts index b1cf97db7f..3a264b1aa1 100644 --- a/src/store/cache/useCacheStore.ts +++ b/src/store/cache/useCacheStore.ts @@ -1,5 +1,5 @@ import { t } from "i18next"; -import isEqual from "lodash/isEqual"; +import { isEqual } from "radash"; import { createSelector } from "reselect"; import { StateCreator, create } from "zustand"; diff --git a/src/store/useProjectStore.ts b/src/store/useProjectStore.ts index eb63024956..c4f622d96e 100644 --- a/src/store/useProjectStore.ts +++ b/src/store/useProjectStore.ts @@ -1,6 +1,6 @@ import { t } from "i18next"; import { load } from "js-yaml"; -import isEqual from "lodash/isEqual"; +import { isEqual } from "radash"; import { StateCreator, create } from "zustand"; import { persist } from "zustand/middleware"; import { immer } from "zustand/middleware/immer"; diff --git a/src/utilities/calculateDeploymentSessionsStats.utils.ts b/src/utilities/calculateDeploymentSessionsStats.utils.ts index 2eeac705a9..e8fc662087 100644 --- a/src/utilities/calculateDeploymentSessionsStats.utils.ts +++ b/src/utilities/calculateDeploymentSessionsStats.utils.ts @@ -1,5 +1,3 @@ -import { cloneDeep } from "lodash"; - import { SessionStateType } from "@src/enums"; import { SessionStatsFilterType } from "@src/types/components"; import { Deployment } from "@src/types/models"; @@ -8,7 +6,7 @@ export const calculateDeploymentSessionsStats = (deployments: Deployment[]): Ses const allSessionStats = deployments.flatMap((deployment) => deployment.sessionStats || []); let totalSessionsCount = 0; - const resetSessionStats = cloneDeep(initialSessionCounts); + const resetSessionStats = structuredClone(initialSessionCounts); const sessionStats = allSessionStats.reduce>((acc, { count, state }) => { if (!state) return acc; diff --git a/src/utilities/convertBuildRuntimesToViewTriggers.utils.ts b/src/utilities/convertBuildRuntimesToViewTriggers.utils.ts index 9e1b5f61fb..c45070bd50 100644 --- a/src/utilities/convertBuildRuntimesToViewTriggers.utils.ts +++ b/src/utilities/convertBuildRuntimesToViewTriggers.utils.ts @@ -1,4 +1,4 @@ -import { uniqBy } from "lodash"; +import { unique } from "radash"; import { namespaces } from "@constants"; import { LoggerService } from "@services"; @@ -16,7 +16,7 @@ const processRuntime = (runtime: BuildInfoRuntimes): Record => .filter(({ location: { path }, symbol: name }) => path === fileName && !name.startsWith("_")) .map(({ symbol: name }) => name); - const uniqueEntrypoints = uniqBy(entrypointsForFile, (func) => func); + const uniqueEntrypoints = unique(entrypointsForFile, (func) => func); result[fileName] = uniqueEntrypoints; }); diff --git a/src/utilities/fetchAndExtractZip.utils.ts b/src/utilities/fetchAndExtractZip.utils.ts index 18494af9d7..63dbd0e3d7 100644 --- a/src/utilities/fetchAndExtractZip.utils.ts +++ b/src/utilities/fetchAndExtractZip.utils.ts @@ -2,7 +2,7 @@ import axios from "axios"; import frontMatter from "front-matter"; import { t } from "i18next"; import JSZip from "jszip"; -import { memoize } from "lodash"; +import { memo } from "radash"; import { DirectoryNode, @@ -16,9 +16,9 @@ import { LoggerService } from "@services/logger.service"; import { namespaces } from "@src/constants"; import { ProcessedRemoteCategory, RemoteTemplateCardWithFiles } from "@src/interfaces/store"; -const isFileNode = memoize((node: FileNode | DirectoryNode): node is FileNode => node?.type === "file"); +const isFileNode = (node: FileNode | DirectoryNode): node is FileNode => node?.type === "file"; -const isDirectoryNode = memoize((node: FileNode | DirectoryNode): node is DirectoryNode => node?.type === "directory"); +const isDirectoryNode = (node: FileNode | DirectoryNode): node is DirectoryNode => node?.type === "directory"; const directoryCache = new Map(); @@ -145,7 +145,7 @@ export const fetchAndUnpackZip = async (remoteTemplatesArchiveUrl: string): Prom } }; -const getFileName = memoize((path: string): string => path.split("/").pop() || path); +const getFileName = memo((path: string): string => path.split("/").pop() || path); const getDirectoryStructure = (fileStructure: FileStructure, targetPath: string): FileStructure | null => { if (!targetPath) return fileStructure; diff --git a/src/utilities/openedEditorFilesState.utils.ts b/src/utilities/openedEditorFilesState.utils.ts index e8182535e6..993741bff1 100644 --- a/src/utilities/openedEditorFilesState.utils.ts +++ b/src/utilities/openedEditorFilesState.utils.ts @@ -1,8 +1,8 @@ -import { map, uniqBy } from "lodash"; +import { unique } from "radash"; export const updateOpenedFilesState = (files: { isActive: boolean; name: string }[], name: string) => { - return uniqBy( - [...map(files, (file) => ({ ...file, isActive: file.name === name })), { isActive: true, name }], - "name" + return unique( + [...files.map((file) => ({ ...file, isActive: file.name === name })), { isActive: true, name }], + (f) => f.name ); }; diff --git a/vite.config.ts b/vite.config.ts index 49d795571e..fd70f8bf39 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,8 @@ -import react from "@vitejs/plugin-react"; +import react from "@vitejs/plugin-react-swc"; import dotenv from "dotenv"; import fs from "fs"; import path from "path"; +import { visualizer } from "rollup-plugin-visualizer"; import { defineConfig } from "vite"; import { ViteEjsPlugin } from "vite-plugin-ejs"; import mkcert from "vite-plugin-mkcert"; @@ -30,11 +31,13 @@ export default defineConfig({ : 8000, }, build: { - sourcemap: true, + sourcemap: process.env.NODE_ENV === "production" ? "hidden" : true, minify: "terser", + chunkSizeWarningLimit: 1000, terserOptions: { compress: { dead_code: true, + drop_console: process.env.NODE_ENV === "production", if_return: true, unused: true, reduce_vars: true, @@ -42,6 +45,30 @@ export default defineConfig({ passes: 2, }, }, + rollupOptions: { + output: { + manualChunks: { + "vendor-react": ["react", "react-dom", "react-router-dom"], + "vendor-ui": [ + "motion", + "swiper", + "react-select", + "@floating-ui/react", + "react-arborist", + "react-complex-tree", + ], + "vendor-editor": ["@monaco-editor/react", "monaco-editor-textmate", "monaco-textmate", "onigasm"], + "vendor-charts": ["apexcharts", "react-apexcharts"], + "vendor-pdf": ["pdfjs-dist", "react-pdf"], + "vendor-mermaid": ["mermaid"], + "vendor-monitoring": ["@sentry/react", "@datadog/browser-rum", "@datadog/browser-rum-react"], + "vendor-utils": ["radash", "dayjs", "zod", "zustand", "immer", "clsx", "tailwind-merge"], + "vendor-forms": ["react-hook-form", "@hookform/resolvers"], + "vendor-i18n": ["i18next", "react-i18next"], + "vendor-markdown": ["react-markdown", "remark-gfm", "remark-github-blockquote-alert"], + }, + }, + }, }, define: { "import.meta.env.VITE_APP_VERSION": JSON.stringify(version), @@ -145,6 +172,16 @@ export default defineConfig({ ], }), reactVirtualized(), + ...(process.env.ANALYZE === "true" + ? [ + visualizer({ + open: true, + filename: "dist/stats.html", + gzipSize: true, + brotliSize: true, + }), + ] + : []), ], resolve: { alias: {