From 7948bca8646e365ab269f31fae2c05967e412598 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 25 Jun 2025 19:21:12 +1000 Subject: [PATCH] build(ui): adopt sonda over rollup-plugin-visualizer to examine bundle Requires a change to tsconfig module/moduleResolution settings. We were on old legacy values anyways so good to update it. --- .gitignore | 3 + invokeai/frontend/web/.prettierignore | 1 + invokeai/frontend/web/package.json | 1 + invokeai/frontend/web/pnpm-lock.yaml | 73 +++++++++++++++++++ .../components/ColorPicker/RgbColorPicker.tsx | 2 +- .../ColorPicker/RgbaColorPicker.tsx | 2 +- .../src/common/components/Picker/Picker.tsx | 58 +++++++-------- invokeai/frontend/web/tsconfig.json | 2 +- invokeai/frontend/web/tsconfig.node.json | 4 +- invokeai/frontend/web/vite.config.mts | 7 +- 10 files changed, 111 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index fba699d5e9..90d01aec13 100644 --- a/.gitignore +++ b/.gitignore @@ -190,3 +190,6 @@ installer/update.bat installer/update.sh installer/InvokeAI-Installer/ .aider* + +# sonda build stat visualizer +.sonda/ diff --git a/invokeai/frontend/web/.prettierignore b/invokeai/frontend/web/.prettierignore index 0f53a0b0a8..fd36f59e47 100644 --- a/invokeai/frontend/web/.prettierignore +++ b/invokeai/frontend/web/.prettierignore @@ -14,3 +14,4 @@ static/ src/theme/css/overlayscrollbars.css src/theme_/css/overlayscrollbars.css pnpm-lock.yaml +.sonda/ diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index 2e71458416..5e5ee79c97 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -151,6 +151,7 @@ "openapi-typescript": "^7.6.1", "prettier": "^3.5.3", "rollup-plugin-visualizer": "^5.14.0", + "sonda": "^0.8.2", "storybook": "^8.6.12", "tsafe": "^1.8.5", "type-fest": "^4.40.0", diff --git a/invokeai/frontend/web/pnpm-lock.yaml b/invokeai/frontend/web/pnpm-lock.yaml index 890b5a9296..03a10a465a 100644 --- a/invokeai/frontend/web/pnpm-lock.yaml +++ b/invokeai/frontend/web/pnpm-lock.yaml @@ -286,6 +286,9 @@ devDependencies: rollup-plugin-visualizer: specifier: ^5.14.0 version: 5.14.0 + sonda: + specifier: ^0.8.2 + version: 0.8.2 storybook: specifier: ^8.6.12 version: 8.6.12(prettier@3.5.3) @@ -3979,6 +3982,13 @@ packages: ieee754: 1.2.1 dev: true + /bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + dependencies: + run-applescript: 7.0.0 + dev: true + /cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -4446,6 +4456,19 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true + /default-browser-id@5.0.0: + resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} + engines: {node: '>=18'} + dev: true + + /default-browser@5.2.1: + resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} + engines: {node: '>=18'} + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.0 + dev: true + /defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} requiresBuild: true @@ -4466,6 +4489,11 @@ packages: engines: {node: '>=8'} dev: true + /define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + dev: true + /define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -5792,6 +5820,12 @@ packages: hasBin: true dev: true + /is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + dev: true + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -5832,6 +5866,14 @@ packages: is-extglob: 2.1.1 dev: true + /is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + dependencies: + is-docker: 3.0.0 + dev: true + /is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -5953,6 +5995,13 @@ packages: is-docker: 2.2.1 dev: true + /is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + dependencies: + is-inside-container: 1.0.0 + dev: true + /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} dev: true @@ -6558,6 +6607,16 @@ packages: mimic-fn: 2.1.0 dev: true + /open@10.1.2: + resolution: {integrity: sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==} + engines: {node: '>=18'} + dependencies: + default-browser: 5.2.1 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + is-wsl: 3.1.0 + dev: true + /open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} @@ -7530,6 +7589,11 @@ packages: '@babel/runtime': 7.27.0 dev: false + /run-applescript@7.0.0: + resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} + engines: {node: '>=18'} + dev: true + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -7735,6 +7799,15 @@ packages: - supports-color dev: false + /sonda@0.8.2: + resolution: {integrity: sha512-OzLlgd4TVjNzhk+Q7NWrdHc4hAU3gchF9u9ZQ4GcQLKk3IX6OTbaf4U7Itq9XssuuCPVVPTl8p0rGAjBHalxsg==} + engines: {node: '>=20.19 || >=22.12'} + hasBin: true + dependencies: + '@ampproject/remapping': 2.3.0 + open: 10.1.2 + dev: true + /source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} diff --git a/invokeai/frontend/web/src/common/components/ColorPicker/RgbColorPicker.tsx b/invokeai/frontend/web/src/common/components/ColorPicker/RgbColorPicker.tsx index 1361039b75..54eadf7467 100644 --- a/invokeai/frontend/web/src/common/components/ColorPicker/RgbColorPicker.tsx +++ b/invokeai/frontend/web/src/common/components/ColorPicker/RgbColorPicker.tsx @@ -4,8 +4,8 @@ import { RGB_COLOR_SWATCHES } from 'common/components/ColorPicker/swatches'; import { rgbColorToString } from 'common/util/colorCodeTransformers'; import type { CSSProperties } from 'react'; import { memo, useCallback } from 'react'; +import type { RgbColor } from 'react-colorful'; import { RgbColorPicker as ColorfulRgbColorPicker } from 'react-colorful'; -import type { RgbColor } from 'react-colorful/dist/types'; import { useTranslation } from 'react-i18next'; type Props = { diff --git a/invokeai/frontend/web/src/common/components/ColorPicker/RgbaColorPicker.tsx b/invokeai/frontend/web/src/common/components/ColorPicker/RgbaColorPicker.tsx index fb3b1da2a0..0d1bc4f4aa 100644 --- a/invokeai/frontend/web/src/common/components/ColorPicker/RgbaColorPicker.tsx +++ b/invokeai/frontend/web/src/common/components/ColorPicker/RgbaColorPicker.tsx @@ -4,8 +4,8 @@ import { RGBA_COLOR_SWATCHES } from 'common/components/ColorPicker/swatches'; import { rgbaColorToString } from 'common/util/colorCodeTransformers'; import type { CSSProperties } from 'react'; import { memo, useCallback } from 'react'; +import type { RgbaColor } from 'react-colorful'; import { RgbaColorPicker as ColorfulRgbaColorPicker } from 'react-colorful'; -import type { RgbaColor } from 'react-colorful/dist/types'; import { useTranslation } from 'react-i18next'; type Props = { diff --git a/invokeai/frontend/web/src/common/components/Picker/Picker.tsx b/invokeai/frontend/web/src/common/components/Picker/Picker.tsx index 14b2476a45..9a8251447e 100644 --- a/invokeai/frontend/web/src/common/components/Picker/Picker.tsx +++ b/invokeai/frontend/web/src/common/components/Picker/Picker.tsx @@ -14,9 +14,8 @@ import { useStore } from '@nanostores/react'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; import { typedMemo } from 'common/util/typedMemo'; import { NO_DRAG_CLASS, NO_WHEEL_CLASS } from 'features/nodes/types/constants'; -import type { AnyStore, ReadableAtom, Task, WritableAtom } from 'nanostores'; +import type { ReadableAtom, WritableAtom } from 'nanostores'; import { atom, computed } from 'nanostores'; -import type { StoreValues } from 'nanostores/computed'; import type { ChangeEvent, MouseEventHandler, PropsWithChildren, RefObject } from 'react'; import React, { createContext, @@ -472,17 +471,6 @@ const useKeyboardNavigation = () => { return keyboardNavProps; }; -const useAtom = (initialValue: T) => { - return useState(() => atom(initialValue))[0]; -}; - -const useComputed = ( - stores: [...OriginStores], - cb: (...values: StoreValues) => Task | Value -) => { - return useState(() => computed(stores, cb))[0]; -}; - const countOptions = (optionsOrGroups: OptionOrGroup[]) => { let count = 0; for (const optionOrGroup of optionsOrGroups) { @@ -515,18 +503,20 @@ export const Picker = typedMemo((props: PickerProps) => { const rootRef = useRef(null); const inputRef = useRef(null); const { $groupStatusMap, $areAllGroupsDisabled, toggleGroup } = useTogglableGroups(optionsOrGroups); - const $activeOptionId = useAtom(getFirstOptionId(optionsOrGroups, getOptionId)); - const $compactView = useAtom(true); - const $optionsOrGroups = useAtom(optionsOrGroups); - const $totalOptionCount = useComputed([$optionsOrGroups], countOptions); - const $filteredOptions = useAtom[]>([]); - const $flattenedFilteredOptions = useComputed([$filteredOptions], flattenOptions); - const $hasOptions = useComputed([$totalOptionCount], (count) => count > 0); - const $filteredOptionsCount = useComputed([$flattenedFilteredOptions], (options) => options.length); - const $hasFilteredOptions = useComputed([$filteredOptionsCount], (count) => count > 0); - const $selectedItem = useAtom(undefined); - const $searchTerm = useAtom(''); - const $selectedItemId = useComputed([$selectedItem], (item) => (item ? getOptionId(item) : undefined)); + const $activeOptionId = useState(() => atom(getFirstOptionId(optionsOrGroups, getOptionId)))[0]; + const $compactView = useState(() => atom(true))[0]; + const $optionsOrGroups = useState(() => atom(optionsOrGroups))[0]; + const $totalOptionCount = useState(() => computed([$optionsOrGroups], countOptions))[0]; + const $filteredOptions = useState(() => atom[]>([]))[0]; + const $flattenedFilteredOptions = useState(() => computed([$filteredOptions], flattenOptions))[0]; + const $hasOptions = useState(() => computed([$totalOptionCount], (count) => count > 0))[0]; + const $filteredOptionsCount = useState(() => computed([$flattenedFilteredOptions], (options) => options.length))[0]; + const $hasFilteredOptions = useState(() => computed([$filteredOptionsCount], (count) => count > 0))[0]; + const $selectedItem = useState(() => atom(undefined))[0]; + const $searchTerm = useState(() => atom(''))[0]; + const $selectedItemId = useState(() => + computed([$selectedItem], (item) => (item ? getOptionId(item) : undefined)) + )[0]; const onSelectById = useCallback( (id: string) => { @@ -809,15 +799,17 @@ SearchInput.displayName = 'SearchInput'; const GroupToggleButtons = typedMemo(() => { const { $optionsOrGroups, $groupStatusMap, $areAllGroupsDisabled } = usePickerContext(); const { t } = useTranslation(); - const $groups = useComputed([$optionsOrGroups], (optionsOrGroups) => { - const _groups: Group[] = []; - for (const optionOrGroup of optionsOrGroups) { - if (isGroup(optionOrGroup)) { - _groups.push(optionOrGroup); + const $groups = useState(() => + computed([$optionsOrGroups], (optionsOrGroups) => { + const _groups: Group[] = []; + for (const optionOrGroup of optionsOrGroups) { + if (isGroup(optionOrGroup)) { + _groups.push(optionOrGroup); + } } - } - return _groups; - }); + return _groups; + }) + )[0]; const groups = useStore($groups); const areAllGroupsDisabled = useStore($areAllGroupsDisabled); diff --git a/invokeai/frontend/web/tsconfig.json b/invokeai/frontend/web/tsconfig.json index 50de71b68e..c06be101e0 100644 --- a/invokeai/frontend/web/tsconfig.json +++ b/invokeai/frontend/web/tsconfig.json @@ -11,7 +11,7 @@ "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", - "moduleResolution": "Node", + "moduleResolution": "bundler", // TODO: Disabled for IDE performance issues with our translation JSON // "resolveJsonModule": true, "noUncheckedIndexedAccess": true, diff --git a/invokeai/frontend/web/tsconfig.node.json b/invokeai/frontend/web/tsconfig.node.json index 046964021f..ec7d7d72ca 100644 --- a/invokeai/frontend/web/tsconfig.node.json +++ b/invokeai/frontend/web/tsconfig.node.json @@ -1,8 +1,8 @@ { "compilerOptions": { "composite": true, - "module": "ESNext", - "moduleResolution": "Node", + "module": "NodeNext", + "moduleResolution": "NodeNext", "allowSyntheticDefaultImports": true }, "include": ["vite.config.mts"] diff --git a/invokeai/frontend/web/vite.config.mts b/invokeai/frontend/web/vite.config.mts index b32fe0fc74..511e80c7ed 100644 --- a/invokeai/frontend/web/vite.config.mts +++ b/invokeai/frontend/web/vite.config.mts @@ -1,8 +1,7 @@ /// import react from '@vitejs/plugin-react-swc'; import path from 'path'; -import { visualizer } from 'rollup-plugin-visualizer'; -import type { PluginOption } from 'vite'; +import Sonda from 'sonda/vite'; import { defineConfig } from 'vite'; import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'; import dts from 'vite-plugin-dts'; @@ -17,7 +16,6 @@ export default defineConfig(({ mode }) => { react(), eslint(), tsconfigPaths(), - visualizer() as unknown as PluginOption, dts({ insertTypesEntry: true, }), @@ -70,9 +68,10 @@ export default defineConfig(({ mode }) => { react(), mode !== 'test' && eslint({ failOnError: mode === 'production', failOnWarning: mode === 'production' }), tsconfigPaths(), - visualizer() as unknown as PluginOption, + Sonda(), ], build: { + sourcemap: true, chunkSizeWarningLimit: 1500, }, server: {