SkillHub 前端架构注释评审:7 项代码结构观察与渐进式重构方向
人工智能AI AgentAI 技能后端前端【免费下载链接】skillhubSelf-hosted, open-source agent skill registry for enterprises. Publish version skill packages, govern with RBAC and audit logs, deploy on-premise with Docker or Kubernetes.项目地址https://gitcode.com/gh_mirrors/sk/skillhub点击查看免费下载本文基于 SkillHub 仓库中的 docs/18-frontend-annotation-findings.md 展开。该文档记录的是前端代码在补充注释annotation pass过程中反复出现的结构与架构问题——它们持续影响代码可读性但文档的定位是保留观察结果而非强制推倒重写。本文将逐条还原这 7 项观察并结合web/src下的真实源码与测试给出可验证的现状分析、成因解读与落地的重构方向帮助读者在维护 SkillHub 前端或类似的分层 React 应用时建立清晰的架构边界判断力。SkillHub 的前端是一个基于 React 19 TanStack Router TanStack Query 的单页应用源码集中在web/src按app应用装配、pages路由页面、features业务功能、shared共享模块四层组织。本文讨论的 7 个问题均围绕这一分层展开共享层是否过度膨胀、路由声明是否过于集中、页面编排是否过重、功能边界是否一致、运行时配置契约是否隐晦、feature 层是否名不副实、组件是否越界关心路由。一、shared 查询层沦为跨功能厨房水槽观察到的现状文档指出一个共享 hook 文件同时承担了搜索、技能详情、版本读取、命名空间成员、发布、推广promotion等多类查询与变更mutation逻辑web/src/shared/hooks/use-skill-queries.tsweb/src/features/skill/use-skill-detail.tsweb/src/features/namespace/use-namespace-detail.ts源码核实use-skill-queries.ts全文件共 277 行export 的函数覆盖了远不止技能查询一个领域查询类useSearchSkills、useSkillDetail、useSkillVersions、useSkillFiles、useSkillReadme、useSkillFile、useSkillVersionDetail、useSkillVersionCompare变更类usePublishSkill发布含 60 秒超时PUBLISH_REQUEST_TIMEOUT_MS、useArchiveSkill、useUnarchiveSkill、useDeleteSkillVersion、useDeleteSkill、useWithdrawSkillReview、useRereleaseSkillVersion、useSubmitForReview、useConfirmPublish其中发布publish、归档/删除lifecycle、提交评审review workflow本质上是彼此独立的业务域却被集中在同一个模块里且每个 mutation 的onSuccess都要手动invalidateQueries一大串 query key例如 web/src/shared/hooks/use-skill-queries.ts 的发布成功回调用queryClient.invalidateQueries({ queryKey: [skills] })全量刷新。命名空间侧同理use-namespace-queries.ts又集中了命名空间创建、成员分页查询、候选人搜索、成员角色变更、批量导入等逻辑。更隐蔽的问题是依赖方向被掩盖多个 feature 模块只是把该共享文件的内容再导出外部调用方看到的是 feature 路径实际依赖却落在 shared 层shared与features的边界因此看起来是倒置的。建议方向按 feature 切片如skill、namespace、publish或按后端资源域拆分该文件并让 feature 目录拥有面向自身的 hook。拆分后每个文件职责单一onSuccess失效逻辑也能就近收敛避免改一个 hook 牵动全部页面的连锁成本。二、路由器是大型集中注册表路由策略混入路由声明观察到的现状路由创建、鉴权守卫、角色检查、搜索参数校验、懒加载规则全部声明在同一个大模块里。文档点名的文件web/src/app/router.tsxweb/src/shared/components/role-guard.tsx源码核实router.tsx全文件 730 行承担了四类互不相同的职责路由组件懒加载工厂createLazyRouteComponentrouter.tsx统一处理React.lazySuspense并接入动态导入失败恢复recoverFromDynamicImportError与一次性 reload 守卫clearDynamicImportReloadGuard还区分可见的RouteLoadingFallback与静默的SilentRouteFallbackdashboard 系页面用静默回退避免出现双重骨架屏。角色保护工厂createRoleProtectedRouteComponentrouter.tsx在路由边缘包一层RoleGuardReports、Promotions、Admin 用户、审计日志、标签管理、系统配置、命名空间管理等页面各自声明allowedRoles数组如[SKILL_ADMIN, SUPER_ADMIN]、[AUDITOR, SUPER_ADMIN]。鉴权与重定向requireAuth/redirectAuthenticated由createRequireAuth(getCurrentUser)生成beforeLoad挂在几乎每条受保护路由上登录页还有isSafeAuthReturnTo对returnTo做安全校验。search 参数校验几乎每条路由都内联validateSearch例如搜索页把q归一化、page强制转数字、starredOnly识别布尔与字符串两种形态router.tsxCLI 授权页则刻意把参数补成空字符串以防止被路由框架移除。RoleGuard组件本身role-guard.tsx负责在鉴权解析完成后的客户端角色判定未登录跳登录页、无权限 toast 提示并回退/dashboard且用hasHandledForbiddenRef防止重复处理。这样的集中声明能工作且行为显式但改动单条路由的成本被放大——加一条路由要同时考虑路径、鉴权、角色、参数校验、懒加载回退方式五种关注点且全部集中在一个文件里。建议方向保留单一路由入口createRouter与routeTree的组装点但将路由定义按区域拆分public、dashboard、admin、settings 各自成文件再在入口聚合。角色校验与validateSearch可随路由定义就近放置使改某区域的路由只在对应文件内发生。三、部分页面仍在做过多编排未委托给 feature 级容器观察到的现状多个页面同时协调多个 hook、查询失效、本地 UI 状态、导航规则与派生展示决策页面层同时扮演路由入口与业务容器两种角色。文档点名的文件web/src/pages/landing.tsxweb/src/pages/search.tsxweb/src/pages/skill-detail.tsxweb/src/pages/dashboard.tsx源码核实以 web/src/pages/skill-detail.tsx 为例该文件共 1955 行其头部 import 就横跨了文档渲染MarkdownRenderer、文件树FileTree、生命周期动作archive/delete/rerelease/withdraw/submit/confirm、推广与撤销promotion、社交互动star/subscribe/review、安全审计摘要、举报report等多个 feature并直接持有大量本地状态与导航辅助navigateAfterOverlays、resolveDeletedSkillReturnTo等。页面层几乎成了所有业务的总装线。web/src/pages/landing.tsx494 行同时承载 Hero 区、演示技能、快速上手区块、数据预取与滚动可见性触发useInViewweb/src/pages/search.tsx328 行把搜索文本、排序、分页、starredOnly过滤全部镜像进路由 search 参数并自带防抖、聚焦管理、滚动定位等 UI 细节web/src/pages/dashboard.tsx259 行则把侧边栏分组、权限过滤、徽标逻辑全部内联在页面文件中。建议方向把较重的编排下沉到 feature 容器或页面专属 hookpage-specific hooks页面文件只负责组合。例如skill-detail可以抽出use-skill-detail-page.ts统一管理查询开关、失效与导航决策页面主体只消费返回值并渲染这样页面文件回归薄可测试的编排逻辑也能独立成 hook 单元测试。四、功能边界分布不均匀观察到的现状有些关注点按 feature 组织得很干净另一些明明领域专用却长期留在 shared 目录导致新逻辑该放哪难以预测。文档点名的范围web/src/features/*web/src/shared/hooks/use-skill-queries.tsweb/src/shared/lib/*源码核实从web/src/shared/lib的实际文件清单看大量模块是业务专用而非通用工具skill-lifecycle.ts技能生命周期状态机辅助、skill-navigation.ts技能详情回跳与搜索链接构造、skill-documentation.ts文档路径解析、skill-download-cache.ts下载计数缓存、namespace-member-cache.ts命名空间成员缓存替换、governance-access.ts治理中心访问判定、registry-url.tsregistry 地址构造、search-query.ts搜索词归一化与命名空间解析。这些模块要么围绕技能领域、要么围绕命名空间/治理领域却被归类为共享与文档所述业务查询逻辑应归属 features的规则相矛盾。同时use-skill-queries.ts这样的领域专用 hook 位于 shared 层与第 1、6 点观察互为因果。建议方向收紧shared的准入标准通用 UI、通用 hooks、框架胶水如date-time.ts、number-format.ts、theme.ts、toast.ts、utils.ts留在 shared领域专用逻辑技能、命名空间、治理、搜索的领域规则迁入对应 feature 目录。迁移后shared/lib中大量存在的一一对应测试文件如skill-lifecycle.test.ts、skill-navigation.test.ts也随之就近归属测试与实现的关系更清晰。五、运行时配置引导依赖全局 window 契约观察到的现状部署期配置注入采用全局可变对象方案启动与 API 行为都耦合到window上的一个对象且该契约的定义散落在 bootstrap 与 API 代码两处容易被遗漏。文档点名的文件web/src/bootstrap.tsweb/src/api/client.tsweb/public/runtime-config.js源码核实bootstrap.ts46 行是应用真正的启动入口先initializeTheme()然后动态插入script src../runtime-config.js同步加载部署注入的配置加载失败时调用ensureRuntimeConfigFallback()写入默认值对象bootstrap.ts最后才import(./main)挂载 React 应用——即先保证配置就位再启动业务。部署侧 web/public/runtime-config.js 定义了 8 个契约字段apiBaseUrl、appBaseUrl以及 6 个认证相关开关/参数authDirectEnabled、authDirectProvider、authSessionBootstrapEnabled、authSessionBootstrapProvider、authSessionBootstrapAuto。该文件在 Docker 镜像中由 web/docker-entrypoint.d/30-runtime-config.sh 在容器启动时渲染注入实现同一镜像、不同部署环境。消费端 web/src/api/client.ts 中getRuntimeConfig()client.ts直接读取window.__SKILLHUB_RUNTIME_CONFIG__并在此基础上派生apiBaseUrl未配置时回退到import.meta.env.BASE_URL容器启动时替换的部署 base path保证子路径代理下 API 请求落到/skillhub/api/...而非/api/...client.ts。布尔开关通过parseBooleanFlag接受1/true/yes/on等字符串。这套机制对部署注入是务实的但问题在于契约的定义bootstrap 的回退对象 client 的 TypeScript 类型 部署脚本的渲染逻辑分布在三个位置任何一处新增字段都要三处同步且运行时完全信任window上的可变对象。建议方向如果必须保留部署期注入至少做两件事一是文档化完整生命周期脚本注入时机、回退语义、字段清单与取值规则二是将读取逻辑收敛进一个专用runtime-config模块如web/src/shared/lib/runtime-config.ts由它统一提供类型化的getApiBaseUrl()、isAuthDirectEnabled()等访问器避免 API 层直接触碰window全局对象。六、部分 feature 模块只是共享 hook 的薄重导出层观察到的现状一批 feature 文件只保留面向 feature 的导入路径实际逻辑并不在它们手里长期维护这两层只会增加间接性。文档点名的文件web/src/features/skill/use-skill-detail.tsweb/src/features/namespace/use-namespace-detail.tsweb/src/features/namespace/use-namespace-members.tsweb/src/features/publish/use-publish-skill.ts源码核实这四个文件的实际内容几乎完全一致都是仅注释 一行再导出// web/src/features/skill/use-skill-detail.ts全文 export { useSkillDetail } from /shared/hooks/use-skill-queries // web/src/features/namespace/use-namespace-detail.ts全文 export { useNamespaceDetail } from /shared/hooks/use-namespace-queries // web/src/features/publish/use-publish-skill.ts全文 export { usePublishSkill } from /shared/hooks/use-skill-queries调用方通过 feature 路径 import但真实实现仍在shared/hooks下的共享文件中。这带来两个代价一是读者以为 feature 目录拥有领域逻辑实际只是传话误导依赖方向的判断二是新增逻辑时无法确定该写在哪个文件进一步加剧第 1、4 点描述的不均匀。建议方向二选一并保持一致要么把实现真正迁入 feature 模块让use-skill-detail.ts拥有完整的useQuery定义要么让调用方直接 import 共享 hook 并删除重导出层。两者长期共存既有共享实现、又有 feature 薄壳是最差状态——既保留了共享层的耦合又增加了一层无价值的间接调用。七、通用 dashboard 组件仍内嵌工作流特定的路由规则观察到的现状部分组件看起来是纯展示导向内部却知道 review、promotion、report、skill 各条路由如何映射到 dashboard URL导致路由策略分散在中央 router 与少数 feature 组件两处。文档点名的文件web/src/features/governance/governance-inbox.tsxweb/src/features/governance/governance-notifications.tsx源码核实governance-inbox.tsx的openItem()governance-inbox.tsx确实内嵌了完整的路由分发策略REVIEW类型跳到/dashboard/reviews/${item.id}、PROMOTION跳到/dashboard/promotions、REPORT跳到/dashboard/reports、其余带namespace skillSlug的跳到/space/${namespace}/${skillSlug}。组件注释也承认backend payload 是刻意泛化的没有暴露单一前端路由因此由 UI 自行解析目标页——路由策略由此被复制进了一个展示组件。对比之下governance-notifications.tsx则克制得多它只暴露onMarkRead交互回调导航职责完全留在外部governance-notifications.tsx 注释明确更复杂的导航保持在该组件之外。两个同目录组件呈现了截然不同的边界风格直观体现了第 4 点功能边界不均匀的现状。建议方向把治理条目 → 目标路由的解析抽成一个独立的治理导航辅助函数或 feature hook例如governance-navigation.ts内含resolveGovernanceItemRoute(item)的纯函数与对应单元测试让governance-inbox.tsx回归纯渲染只消费解析结果。这样路由策略只存在于 router 与该辅助模块两处且辅助模块可被独立测试。八、观察汇总与渐进式重构优先级七项观察一览#观察点核心文件主要代价1shared 查询层跨功能膨胀shared/hooks/use-skill-queries.ts、use-namespace-queries.ts依赖方向倒置改动牵一发动全身2路由集中注册策略混入声明app/router.tsx、shared/components/role-guard.tsx改一条路由需同时权衡五种关注点3页面编排过重pages/skill-detail.tsx、landing.tsx、search.tsx、dashboard.tsx页面层同时是入口与业务容器难测试4功能边界分布不均features/*、shared/hooks/*、shared/lib/*新逻辑归属难以预测5运行时配置依赖全局 window 契约bootstrap.ts、api/client.ts、public/runtime-config.js契约定义三处分散易遗漏6feature 模块沦为薄重导出层features/*/use-*.ts四个文件增加无价值间接层7dashboard 组件内嵌路由规则features/governance/governance-inbox.tsx路由策略分散到组件层落地建议原文档的定位是记录观察、不主张全量重写这与渐进式重构的原则一致。按收益与风险排序建议的实施次序大致为低风险先做删除第 6 点的薄重导出层或迁入实现立刻消除误导抽第 7 点的治理导航辅助函数让纯函数可测试。中等风险为第 5 点建立统一的 runtime-config 访问模块先收敛读取点再逐步文档化生命周期。结构性改造按第 1、4 点拆 shared 查询层与迁移 shared 领域逻辑可借助shared/lib中现成的一一对应测试作为迁移安全网按第 2 点拆分路由文件保留单一routeTree组装点按第 3 点逐步把页面编排下沉为 page-specific hooks。每一步都应在现有测试如 web/e2e 的 Playwright 用例与shared/lib的单元测试覆盖下进行避免一次性大规模重构带来的回归风险。上述观察与建议同样适用于任何以featuresshared分层的大型 React 单页应用共享层应当默认最小化领域逻辑应当默认下沉到 feature路由与导航策略应当收敛到可测试的单一位置。赞分享人工智能AI AgentAI 技能后端前端【免费下载链接】skillhubSelf-hosted, open-source agent skill registry for enterprises. Publish version skill packages, govern with RBAC and audit logs, deploy on-premise with Docker or Kubernetes.项目地址https://gitcode.com/gh_mirrors/sk/skillhub点击查看免费下载相关推荐skillhub 后端架构体检一次代码注释过程中的 13 项结构发现与治理实践skillhub 后端架构体检一次代码注释过程中的 13 项结构发现与治理实践 本文整理自 skillhub 开源仓库中的技术记录文档 docs/17 bac人工智能AI AgentAI 技能后端前端vue2-elm前端架构评审如何评估与改进现有架构vue2 elm前端架构评审如何评估与改进现有架构 架构现状分析 vue2 elm是一个基于Vue2.x技术栈的大型单页面应用 SPA 采用组件化设计思想构前端电商移动开发Rector观察者模式重构事件驱动架构Rector观察者模式重构事件驱动架构 在现代PHP开发中事件驱动架构Event Driven Architecture已成为构建灵活可扩展系统的关键模开发工具代码质量上一篇Node.js 2019 年 12 月安全发布回顾npm CLI 三大高危漏洞与修复升级指南下一篇DeepSeek Harness 压缩摘要的前缀缓存复用把汇总请求做成对话前缀的扩展创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考