From 7ca20fc4ede99aa4cc4fb0ff8519b0a2df2d9454 Mon Sep 17 00:00:00 2001 From: Tunglies Date: Mon, 13 Jul 2026 11:29:25 +0800 Subject: [PATCH] refactor: knip cleanup (#7468) * chore(frontend): expand Knip reports * chore: remove unused Mihomo changelog sync script * refactor: remove unused value exports * refactor: remove unused type exports * refactor: remove circular route dependencies * chore(frontend): move Knip scope to config * chore(i18n): remove stale portable update message --- .github/workflows/frontend-check.yml | 2 +- Makefile.toml | 2 +- knip.json | 22 ++- package.json | 2 +- scripts/sync-mihomo-version.mjs | 104 ---------- .../base/base-split-chip-editor.tsx | 2 +- src/components/base/index.ts | 5 +- src/components/profile/editor-viewer.tsx | 2 +- src/components/proxy/use-filter-sort.ts | 86 -------- src/components/setting/mods/update-viewer.tsx | 5 - .../setting/setting-verge-basic.tsx | 4 +- src/hooks/use-connection-data.ts | 8 +- src/hooks/use-mihomo-ws-subscription.ts | 2 +- src/hooks/use-system-state.ts | 2 +- src/hooks/use-update.ts | 8 - src/hooks/use-window.ts | 2 +- src/locales/ar/settings.json | 1 - src/locales/de/settings.json | 1 - src/locales/en/settings.json | 1 - src/locales/es/settings.json | 1 - src/locales/fa/settings.json | 1 - src/locales/id/settings.json | 1 - src/locales/jp/settings.json | 1 - src/locales/ko/settings.json | 1 - src/locales/ru/settings.json | 1 - src/locales/tr/settings.json | 1 - src/locales/tt/settings.json | 1 - src/locales/zh/settings.json | 1 - src/locales/zhtw/settings.json | 1 - src/pages/_layout.tsx | 8 +- src/pages/_navigation-meta.ts | 25 +++ src/pages/_navigation.tsx | 185 ++++++++++++++++++ src/pages/_routers.tsx | 184 +---------------- src/providers/app-data-context.ts | 57 ------ src/services/cmds.ts | 79 -------- src/services/i18n.ts | 11 -- src/services/notice-service.ts | 2 +- src/services/preload.ts | 4 +- src/services/query-client.ts | 2 +- src/services/update.ts | 23 +-- src/types/generated/i18n-keys.ts | 1 - src/types/generated/i18n-resources.ts | 1 - src/utils/debug.ts | 13 +- src/utils/search-matcher.ts | 2 +- 44 files changed, 266 insertions(+), 602 deletions(-) delete mode 100644 scripts/sync-mihomo-version.mjs create mode 100644 src/pages/_navigation-meta.ts create mode 100644 src/pages/_navigation.tsx diff --git a/.github/workflows/frontend-check.yml b/.github/workflows/frontend-check.yml index 0665af375..d2049766e 100644 --- a/.github/workflows/frontend-check.yml +++ b/.github/workflows/frontend-check.yml @@ -75,6 +75,6 @@ jobs: if: steps.check_frontend.outputs.frontend == 'true' run: pnpm typecheck - - name: Report unused frontend files + - name: Report unused frontend code and dependencies if: steps.check_frontend.outputs.frontend == 'true' run: pnpm knip:report diff --git a/Makefile.toml b/Makefile.toml index acb58c6b9..a5a757eca 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -28,7 +28,7 @@ args = ["typecheck"] command = "pnpm.cmd" [tasks.knip-report] -description = "Report unused frontend files" +description = "Report unused frontend code and dependencies" command = "pnpm" args = ["knip:report"] diff --git a/knip.json b/knip.json index 7464f18ae..d2f44fb6f 100644 --- a/knip.json +++ b/knip.json @@ -1,5 +1,25 @@ { "$schema": "https://unpkg.com/knip@6/schema.json", "entry": ["src/polyfills/*.js!"], - "project": ["src/**/*.{js,jsx,ts,tsx}!"] + "project": ["src/**/*.{js,jsx,ts,tsx}!", "scripts/**/*.{js,mjs}!"], + "include": [ + "files", + "dependencies", + "devDependencies", + "unlisted", + "unresolved", + "binaries", + "exports", + "types", + "duplicates", + "cycles" + ], + "ignoreBinaries": ["rustc"], + "ignoreDependencies": ["lint-staged"], + "ignoreIssues": { + "src/types/generated/**": ["exports", "types"] + }, + "cycles": { + "dynamicImports": true + } } diff --git a/package.json b/package.json index bcb429a81..c41297f07 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "i18n:format": "node scripts/cleanup-unused-i18n.mjs --align --apply", "i18n:types": "node scripts/generate-i18n-keys.mjs", "typecheck": "tsc --noEmit", - "knip:report": "knip --files --no-exit-code" + "knip:report": "knip --no-exit-code" }, "dependencies": { "@dnd-kit/core": "^6.3.1", diff --git a/scripts/sync-mihomo-version.mjs b/scripts/sync-mihomo-version.mjs deleted file mode 100644 index 5ccfcfa70..000000000 --- a/scripts/sync-mihomo-version.mjs +++ /dev/null @@ -1,104 +0,0 @@ -import fs from 'node:fs' -import fsp from 'node:fs/promises' -import path from 'node:path' - -const VERSION_URL = - 'https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt' -const DEFAULT_CHANGELOG = 'Changelog.md' - -const args = process.argv.slice(2) -const CHECK = args.includes('--check') -const DRY_RUN = args.includes('--dry-run') - -const logError = (message, ...optionalParams) => - console.error(message, ...optionalParams) -const logInfo = (message, ...optionalParams) => - console.log(message, ...optionalParams) - -function readArgValue(name) { - const index = args.indexOf(name) - if (index === -1) return null - return args[index + 1] ?? null -} - -function normalizeVersion(version) { - const normalized = version.trim() - return normalized.startsWith('v') ? normalized : `v${normalized}` -} - -async function fetchLatestVersion() { - const explicitVersion = readArgValue('--version') - if (explicitVersion) return normalizeVersion(explicitVersion) - - const response = await fetch(VERSION_URL, { - method: 'GET', - }) - - if (!response.ok) { - throw new Error(`Failed to fetch ${VERSION_URL}: ${response.status}`) - } - - return normalizeVersion(await response.text()) -} - -function replaceExistingLine(content, version) { - const mihomoLine = - /^(\s*-\s*(?:\*\*)?\s*Mihomo\(Meta\)\s*内核升级至\s*)v?\d+(?:\.\d+)+(?:[-+][^\s*]+)?((?:\*\*)?\s*)$/m - - if (!mihomoLine.test(content)) return null - - return content.replace(mihomoLine, `$1${version}$2`) -} - -function insertMihomoLine(content, version) { - const title = /^## .+$/m - const match = content.match(title) - - if (!match || match.index === undefined) { - throw new Error('could not find changelog title') - } - - const titleEnd = match.index + match[0].length - const before = content.slice(0, titleEnd) - const after = content.slice(titleEnd).replace(/^\n*/, '\n\n') - - return `${before}\n\n- **Mihomo(Meta) 内核升级至 ${version}**${after}` -} - -async function main() { - const cwd = process.cwd() - const changelogArg = readArgValue('--file') - const changelogPath = path.join(cwd, changelogArg || DEFAULT_CHANGELOG) - - if (!fs.existsSync(changelogPath)) { - throw new Error(`could not find ${path.relative(cwd, changelogPath)}`) - } - - const version = await fetchLatestVersion() - const content = await fsp.readFile(changelogPath, 'utf-8') - const nextContent = - replaceExistingLine(content, version) ?? insertMihomoLine(content, version) - - if (nextContent === content) { - logInfo(`Mihomo version in changelog is already ${version}`) - return - } - - if (CHECK) { - logError(`Mihomo version in changelog is not ${version}`) - process.exit(1) - } - - if (DRY_RUN) { - logInfo(`Would update Mihomo version in changelog to ${version}`) - return - } - - await fsp.writeFile(changelogPath, nextContent) - logInfo(`Updated Mihomo version in changelog to ${version}`) -} - -main().catch((error) => { - logError('sync-mihomo-version failed:', error.message) - process.exit(1) -}) diff --git a/src/components/base/base-split-chip-editor.tsx b/src/components/base/base-split-chip-editor.tsx index be9524291..75c21d020 100644 --- a/src/components/base/base-split-chip-editor.tsx +++ b/src/components/base/base-split-chip-editor.tsx @@ -13,7 +13,7 @@ import type { ReactNode } from 'react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -export type BaseSplitChipEditorMode = 'visual' | 'advanced' +type BaseSplitChipEditorMode = 'visual' | 'advanced' interface BaseSplitChipEditorProps { value?: string diff --git a/src/components/base/index.ts b/src/components/base/index.ts index e1ddd9459..e1f87c20d 100644 --- a/src/components/base/index.ts +++ b/src/components/base/index.ts @@ -6,10 +6,7 @@ export { BaseLoading } from './base-loading' export { BaseLoadingOverlay } from './base-loading-overlay' export { BasePage } from './base-page' export { BaseSearchBox, type SearchState } from './base-search-box' -export { - BaseSplitChipEditor, - type BaseSplitChipEditorMode, -} from './base-split-chip-editor' +export { BaseSplitChipEditor } from './base-split-chip-editor' export { BaseStyledSelect } from './base-styled-select' export { BaseStyledTextField } from './base-styled-text-field' export { MonacoEditor } from './monaco-editor' diff --git a/src/components/profile/editor-viewer.tsx b/src/components/profile/editor-viewer.tsx index 2b6d61f9b..f56a3337b 100644 --- a/src/components/profile/editor-viewer.tsx +++ b/src/components/profile/editor-viewer.tsx @@ -28,7 +28,7 @@ import getSystem from '@/utils/get-system' const appWindow = getCurrentWebviewWindow() -export type EditorLanguage = 'yaml' | 'javascript' | 'css' +type EditorLanguage = 'yaml' | 'javascript' | 'css' export interface EditorViewerProps { open: boolean diff --git a/src/components/proxy/use-filter-sort.ts b/src/components/proxy/use-filter-sort.ts index 9b143329f..c3cafa919 100644 --- a/src/components/proxy/use-filter-sort.ts +++ b/src/components/proxy/use-filter-sort.ts @@ -1,6 +1,3 @@ -import { useEffect, useMemo, useReducer, useRef } from 'react' - -import { useVerge } from '@/hooks/use-verge' import delayManager from '@/services/delay' import { compileStringMatcher } from '@/utils/search-matcher' @@ -13,89 +10,6 @@ export type ProxySearchState = { useRegularExpression?: boolean } -export default function useFilterSort( - proxies: IProxyItem[], - groupName: string, - filterText: string, - sortType: ProxySortType, - searchState?: ProxySearchState, -) { - const { verge } = useVerge() - const [_, bumpRefresh] = useReducer((count: number) => count + 1, 0) - const lastInputRef = useRef<{ text: string; sort: ProxySortType } | null>( - null, - ) - const debounceTimerRef = useRef(null) - - useEffect(() => { - let last = 0 - - delayManager.setGroupListener(groupName, () => { - // 简单节流 - const now = Date.now() - if (now - last > 666) { - last = now - bumpRefresh() - } - }) - - return () => { - delayManager.removeGroupListener(groupName) - } - }, [groupName]) - - const compute = useMemo(() => { - const fp = filterProxies(proxies, groupName, filterText, searchState) - const sp = sortProxies( - fp, - groupName, - sortType, - verge?.default_latency_timeout, - ) - return sp - }, [ - proxies, - groupName, - filterText, - sortType, - searchState, - verge?.default_latency_timeout, - ]) - - const [result, setResult] = useReducer( - (_prev: IProxyItem[], next: IProxyItem[]) => next, - compute, - ) - - useEffect(() => { - if (debounceTimerRef.current !== null) { - window.clearTimeout(debounceTimerRef.current) - debounceTimerRef.current = null - } - - const prev = lastInputRef.current - const stableInputs = - prev && prev.text === filterText && prev.sort === sortType - - lastInputRef.current = { text: filterText, sort: sortType } - - const delay = stableInputs ? 0 : 150 - debounceTimerRef.current = window.setTimeout(() => { - setResult(compute) - debounceTimerRef.current = null - }, delay) - - return () => { - if (debounceTimerRef.current !== null) { - window.clearTimeout(debounceTimerRef.current) - debounceTimerRef.current = null - } - } - }, [compute, filterText, sortType]) - - return result -} - export function filterSort( proxies: IProxyItem[], groupName: string, diff --git a/src/components/setting/mods/update-viewer.tsx b/src/components/setting/mods/update-viewer.tsx index aaa2d8228..1d260681e 100644 --- a/src/components/setting/mods/update-viewer.tsx +++ b/src/components/setting/mods/update-viewer.tsx @@ -17,7 +17,6 @@ import type { Options as ReactMarkdownOptions } from 'react-markdown' import { BaseDialog, DialogRef } from '@/components/base' import { useUpdate } from '@/hooks/use-update' -import { portableFlag } from '@/pages/_layout' import { showNotice } from '@/services/notice-service' import { useSetUpdateState, useUpdateState } from '@/services/states' @@ -160,10 +159,6 @@ export function UpdateViewer({ ref }: { ref?: Ref }) { }, [updateInfo]) const onUpdate = useLockFn(async () => { - if (portableFlag) { - showNotice.error('settings.modals.update.messages.portableError') - return - } if (!updateInfo?.body) return if (breakChangeFlag) { showNotice.error('settings.modals.update.messages.breakChangeError') diff --git a/src/components/setting/setting-verge-basic.tsx b/src/components/setting/setting-verge-basic.tsx index 4d4512461..e50741e21 100644 --- a/src/components/setting/setting-verge-basic.tsx +++ b/src/components/setting/setting-verge-basic.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next' import { DialogRef, TooltipIcon } from '@/components/base' import { useVerge } from '@/hooks/use-verge' -import { navItems } from '@/pages/_routers' +import { navigationItems } from '@/pages/_navigation-meta' import { copyClashEnv } from '@/services/cmds' import { supportedLanguages } from '@/services/i18n' import { showNotice } from '@/services/notice-service' @@ -187,7 +187,7 @@ const SettingVergeBasic = ({ onError }: Props) => { onGuard={(e) => patchVerge({ start_page: e })} >