From 6c8473e4e4a77fa439503c49e2361cdce06bb288 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Sat, 7 Feb 2026 19:47:05 -0800 Subject: [PATCH] refactor: replace runtime isElectron() with build-time isDesktop constant (#8710) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Replace all runtime `isElectron()` function calls with the build-time `isDesktop` constant from `@/platform/distribution/types`, enabling dead-code elimination in non-desktop builds. ## Changes - **What**: Migrate 30 files from runtime `isElectron()` detection (checking `window.electronAPI`) to the compile-time `isDesktop` constant (driven by `__DISTRIBUTION__` Vite define). Remove `isElectron` from `envUtil.ts`. Update `isNativeWindow()` to use `isDesktop`. Guard `electronAPI()` calls behind `isDesktop` checks in stores. Update 7 test files to use `vi.hoisted` + getter mock pattern for per-test `isDesktop` toggling. Add `DISTRIBUTION=desktop` to `dev:electron` script. ## Review Focus - The `electronDownloadStore.ts` now guards the top-level `electronAPI()` call behind `isDesktop` to prevent crashes on non-desktop builds. - Test mocking pattern uses `vi.hoisted` with a getter to allow per-test toggling of the `isDesktop` value. - Pre-existing issues not addressed: `as ElectronAPI` cast in `envUtil.ts`, `:class="[]"` in `BaseViewTemplate.vue`, `@ts-expect-error` in `ModelLibrarySidebarTab.vue`. - This subsumes PR #8627 and renders PR #6122 and PR #7374 obsolete. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8710-refactor-replace-runtime-isElectron-with-build-time-isDesktop-constant-3006d73d365081c08037f0e61c2f6c77) by [Unito](https://www.unito.io) --- package.json | 2 +- src/App.vue | 5 +- src/components/TopMenuSection.test.ts | 15 ++++-- src/components/TopMenuSection.vue | 3 +- .../tabs/terminal/BaseTerminal.test.ts | 9 +++- .../tabs/terminal/BaseTerminal.vue | 5 +- .../dialog/content/MissingModelsWarning.vue | 4 +- .../helpcenter/HelpCenterMenuContent.vue | 18 +++---- .../sidebar/tabs/ModelLibrarySidebarTab.vue | 4 +- src/components/topbar/WorkflowTabs.vue | 4 +- .../sidebarTabs/useModelLibrarySidebarTab.ts | 4 +- src/composables/useExternalLink.test.ts | 21 +++++--- src/composables/useExternalLink.ts | 5 +- src/extensions/core/electronAdapter.ts | 5 +- src/platform/distribution/types.ts | 6 +-- .../settings/composables/useSettingUI.ts | 10 ++-- .../updates/common/releaseStore.test.ts | 22 ++++---- src/platform/updates/common/releaseStore.ts | 9 ++-- .../ReleaseNotificationToast.test.ts | 25 ++++++---- .../components/ReleaseNotificationToast.vue | 4 +- src/router.ts | 9 ++-- src/stores/aboutPanelStore.ts | 6 +-- src/stores/electronDownloadStore.ts | 50 +++++++++---------- src/stores/systemStatsStore.test.ts | 23 +++++---- src/stores/systemStatsStore.ts | 4 +- src/stores/workspace/bottomPanelStore.test.ts | 8 ++- src/stores/workspace/bottomPanelStore.ts | 4 +- src/utils/envUtil.ts | 8 ++- src/views/GraphView.vue | 10 ++-- src/views/templates/BaseViewTemplate.vue | 5 +- 30 files changed, 165 insertions(+), 142 deletions(-) diff --git a/package.json b/package.json index 9181a69b9..5dff07bd5 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "collect-i18n": "pnpm exec playwright test --config=playwright.i18n.config.ts", "dev:cloud": "cross-env DEV_SERVER_COMFYUI_URL='https://testcloud.comfy.org/' nx serve", "dev:desktop": "nx dev @comfyorg/desktop-ui", - "dev:electron": "nx serve --config vite.electron.config.mts", + "dev:electron": "cross-env DISTRIBUTION=desktop nx serve --config vite.electron.config.mts", "dev:no-vue": "cross-env DISABLE_VUE_PLUGINS=true nx serve", "dev": "nx serve", "devtools:pycheck": "python3 -m compileall -q tools/devtools", diff --git a/src/App.vue b/src/App.vue index f843a7347..f9e34fc68 100644 --- a/src/App.vue +++ b/src/App.vue @@ -19,7 +19,8 @@ import config from '@/config' import { useWorkspaceStore } from '@/stores/workspaceStore' import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection' -import { electronAPI, isElectron } from './utils/envUtil' +import { electronAPI } from '@/utils/envUtil' +import { isDesktop } from '@/platform/distribution/types' import { app } from '@/scripts/app' const workspaceStore = useWorkspaceStore() @@ -42,7 +43,7 @@ const showContextMenu = (event: MouseEvent) => { onMounted(() => { window['__COMFYUI_FRONTEND_VERSION__'] = config.app_version - if (isElectron()) { + if (isDesktop) { document.addEventListener('contextmenu', showContextMenu) } diff --git a/src/components/TopMenuSection.test.ts b/src/components/TopMenuSection.test.ts index b2bf7041c..ade4a8ae6 100644 --- a/src/components/TopMenuSection.test.ts +++ b/src/components/TopMenuSection.test.ts @@ -18,9 +18,8 @@ import { useCommandStore } from '@/stores/commandStore' import { useExecutionStore } from '@/stores/executionStore' import { TaskItemImpl, useQueueStore } from '@/stores/queueStore' import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore' -import { isElectron } from '@/utils/envUtil' -const mockData = vi.hoisted(() => ({ isLoggedIn: false })) +const mockData = vi.hoisted(() => ({ isLoggedIn: false, isDesktop: false })) vi.mock('@/composables/auth/useCurrentUser', () => ({ useCurrentUser: () => { @@ -30,7 +29,13 @@ vi.mock('@/composables/auth/useCurrentUser', () => ({ } })) -vi.mock('@/utils/envUtil') +vi.mock('@/platform/distribution/types', () => ({ + isCloud: false, + isNightly: false, + get isDesktop() { + return mockData.isDesktop + } +})) vi.mock('@/stores/firebaseAuthStore', () => ({ useFirebaseAuthStore: vi.fn(() => ({ currentUser: null, @@ -107,6 +112,8 @@ describe('TopMenuSection', () => { beforeEach(() => { vi.resetAllMocks() localStorage.clear() + mockData.isDesktop = false + mockData.isLoggedIn = false }) describe('authentication state', () => { @@ -129,7 +136,7 @@ describe('TopMenuSection', () => { describe('on desktop platform', () => { it('should display LoginButton and not display CurrentUserButton', () => { - vi.mocked(isElectron).mockReturnValue(true) + mockData.isDesktop = true const wrapper = createWrapper() expect(wrapper.findComponent(LoginButton).exists()).toBe(true) expect(wrapper.findComponent(CurrentUserButton).exists()).toBe(false) diff --git a/src/components/TopMenuSection.vue b/src/components/TopMenuSection.vue index 13b28e904..85a01d1a1 100644 --- a/src/components/TopMenuSection.vue +++ b/src/components/TopMenuSection.vue @@ -153,7 +153,7 @@ import { useQueueStore, useQueueUIStore } from '@/stores/queueStore' import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore' import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore' import { useWorkspaceStore } from '@/stores/workspaceStore' -import { isElectron } from '@/utils/envUtil' +import { isDesktop } from '@/platform/distribution/types' import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment' import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState' import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes' @@ -163,7 +163,6 @@ const workspaceStore = useWorkspaceStore() const rightSidePanelStore = useRightSidePanelStore() const managerState = useManagerState() const { isLoggedIn } = useCurrentUser() -const isDesktop = isElectron() const { t, n } = useI18n() const { toastErrorHandler } = useErrorHandling() const commandStore = useCommandStore() diff --git a/src/components/bottomPanel/tabs/terminal/BaseTerminal.test.ts b/src/components/bottomPanel/tabs/terminal/BaseTerminal.test.ts index b99e54ea1..a651475fb 100644 --- a/src/components/bottomPanel/tabs/terminal/BaseTerminal.test.ts +++ b/src/components/bottomPanel/tabs/terminal/BaseTerminal.test.ts @@ -55,10 +55,17 @@ vi.mock('@/composables/bottomPanelTabs/useTerminal', () => ({ })) vi.mock('@/utils/envUtil', () => ({ - isElectron: vi.fn(() => false), electronAPI: vi.fn(() => null) })) +const mockData = vi.hoisted(() => ({ isDesktop: false })) + +vi.mock('@/platform/distribution/types', () => ({ + get isDesktop() { + return mockData.isDesktop + } +})) + // Mock clipboard API Object.defineProperty(navigator, 'clipboard', { value: { diff --git a/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue b/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue index 655df0b65..54b337c20 100644 --- a/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue +++ b/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue @@ -35,7 +35,8 @@ import { useI18n } from 'vue-i18n' import Button from '@/components/ui/button/Button.vue' import { useTerminal } from '@/composables/bottomPanelTabs/useTerminal' -import { electronAPI, isElectron } from '@/utils/envUtil' +import { electronAPI } from '@/utils/envUtil' +import { isDesktop } from '@/platform/distribution/types' import { cn } from '@/utils/tailwindUtil' const { t } = useI18n() @@ -85,7 +86,7 @@ const showContextMenu = (event: MouseEvent) => { electronAPI()?.showContextMenu({ type: 'text' }) } -if (isElectron()) { +if (isDesktop) { useEventListener(terminalEl, 'contextmenu', showContextMenu) } diff --git a/src/components/dialog/content/MissingModelsWarning.vue b/src/components/dialog/content/MissingModelsWarning.vue index 1b044e4d3..f939b6c6c 100644 --- a/src/components/dialog/content/MissingModelsWarning.vue +++ b/src/components/dialog/content/MissingModelsWarning.vue @@ -13,7 +13,7 @@ @@ -52,7 +52,7 @@ import { useProgressFavicon } from '@/composables/useProgressFavicon' import { SERVER_CONFIG_ITEMS } from '@/constants/serverConfig' import { i18n, loadLocale } from '@/i18n' import ModelImportProgressDialog from '@/platform/assets/components/ModelImportProgressDialog.vue' -import { isCloud } from '@/platform/distribution/types' +import { isCloud, isDesktop } from '@/platform/distribution/types' import { useSettingStore } from '@/platform/settings/settingStore' import { useTelemetry } from '@/platform/telemetry' import { useFrontendVersionMismatchWarning } from '@/platform/updates/common/useFrontendVersionMismatchWarning' @@ -78,7 +78,7 @@ import { useServerConfigStore } from '@/stores/serverConfigStore' import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore' -import { electronAPI, isElectron } from '@/utils/envUtil' +import { electronAPI } from '@/utils/envUtil' import LinearView from '@/views/LinearView.vue' import ManagerProgressToast from '@/workbench/extensions/manager/components/ManagerProgressToast.vue' @@ -111,7 +111,7 @@ watch( document.body.classList.add(DARK_THEME_CLASS) } - if (isElectron()) { + if (isDesktop) { electronAPI().changeTheme({ color: 'rgba(0, 0, 0, 0)', symbolColor: newTheme.colors.comfy_base['input-text'] @@ -121,7 +121,7 @@ watch( { immediate: true } ) -if (isElectron()) { +if (isDesktop) { watch( () => queueStore.tasks, (newTasks, oldTasks) => { diff --git a/src/views/templates/BaseViewTemplate.vue b/src/views/templates/BaseViewTemplate.vue index 786cd77ac..b6b245cf4 100644 --- a/src/views/templates/BaseViewTemplate.vue +++ b/src/views/templates/BaseViewTemplate.vue @@ -22,7 +22,8 @@