项目:xiaoye-admin-react | 版本:v1.2 | 生成日期:2026-10-06 | 依据:源码分析
package.json 共声明 53 个直接依赖:dependencies 21 个、devDependencies 32 个,另有 engines: node >= 20.19.0 的运行门槛。这份清单的第一个特征是短——对一个内置动态路由、权限、四套布局、国际化、Mock、PWA 的后台框架而言,53 个直接依赖属于克制水平,得益于把大量职责压回少数几个"多面手"(antd 承担组件与图标体系、Tailwind 承担全部原子样式、ahooks 承担 Hooks 工具)而不引入专用库。第二个特征是每项都能落到源码:本篇的每个依赖都标注了真实使用位置,无法落到源码的两项(qs、react-transition-group,见第 6 节健康度评估)恰好就是依赖审计的发现。第三个特征是版本策略分级:绝大多数依赖用 ^ 宽范围跟随 minor 更新,唯独两处收紧——msw 精确锁定 2.15.0(Service Worker 文件与版本绑定)、TypeScript 事实上锁 5.9.x(typescript-eslint 的 peer 约束,README.md 明确警告不可升 7)。
包管理器是 pnpm(锁文件 pnpm-lock.yaml 为依赖一致性的唯一凭证),它的严格 node_modules 结构在本项目不是性能偏好而是依赖纪律:幽灵依赖(未声明却 import)在 pnpm 下无法解析,因此 @ant-design/cssinjs 虽是 antd 的传递依赖,也必须在 dependencies 中显式声明才能直接 import(src/main.tsx)。这条纪律与 ADR-02(无 Less)同源:依赖清单里不存在的预处理器,机制上就是不可用的。依赖的选型动机考古见《16-决策记录ADR》各条目;构建期对这些依赖的消费方式(manualChunks 分包、插件开关)见《14-配置与环境详解》第 3-4 节。
| 依赖 | 版本 | 用途 | 使用位置 | 选型理由 | 替代方案 | 升级风险 |
|---|---|---|---|---|---|---|
| react | ^19.3.0 | 视图层运行时 | 全部 tsx | 19 稳定版;useSyncExternalStore(OfflineBanner)、lazy/Suspense(动态路由)为框架级依赖 | 无 | 大版本升级需重验 KeepAlive 与 Activity 语义(ADR-08) |
| react-dom | ^19.3.0 | DOM 渲染器 | src/main.tsx createRoot、全屏 Loading 独立 root | 与 react 同版本 | 无 | 同上 |
| antd | ^6.6.3 | 企业级组件库 | 全部 layouts/views;vendor-antd 分包 | 替代原自研 @xiaoye-react/ui,降低维护成本(README 第七节) | arco/fluent-ui | 大版本 API 变更多;Watermark 空 content 坑已在 src/layouts/index.tsx 处理 |
| @ant-design/cssinjs | ^2.1.2 | antd 样式注入 @layer antd | src/main.tsx StyleProvider layer | antd 与 Tailwind 共存的关键(ADR-02) | 无直接替代 | 与 antd 主版本强耦合,需同步升级 |
| @ant-design/icons | ^6.3.4 | 图标库 | 菜单 meta.icon 渲染(src/components/Icon)、各页面 | 与 antd 主版本对齐 | iconfont 全量字体(正是被淘汰的方案) | 桶导出全量进包风险,见《10-性能设计与优化》7 |
| react-router-dom | ^6.30.4 | 路由 | src/routers/**:createBrowserRouter/HashRouter、loader、useLoaderData、useMatches | data router 语义成熟,loader 传递 meta 是动态路由的关键通道 | TanStack Router | v7 迁移有 future flag(v7_relativeSplatPath/v7_startTransition 已在代码中预埋) |
| zustand | ^5.0.14 | 状态管理 | src/stores/** 四 store + persist 中间件 | 轻量、可在非 React 环境 getState()(拦截器依赖此能力)、persist/migrate 成熟 | redux/jotai | v4→v5 已完成;persist API 变动影响 migrate(见《04-数据模型》2.1) |
| axios | ^1.18.1 | HTTP 客户端 | src/api/index.ts 唯一实例 | 拦截器体系承载鉴权/错误分流/loadings | ky/ofetch/原生 fetch | 1.x 内 stable;AxiosHeaders.set 兼容检查已写 |
| i18next | ^26.3.6 | i18n 内核 | src/languages/index.ts 初始化;非 React 场景 i18n.t(checkStatus/errorHandler/ErrorBoundary) | 非 React 场景可用是硬需求 | 自研词条映射 | 大版本词条 API 稳定 |
| react-i18next | ^17.0.10 | React 绑定 | 全部 useTranslation 消费点 | 与 i18next 配套 | 无 | 跟随 i18next |
| ahooks | ^3.9.7 | Hooks 工具集 | useDebounceFn(LayoutMain 窄屏折叠)、useUpdateEffect(Tabs)、showcase 演示页 | 按需取用,替代自研 hooks 群 | 自研 | 3.x 稳定 |
| echarts | ^6.1.0 | 图表 | src/components/Echarts/(echarts/core 按需注册)、home、assembly/echarts | 按需注册控制体积;vendor-echarts 独立分包 | antv 系 | 6 的 legend 破坏性变更已在封装层适配(normalizeLegend) |
| @dnd-kit/core / sortable / utilities | ^6.3.1 / ^10.0.0 / ^3.2.2 | 拖拽 | 标签栏拖拽(src/layouts/components/Tabs/index.tsx)、拖拽表格(useDragTable) | 轻量、无 DOM 克隆、无障碍友好 | react-dnd | 三包版本节奏不同步,升级需逐包验证 |
| dayjs | ^1.11.21 | 时间处理 | DatePicker/RangePicker/TimePicker 封装、表单页、ThemeProvider locale、vite.config.ts 构建时间 | antd 6 内置依赖,locale 同步 | date-fns | 稳定 |
| md5 | ^2.3.0 | 密码摘要传输 | src/views/login/components/LoginForm.tsx、PasswordModal、src/mocks/modules/auth.ts | 与登录契约一致(ADR-05 相关取舍) | 无(或去掉摘要) | 稳定;安全性分析见《08-安全与权限设计》2.3 |
| nprogress | ^0.2.0 | 顶部进度条 | src/config/nprogress.ts、src/components/Loading/index.tsx | 零配置小件 | 自研 CSS 进度条 | 稳定 |
| react-colorful | ^5.8.0 | 取色器 | src/layouts/components/ThemeDrawer/components/ColorPicker/index.tsx | 体积 2KB 级、无依赖 | antd ColorPicker | 稳定 |
| qs | ^6.15.3 | URL 参数序列化 | 全仓零引用(仅 src/enums/httpEnum.ts 注释提及) | 历史遗留(参考项目曾配合 form-urlencoded 使用) | — | 建议移除(见第 6 节) |
| react-transition-group | ^4.4.5 | 进出场动画 | 全仓零引用(grep TransitionGroup/CSSTransition 无结果) | 历史遗留(原项目动画方案残留) | — | 建议移除(见第 6 节) |
| 依赖 | 版本 | 用途 | 使用位置 | 升级风险 |
|---|---|---|---|---|
| vite | ^8.3.0 | 构建与 dev server | vite.config.ts | 大版本升级重点验 glob 语义与 manualChunks |
| @vitejs/plugin-react | ^6.1.1 | JSX 转换与 Fast Refresh | vite.config.ts plugins | 跟随 vite 主版本 |
| tailwindcss | ^4.3.3 | 原子化 CSS | src/styles/global.css(@theme/@layer/@utility) | v4 CSS-first,无 config 文件可迁移;升级需关注 @layer 行为 |
| @tailwindcss/vite | ^4.3.3 | Tailwind 官方 Vite 插件 | vite.config.ts(取代 PostCSS 链) | 跟随 tailwindcss |
| typescript | ^5.9.3 | 类型系统 | tsc -b(project references) | 锁 5.9.x:typescript-eslint peer <6.1.0,TS 7(Go 版)不兼容(README 警告) |
| typescript-eslint | ^8.70.0 | TS lint 规则集 | eslint.config.js | 大版本变动影响 peer 约束(正是 TS 锁版的原因) |
| eslint | ^10.10.0 | Lint 内核 | pnpm lint | flat config 已是默认形态 |
| @eslint/js | ^10.0.1 | JS 推荐规则 | eslint.config.js | 跟随 eslint |
| eslint-plugin-react-hooks | ^7.1.1 | Hooks 规则 | eslint.config.js(手动声明规则而非预设对象) | 预设导出结构大版本会变(源码注释) |
| eslint-plugin-react-refresh | ^0.5.6 | Fast Refresh 边界检查 | eslint.config.js | 稳定 |
| eslint-config-prettier | ^10.1.8 | 关闭格式类 lint 规则 | eslint.config.js 必须置底 | 稳定 |
| globals | ^17.12.0 | 全局变量声明 | scripts 段 Node 全局 | 稳定 |
| prettier | ^3.9.6 | 格式化 | .prettierrc.json(120 列/双引号/无尾逗号) | 稳定 |
| msw | 2.15.0(精确锁定) | Service Worker Mock | src/mocks/**、public/mockServiceWorker.js | 升级必须重跑 npx msw init public/ --save(SW 与版本绑定,.prettierignore 豁免该文件) |
| @umijs/openapi | ^1.14.1 | OpenAPI → service 生成器 | scripts/openapi.mjs(CJS 包经 createRequire 加载) | 生成模板变动直接反映为生成物 diff |
| vitest | ^5.0.0 | 单测框架 | vitest.config.ts(独立于构建链) | 跟随 vite 版本线 |
| @vitest/coverage-v8 | ^5.0.0 | 覆盖率 | pnpm test:coverage | 与 vitest 同步 |
| happy-dom | ^20.14.5 | 测试 DOM 环境 | vitest.config.ts environment | API 缺口由 tests/setupTests.ts polyfill 兜底 |
| @testing-library/react | ^16.3.3 | 组件测试渲染 | Menu/OfflineBanner 测试 | 跟随 react 19 |
| @testing-library/jest-dom | ^7.0.1 | DOM 断言扩展 | tests/setupTests.ts | 稳定 |
| @testing-library/user-event | ^14.6.7 | 交互模拟 | 已备未用(当前用例未交互) | 稳定 |
| vite-plugin-checker | ^0.14.5 | dev 期 TS 错误浮层 | build/plugins.ts(无条件启用) | 跟随 vite |
| vite-plugin-compression | ^0.5.1 | gzip/brotli 预压缩 | build/plugins.ts(VITE_BUILD_COMPRESS 开关) | 稳定 |
| vite-plugin-pwa | ^1.3.0 | PWA 与离线回退 | build/plugins.ts(VITE_PWA 开关,workbox 配置含 4MiB 上限与 navigateFallback) | workbox 上游变更影响预缓存行为 |
| rollup-plugin-visualizer | ^7.1.1 | 体积分析 | build/plugins.ts(VITE_REPORT 开关产出 stats.html) | 跟随 rollup |
| @types/react / react-dom | ^19.3.0 | 类型 | 全仓 | 跟随 react |
| @types/react-transition-group | ^4.4.12 | 类型 | 对应依赖零引用 | 随 react-transition-group 一并清理 |
| @types/md5 / nprogress / qs / node | 各 ^22/^2 | 类型 | 对应依赖 | qs 类型随 qs 清理;node 类型供构建脚本 |
| @vitejs/plugin-react 之外的 dev 依赖共 32 项 | — | — | — | — |
Vite 在本项目中扮演四个角色。角色一是动态路由的物理基础:import.meta.glob 是 Vite 专属编译期能力,ConvertRouter 的整条链路(视图收集 → 键匹配 → lazy chunk)建立在它之上,这也是为什么换构建工具等于换掉动态路由方案(ADR-07)。角色二是环境分化:loadEnv + wrapperEnv(build/getEnv.ts)把五份 .env 编译期注入,VITE_ENABLE_MOCK 的字符串比较暗坑也源于 Vite "一切皆字符串"的注入语义(见《14-配置与环境详解》2.2)。角色三是构建期注入:define 把 __APP_INFO__ 与 __COMMIT_HASH__ 编进产物,这是独立于 .env 的"构建时才可知"信息通道。角色四是分包策略宿主:manualChunks 三分 vendor(react/antd/echarts)+ chunkSizeWarningLimit: 1500,直接决定缓存命中率(见《10-性能设计与优化》2.2)。
四个开关插件(build/plugins.ts,条件 && 工厂函数 + filter(Boolean) 的清单式组装)各司其职:checker 让类型错误在浏览器浮层(开发体验);compression 按 VITE_BUILD_COMPRESS 产出 .gz/.br(当前默认 none,开启零代码);PWA 的 workbox 配置有两处项目特有参数——maximumFileSizeToCacheInBytes: 4MiB(注释点名 vendor-antd 超默认 2MiB)与 navigateFallbackDenylist: [/^\/api\//](接口请求不回退);visualizer 产出 stats.html(性能测量入口)。插件清单的可读性设计值得照抄:新增可开关插件时加一行 条件 && 插件() 即可,禁用项被 filter 干净剔除。
antd 6 是最大的运行时依赖(vendor-antd 约 2.2MB,历史实测见 docs/bug-audit.md),也是选型上最重的决策:替代原自研 @xiaoye-react/ui 后,组件维护成本转嫁给 antd 团队,代价是接受它的 API 变更节奏与体积。配套两件直接决定体验:@ant-design/cssinjs 的 StyleProvider layer 是 ADR-02 共存机制的一半(另一半是 global.css 的层序声明),两者必须成对存在——升级 antd 时 cssinjs 必须同步,这是清单中耦合最强的一对;@ant-design/icons 则藏着一个体积陷阱:import { XOutlined } from "@ant-design/icons" 的桶导出会把全量图标拉进包,优化路径(深路径导入或按需注册)已在《10-性能设计与优化》7 登记。项目对 antd 6 的适配沉淀在三处源码注释里:Watermark 空 content 抛 InvalidStateError(src/layouts/index.tsx 关水印时卸载组件而非传空 content)、mask.closable 新写法(useMessage 的 modal 封装)、algorithm 叠加(ThemeProvider)。
Tailwind v4 的选型本质是一次"减法":官方 Vite 插件取代了 v3 时代的 postcss.config.js + autoprefixer + tailwind.config.js 三件套,设计令牌直接写在 src/styles/global.css 的 @theme 块里并被工具类自动消费(--color-brand-500 → bg-brand-500),@utility 提供项目级自定义工具类(scrollbar-thin)。这份"CSS-first"与 ADR-02 的层序机制互为条件:Tailwind 的 theme/base/utilities 三个层名恰好是层序声明的四个成员之三,说明 v4 内生就是 layer-aware 的——这层兼容是"Tailwind 4 + antd 6"能优雅共存的技术前提,若回退 v3(无原生 layer 语义),@layer 定序方案需要重新设计。升级风险集中在 @theme 令牌与 dark: 自定义变体(html.dark class 由 useTheme 切换)两个项目自定义点上。
msw 是全清单唯一精确锁定版本的依赖,原因链完整:public/mockServiceWorker.js 是 msw 包生成的版本绑定文件,带完整性校验(所以 .prettierignore 明确豁免它,注释写明"被 Prettier 改写会导致校验失败"),版本错位的症状是请求穿透 mock(onUnhandledRequest: "warn" 告警)。MSW 在项目中的角色超出"mock 工具"——它是 ADR-06 演示模式的执行者、ADR-04 契约三对齐的一角、开发态按钮权限重试窗口的保护对象,升级决策应视为架构决策而非例行维护。测试侧的额外红利:浏览器端 mock 让未来 Playwright E2E 无需真实后端(《09-测试与质量保障》6 的 P3 路线)。
i18next + react-i18next 的组合满足一个硬需求:非 React 场景的翻译。checkStatus/errorHandler(axios 拦截器侧)、ErrorBoundary、OfflineBanner 都通过直接 i18n.t 取文案,这是自研映射或 context-only 方案做不到的。词表组织(17 个命名空间、zh/en 各约 990 行)与防御式消费写法(t("menu." + key, { defaultValue: meta.title }))见《05-核心流程》流程三。
zustand 的不可替代点是 getState() 让非 React 环境读写 store 成为可能——axios 拦截器实时读 token(useUserStore.getState().token)、main.tsx 预置登录态、ErrorBoundary 判断登录态,全都依赖这一能力;persist 中间件则承载 localStorage 双 store 的版本迁移(migrate "绝不抛错"纪律见《04-数据模型》2.1)。选择它而非 redux 的隐性理由:store 的方法由 zustand 自动推导,src/stores/interface/index.ts 只需声明数据字段,类型维护面减半。
axios 的价值集中在拦截器管线(信封判定、401 收敛、blob 直通)与 AxiosHeaders 兼容检查(config.headers.set 函数判断,1.x 内部结构变动的防护);泛型封装(get<T> 返回 Promise<ResultData<T>>)让信封类型在调用侧强约束。react-router-dom 选 6.x data router 而非 v7:loader 下发 meta 与 useLoaderData 消费是路由元信息链路的通道,代码中已预埋 v7_relativeSplatPath/v7_startTransition 两个 future flag,说明作者已为 v7 迁移铺路。echarts 走 echarts/core 按需注册(src/components/Echarts/config/index.ts),封装层补齐了 6 的 legend 布局回退(normalizeLegend)与 notMerge: true 防残留——升级 ECharts 大版本的适配成本被压进这一个文件。@dnd-kit 三包服务标签栏与拖拽表格两个场景,PointerSensor 的 10px 激活阈值与 -1 索引防护是实测调参的痕迹(《07-模块详解》7.2)。
运行时清单里有六个"小件"(dayjs、md5、nprogress、react-colorful、qs、react-transition-group),把它们放在一起观察能读出这个项目的依赖取舍哲学。留下的小件各有不可替代的理由:md5 只在登录/改密两处使用,但它是密码传输契约的一部分(去掉它等于改前后端约定,见《08-安全与权限设计》2.3);nprogress 两处使用且零配置;react-colorful 取代 antd 内置 ColorPicker 的理由是体积(2KB 级、零依赖,主题抽屉是高频交互件)。q与动画库的教训:qs 与 react-transition-group 则是反面样本——它们大概率来自"参考项目用了所以带上"的惯性,而不是本项目某个需求驱动。这组对照给出的判断方法可以复用:新增依赖前先写一句话说明"哪个文件、哪个场景、非它不可的理由是什么",写不出这句话就不装;反之已声明的依赖若连续两个迭代找不到使用位置,就进入清理候选。这条纪律与 ADR-02 的"机制性禁用"一脉相承:依赖清单的克制不是靠自觉,而是靠让"随手装一个"变得有解释成本。
另一个值得记录的观察是依赖与目录的对应关系。本项目没有一个"utils 大杂烩里塞第三方封装"的模式——每个第三方能力都有明确的宿主文件:图表封装在 src/components/Echarts/、全屏 loading 在 src/components/Loading/fullScreen.tsx、取色器封装在 ThemeDrawer/components/ColorPicker/、时间组件封装在 src/components/{DatePicker,RangePicker,TimePicker}/。这意味着替换任何一个第三方库的影响面都被限制在单一封装文件内(例如 ECharts 6 的 legend 破坏性变更只落在 normalizeLegend 一处),依赖升级的回归成本与封装层的厚度成反比。二次开发者沿用这个模式:引入新第三方库时先建封装文件、页面只消费封装 API,让"换库"永远是一个文件的改动而不是全仓 grep。
升级依赖时的风险分级与操作要点:
| 级别 | 依赖 | 升级动作 |
|---|---|---|
| 禁止例行升级 | typescript(5.9.x)、msw(2.15.0) | TS 需等 typescript-eslint 放开 peer;msw 升级必须 npx msw init public/ --save 并回归全部演示链路 |
| 谨慎大版本 | antd、@ant-design/cssinjs、react、react-router-dom、echarts | 逐项回归:antd 验 Watermark/modal/algorithm 三处已记录的适配;react 验 KeepAlive;router 验 future flag 全开后的行为;echarts 验 legend 适配层 |
| 常规跟随 | 其余全部 | pnpm update + 四连门禁(typecheck/lint/test/build) |
| 构建期联动 | vite 与全部 vite-plugin-* / @vitejs/* / vitest | 建议整组同步升级,避免 peer 冲突 |
CI 升级回归的最小用例集(来自现有测试与人工清单):safeRedirect 与 errorHandler 单测、Menu 手风琴测试,外加"登录 → 出菜单 → 深链刷新 → 登出"人工四步(见《09-测试与质量保障》4.1 的门禁序列与《06-部署运行》8 的验收项)。
正向指标。其一,声明-使用一致率高:53 项依赖中 51 项能落到真实使用位置(本篇逐项核实),清单无"防御性声明"(先把可能用的装上)。其二,重量分布合理:运行时依赖无巨型框架叠加(无 SSR 元框架、无双状态库、无双组件库),vendor 三分包后业务代码体积占比健康。其三,测试栈与构建栈解耦(vitest 独立配置),依赖升级互不牵连。其四,幽灵依赖为零:pnpm 严格结构下全部直接 import 都有声明,@ant-design/cssinjs 的显式声明是这条纪律的活样本。
风险项。风险一,两个零引用运行时依赖:qs(仅 src/enums/httpEnum.ts 注释提及)与 react-transition-group(含 @types/react-transition-group)全仓无 import,属原项目遗留——运行时体积影响近零(tree-shaking 会剔除),但它们污染依赖审计信号、误导"这里用了动画库/qs"的读者判断,建议连同注释一并清理(与《13-技术债务与演进建议》D8 死资源清理同批)。风险二,双版本耦合对:antd ↔ @ant-design/cssinjs、vite ↔ 全部 vite-plugin-*/vitest,升级必须成组操作,建议在 lockfile 变更时整组核对。风险三,宽范围 ^ 下的漂移面:53 项中 51 项宽范围,锁文件是唯一稳定凭证,CI 与 sync.sh 必须坚持 --frozen-lockfile(nas-deploy/sync.sh 已内置等价纪律)。风险四,上游 EOL 观察:react-transition-group 的清理决定同时消除了对 react 19 兼容性的一个潜在疑点;msw 2.x 的 Service Worker 机制在长期看需关注浏览器 SW 政策演进。
综合结论:依赖健康度评级为良(清单克制、使用透明、锁定策略明确;扣分项为零引用遗留与升级文档缺位)。维护建议三步:清理两个零引用项(半小时);把本篇第 5 节的升级矩阵随下一次依赖变更回填;新增依赖时先过"能不能用现有三件套(antd/Tailwind/ahooks)解决"的提问,守住 53 这个数字的克制。依赖清单和架构一样,最贵的不是加东西的成本,而是每个"也许有人用"留下的认知税。