项目:PEXELS 壁纸应用 | 版本:v1.2 | 生成日期:2026-10-06 | 依据:源码分析
本项目 package.json(包名 front)的直接依赖共 19 个:dependencies 8 个、devDependencies 11 个。与它退役前的形态相比(docs/dependency-upgrade-plan.md 记录了随账号体系一并移除的九个包:axios、md5、dayjs、vee-validate、ali-oss、cropperjs、driver.js、tailwind-scrollbar、vite-plugin-svg-icons),这份清单已经历过一次大瘦身,剩下的每一个包都能在源码里找到明确的消费点。本章的解析原则与《15-接口全量参考》一脉相承:不看包的自我介绍,看它在源码里的实际调用行。版本列同时给出 package.json 的语义化范围(^)与 pnpm-lock.yaml 实际锁定的版本(下称"锁定版")——范围决定"下次重装会升到哪",锁定版决定"此刻装的到底是哪个",两者都对判断升级风险有用。
按"用途域"先做一次概览分组。运行时框架域:vue、vue-router、pinia、pinia-plugin-persistedstate——这是唯一不可替换的根基组。浏览器原语域:@vueuse/core——一个包替代了自行实现交集观察、滚动监听、滚动锁的全部工作量。UI 能力域:gsap(弹层动画)、file-saver(跨域下载)、@lucide/vue(图标)。构建域:vite、@vitejs/plugin-vue、@tailwindcss/vite、tailwindcss、postcss-px-to-viewport-8-plugin。质量域:typescript、vue-tsc、prettier、prettier-plugin-tailwindcss。类型补全域:@types/file-saver、@types/node。没有 HTTP 客户端(原生 fetch 承担,见《00-项目总览》第 3 节)、没有测试框架(09 篇第 1 节实测无)、没有 lint(同前)、没有运行时校验库(15 篇第 7 节"信任 Pexels 契约"的取舍)。
依赖纪律有两条结构性约定需要先立住。其一,依赖方向纪律(《07-模块详解》第 13 节):libs 层不得 import views/api/store,因此"哪些包能出现在哪一层"是可静态推导的——gsap 与 file-saver 只被 src/components/ 引用,@vueuse/core 被指令、libs、utils、views 四层引用,@lucide/vue 只出现在 libs/icon-map.ts 与少数组件的静态具名导入。其二,分包纪律(vite.config.ts 第 13~16 行的分组常量):@vueuse/core、gsap、file-saver 各有专属 chunk(vendor-utils / vendor-animation / vendor-media),其余 node_modules 落 vendor-misc 兜底,chunkSizeWarningLimit: 300 是分包退化的唯一报警信号。引入任何新依赖时,这两条纪律决定它"能被谁 import"与"会落进哪个 chunk"。
| 依赖 | 版本范围 / 锁定版 | 用途 | 使用位置 | 选型理由 | 替代方案 | 升级风险 |
|---|---|---|---|---|---|---|
| vue | ^3.5.29 / 3.5.42 | UI 框架 | 全站 | Composition API + <script setup> + defineAsyncComponent + 模板编译 | Svelte/Solid | 大版本迁移成本极高;3.x 内小版本安全 |
| vue-router | ^4.6.4 / 4.6.4 | 路由 | src/router/index.ts;media-viewer 依赖其 history.state | createWebHistory 单路由表双端复用 | TanStack Router | media-viewer 读 history.state?.back 属弱契约(13 篇) |
| pinia | ^3.0.4 / 3.0.4 | 状态管理 | src/store/index.ts 三 store | 从 Vuex 迁移;Options 风格 API 与现有写法兼容 | zustand 类 | 3.x 内安全;store 写法为 Options,5.x 若弃用需改造 |
| pinia-plugin-persistedstate | ^4.7.1 / 4.7.1 | theme 持久化 | src/store/index.ts 第 9 行 pinia.use(...);仅 theme store persist: true | 唯一持久化需求,无需自研 | 手写 localStorage 同步 | 3.x→4.x 已踩坑:paths 改名 pick(dependency-upgrade-plan 踩坑 6) |
| @vueuse/core | ^13.9.0 / 13.9.0 | 浏览器交互原语 | useIntersectionObserver / useScroll / useEventListener / useScrollLock / useElementBounding / useVModel / useScrollRestore | 按需 tree-shaking,实测仅 6.7KB 进包(10 篇) | 各原语手写 | 中等;API 面大但使用点收敛,锁定单函数语义即可 |
| gsap | ^3.14.2 / 3.15.0 | 弹层进出场动画 | src/components/media-viewer/index.vue 三个 JS 钩子 | :css="false" 下完全受控的 JS 动画,从卡片中心点缩放 | CSS transition / @vueuse/motion | 独立 chunk(vendor-animation 69.6KB),仅详情链路加载 |
| file-saver | ^2.0.5 / 2.0.5 | 跨域文件下载 | media-viewer 与 media-hover-actions 的 saveAs | 处理跨域 blob 下载的命名问题 | 原生 <a download>(dependency-upgrade-plan"待评估"清单) | 2020 年后无维护;无已知漏洞 ⚠️ 待确认(以审计工具为准) |
| @lucide/vue | ^1.37.0 / 1.37.0 | 描边图标 | src/libs/icon-map.ts 字符串映射 + 各组件具名导入 | 描边风格统一、tree-shakable | 自绘 SVG 集 | 中等;fill-* 会涂实描边图形(升级文档踩坑 4) |
| 依赖 | 版本范围 / 锁定版 | 用途 | 使用位置 | 选型理由 | 替代方案 | 升级风险 |
|---|---|---|---|---|---|---|
| vite | ^8.2.2 / 8.2.2 | 构建 | vite.config.ts | rolldown 内核,构建快;手动分包语义化 | webpack/Rspack | 大版本跳升曾引发 postcss 位置问题(踩坑 2) |
| @vitejs/plugin-vue | ^6.0.8 / 6.0.8 | Vite 的 Vue 插件 | vite.config.ts plugins | 官方标配 | — | 随 vite 主版本联动升级 |
| tailwindcss | ^4.3.3 / 4.3.3 | 原子化样式 | src/styles/index.css(CSS-first 唯一配置点) | @theme/@utility/@custom-variant 全在 CSS,无 config.js | UnoCSS | 4.x 内安全;升级曾致 scrollbar 类静默失效(踩坑 1) |
| @tailwindcss/vite | ^4.3.3 / 4.3.3 | Tailwind 4 的 Vite 插件 | vite.config.ts plugins | Tailwind 4 官方集成方式,替代 PostCSS 插件链 | @tailwindcss/postcss | 随 tailwindcss 联动 |
| postcss-px-to-viewport-8-plugin | ^1.2.5 / 1.2.5 | px→vw 移动端适配 | vite.config.ts css.postcss(仅 include: [/\.vue/]) | 8-plugin 分支兼容 Vite 8 | 新版 postcss-px-to-viewport / 自写插件 | 低活跃度社区包,锁版本为主 ⚠️ 待确认(上游维护状态) |
| typescript | ^5.9.3 / 5.9.3 | 类型系统 | tsconfig strict 全家桶 | strict + noUncheckedIndexedAccess 是唯一自动门禁的底座 | — | 5.x 内安全 |
| vue-tsc | ^3.3.11 / 3.3.11 | SFC 类型检查 | pnpm type-check(vue-tsc --noEmit) | 模板内 props/emit 校验的前提(无 *.vue shim) | — | 与 vue/typescript 版本矩阵需配套 |
| prettier | ^3.5.3 / 3.9.6 | 格式化 | .prettierrc(semi:false 等) | 免争议格式化 | dprint | 低 |
| prettier-plugin-tailwindcss | ^0.6.14 / 0.6.14 | 类名排序 | 同上 | 类名顺序统一,diff 降噪 | — | 需与 prettier 版本匹配 |
| @types/file-saver | ^2.0.7 / 2.0.7 | file-saver 类型 | file-saver 导入点 | file-saver 无内置类型 | — | 跟随 file-saver |
| @types/node | ^25.3.3 / 25.9.5 | Node 类型 | tsconfig.node.json(vite.config.ts 用 path/url) | 配置文件类型检查需要 | — | 低 |
两点表格之外的版本注记。第一,prettier 的锁定版(3.9.6)与范围下限(^3.5.3)之间有多个小版本差,属于无害的补丁级漂移;第二,docs/dependency-upgrade-plan.md 的工具链表里列有 sass-embedded 1.97.3,但 .npmrc 的 optional=false(注释原文:"禁用全部可选依赖,使 vite 的 optional 依赖 sass 及其 @parcel/watcher 不被安装")使它实际不在安装树中,且源码无任何 .scss 文件——这是该文档自身的一处漂移(与 13 篇 D-06 的"39 个 SFC 全无 lang=ts"过时结论同类),升级前应以 pnpm-lock.yaml 与 node_modules 实况为准 ⚠️ 待确认(未逐项核对安装树全量)。
vue(3.5.42)是唯一的根基依赖,本项目的使用面有三个超出"模板 + 响应式"的特征值得记录。一是 <script setup lang="ts"> 的覆盖度已达 29 个 SFC 中 28 个(09 篇第 2.2 节实测),模板级 props 校验因此实际生效——这依赖 src/env.d.ts 刻意不写 declare module '*.vue' shim 的配套决策(07 篇第 2.2 节)。二是异步组件用得极重:src/libs/index.ts 的 import.meta.glob + defineAsyncComponent 让六个 m-* 积木各自成 chunk,三个列表页再用 defineAsyncComponent 包裹 media-viewer 把 gsap 挡在首屏之外;src/libs/index.ts 第 10~13 行的注释还记录了 Vue 依赖预构建的一个边角——message 组件必须排除在 glob 外,否则触发 [INEFFECTIVE_DYNAMIC_IMPORT]。三是 useEventListener 等 VueUse 能力隐式依赖组件的 effect scope 自动清理,这条"框架代管清理"的假设在指令层失效(src/directives/modules/lazy.ts 的 unmounted 必须手动 stop,07 篇第 7 节),升级 Vue 大版本时此类清理假设需要重审。
**vue-router(4.6.4)**的消费面很薄:一张两级路由表(src/router/index.ts,layout 嵌套 6 个子路由 + /404 + catchAll)+ useRouter/useRoute 的常规使用。真正的升级风险不在路由表而在一个隐蔽点——src/components/media-viewer/index.vue 第 277 行读取 history.state?.back 决定关闭时是 router.back() 还是 router.push(exitPath),而 state.back 是 vue-router 内部写入的字段、非公开契约(13 篇第 5 节"隐性契约"其三);vue-router 大版本若变更 history state 的结构,直达页的关闭行为会静默劣化(表现为"关闭时总是跳 exitPath"而非报错)。升级该包时的回归点只有一个:/pins/:id 直达页在有历史与无历史两种进入方式下分别关闭,观察落点。
**pinia(3.0.4)**的管理面刻意极薄:三个 Options 风格 store(app/category/theme),全部在 src/store/index.ts 一个文件里,63 行。选型理由可从迁移史反推——docs/dependency-upgrade-plan.md 记录了 vuex→pinia 的迁移与"vuex-persistedstate → pinia-plugin-persistedstate"的对应替换,迁移动机是 Composition API 时代的状态库默认解。升级风险集中在写法风格:3.x 对 Options 风格的支持没有弃用信号,但社区重心已在 Setup Store 语法,未来大版本若调整 Options 分发器的行为(如 this.searchText = '' 的跨 action 赋值),受影响的正是 changeCurrentCategory(同时清空 searchText 的语义内聚点,08 篇之外另见 05 篇第 1 节)。
**pinia-plugin-persistedstate(4.7.1)**是全项目唯一触碰 localStorage 的代码路径,消费点只有 src/store/index.ts 第 9 行的插件注册与第 62 行的 persist: true。它的版本历史里埋着本项目升级档案的第 6 条踩坑:"3.x 的 persist: { paths: [...] } 在 4.x 中改为 pick / omit"——这条对未来的意义在于:若把更多 store 加入持久化(例如《15-接口全量参考》第 5 节预演的 coverCache 落盘),必须用 4.x 的 pick 语法且记得查 CHANGELOG,3.x 时代的示例代码会静默不生效。
数一下它在源码里的实际调用点,就能理解为什么它值得单独一节:src/directives/modules/lazy.ts 与 src/views/collections/components/collection-item.vue 的 useIntersectionObserver(懒加载与封面按需入队的触发器);src/utils/helpers.ts 的 useScroll(滚动恢复的 y 值来源);src/libs/popup/index.vue 的 useScrollLock(弹层滚动锁);src/views/main/components/list/item.vue 的 useElementBounding(动画起点中心点);src/libs/waterfall/index.vue 的 useEventListener(滚动与 resize 监听的自动清理);popup/segmented/infinite-list 的 useVModel(双向绑定)。七个函数替代了七块手写工作量,且各自都是容易写错的领域(IntersectionObserver 的 rootMargin 边界、滚动锁的引用计数、事件监听的清理)。
选型视角下它有两点被低估的价值。其一,tree-shaking 效果实测极好:10 篇第 5 节的产物表里 vendor-utils 仅 6.7KB——@vueuse/core 的模块化打包让"只付所用函数的成本"成为现实,这也是《00-项目总览》第 3 节"为什么选 @vueuse/core 这么重"的量化答案。其二,它隐式统一了清理纪律:useEventListener 返回的 stop 函数在组件卸载时自动执行,瀑布流的滚动监听、键盘监听、resize 监听因此不需要手写 removeEventListener;但代价是产生了"清理由框架代管"的路径依赖——组件外(模块级)使用时必须手动持有 stop 函数,src/directives/modules/lazy.ts 的注释("指令钩子不一定在组件的 effect scope 内,不能依赖 vueuse 自动清理")是这条纪律的官方记录。升级风险中等:该库 API 面大、小版本迭代快,但本项目的使用点收敛于七个稳定函数,升级时锁定这七个函数的行为(尤其是 useScrollLock 与 useIntersectionObserver 的选项语义)即可。替代方案评估:逐个手写这七个原语约需 200~300 行且风险自担,不如跟随上游。
m-waterfall(src/libs/waterfall/index.vue + utils.ts,合计 573 行)不是依赖,但把它放进本篇讨论是刻意的:从维护视角看,它与一个第三方依赖的属性完全同构——有上游演进(无)、有版本锁定(无)、有行为契约(有,且靠注释维持)、有升级成本(改动即重构)、有 bug 风险面(09 篇档案的瀑布流组七条旧坑全在这里)。它"不进 package.json"的原因是 ADR-005(《16-决策记录ADR》):CSS columns 与第三方库都无法同时满足"宽高比预计算高度 + 向上查找滚动祖先 + ±400px 虚拟裁剪"三个约束,于是这三个约束的解法整体内化成了自研件。
作为"依赖"评估它的健康度:收益是零外部升级风险、与页面的滚动容器约定(02 篇第 6 节)深度咬合、注释密度 14%(75/544 行)意味着决策记录完整;成本是它成为全项目复杂度与回归风险最集中的单点,且没有任何测试锁定其核心算法(列高累加、最矮列定位、visibleIndexes 裁剪、sameIndexes 比对),09 篇第 5 节把它列为第一优先补测对象,理由正是"复杂度最高、注释最密、旧坑最多三者的交集"。维护建议沿用 13 篇 D-14 的排序:在动它之前先给它写测试,而不是先重构它——把"虚拟裁剪后 DOM 数量恒定""追加重排保留旧定位""容器不可见时列宽为 0"三条行为固化为断言,此后它才能像被良好维护的依赖一样被安全地演进。
file-saver 的处境在 docs/dependency-upgrade-plan.md"待评估的依赖"表里有一行官方定性:"2020 年后无维护,但功能稳定。可用原生 <a download> 替代,需处理跨域图片场景。"它在本项目的消费点只有两处:src/components/media-viewer/index.vue 的 onDownload(setTimeout(() => saveAs(...), 100),注释"延迟执行,先让提示消息渲染出来")与 src/components/media-hover-actions/index.vue 的同款下载。两处都只用了 saveAs(url, filename) 这一个 API。
停更风险评估要拆成两层。功能层风险很低:saveAs 的核心逻辑(blob URL 创建 + a[download] 触发 + revoke)随 Web 标准稳定而趋稳,本项目下载的又是 Pexels/Picsum 的公网媒体,无需 blob 转换的复杂路径;无已知漏洞 ⚠️ 待确认(以 pnpm audit 等工具的实时结论为准)。生态层风险是心理负担:@types/file-saver(2.0.7)尚有类型,但一旦未来 TS 或打包器的严格度提升,无人修复。替代路径在 13 篇 D-15 已有方案:"原生 a[download] + 跨域失败回退 file-saver 的封装替换并加测试"——注意"跨域"正是不能直接换原生方案的原因:<a download> 对跨域 URL 的 download 属性会被浏览器忽略(安全限制),这正是 file-saver 存在的理由,替换时必须保留 fetch→blob→saveAs 的跨域路径或接受"新标签打开"的降级体验。执行前提是 09 篇的测试集先就位(D-15 建议原文),当前建议维持锁定 2.0.5 不动。
严格说,md5 已经不是依赖:package.json 与 pnpm-lock.yaml 中均不存在它,它是随账号体系退役的九个包之一。本篇仍为它立一节,是因为它留下了一个教科书级的"幽灵依赖"标本——src/types/vendor.d.ts 全文仅剩 declare module 'md5' 一个声明,文件头自述"若安装了 @types/* 可移除"的条件永不满足。它的危害不在运行时(无任何代码 import 'md5'),而在认知误导:任何接手者读到这个文件都会合理推断"项目在某个地方算 md5",进而寻找不存在的签名链路(08 篇 S-06 与 13 篇 D-02 均已立案,处置建议为整文件删除并跑 pnpm type-check 验证)。
同类残留还有一处更隐蔽的:src/env.d.ts 的 ImportMetaEnv 中,VITE_BASE_API 被声明为必有字段(string 而非可选),而它的读取方已随 utils/request.ts 移除、env 模板中的占位也已删除——类型声明与运行时现实相反,import.meta.env.VITE_BASE_API 在运行时恒为 undefined(13 篇 D-03)。这两条残留共同构成一个维护教训:依赖的移除要连带清理四类痕迹——代码 import、类型声明、env 变量、文档描述,本项目前三类各漏了一半。其余八个已移除依赖(axios、dayjs、vee-validate、ali-oss、cropperjs、driver.js、tailwind-scrollbar、vite-plugin-svg-icons)未发现同类残留 ⚠️ 待确认(结论基于对 src/ 的 grep,未覆盖未来新增文件)。
vite(8.2.2,rolldown 内核)在本项目的配置面极小(vite.config.ts 99 行承担全部构建决策),但它与两个插件的版本联动是升级时最需要小心的矩阵:@vitejs/plugin-vue 6.x 对应 Vite 7/8 线,@tailwindcss/vite 4.x 的锁定版本甚至带着 vite@8.2.2 的 peer 解析后缀(pnpm-lock 中可见 4.3.3(vite@8.2.2...)),三者必须同批升级、不可单点跳跃。Vite 8 的 rolldown 换代已经留下两处档案级踩坑:postcss 配置位置问题(踩坑 2,必须配在 vite.config.ts)与可选依赖安装问题(.npmrc 的 optional=false 为规避 pnpm 11 报错而生),这两处是下次升级时首先要复查的开关。postcss-px-to-viewport-8-plugin(1.2.5)是清单里最"孤"的一个包——社区维护的低活跃度分支,本项目锁死它并依赖其 include: [/\.vue/] 与 mediaQuery: false 两个参数的精确语义(14 篇第 4.2 节逐参数解析),升级该包等同重验全部间距样式,建议以"永不主动升级"为默认策略。tailwindcss 4.x 自身的升级风险已在档案踩坑 1 演练过一次(scrollbar 工具类静默失效,现以 src/styles/index.css 的 @utility 自建对冲),后续 4.x 内升级需重验 @theme 变量与 @custom-variant dark 的行为。
以"供应连续性、安全暴露、升级通道、替代成本"四个维度给 19 个依赖 + 1 个自研件做健康度分级(数据基准日 2026-10-06,社区状态均为推断 ⚠️ 待确认,以当日 pnpm outdated / pnpm audit 实测为准):
健康区(绿,12 个):vue、vue-router、pinia、pinia-plugin-persistedstate、@vueuse/core、gsap、@lucide/vue、vite、@vitejs/plugin-vue、tailwindcss、@tailwindcss/vite、prettier 系——上游活跃、使用点收敛、升级路径有档案可循。其中 @vueuse/core 与 gsap 值得给予最高置信:前者 tree-shaking 后体积与 API 面都可控,后者被隔离在独立 chunk 且调用面只有三个动画钩子。
观察区(黄,5 个):typescript/vue-tsc(版本矩阵需联动,单升必炸);postcss-px-to-viewport-8-plugin(低活跃度,语义锁定,永不主动升级);@types/node(跟随 Node 版本,范围 ^25.3.3 与锁定 25.9.5 间有漂移);pinia(Options 风格写法对未来版本的假设未验证)。观察区的共同处置:保持锁定、升级时跑全量 pnpm type-check 与 06 篇第 5 节手工验收。
债务区(橙,3 个):file-saver(停更,D-15 有替换预案,等测试集就位后执行);md5 声明(幽灵依赖,删 src/types/vendor.d.ts);sass-embedded(档案列出但实际未安装的漂移条目,修订 docs/dependency-upgrade-plan.md)。加上 m-waterfall 作为"自研依赖"的单点风险(D-14 测试缺位),构成完整债务清单。
三条全景结论收尾。其一,这个依赖清单的形态本身就是 ADR-001 的镜像:8 个运行时依赖里没有一个与"业务能力"相关(全是框架与 UI 能力),所有业务能力(数据、降级、缓存、调度)都内化在 src/ 里,依赖树浅到可以直接画在一张图上。其二,升级安全性高度依赖两份既有档案——docs/dependency-upgrade-plan.md 的六条踩坑与《17》本篇的总表——升级前重读踩坑、升级后按 06 篇第 8 节的依赖升级指引跑验收,是这个项目被验证过的路径。其三,当前最大的结构风险不是任何一个外部包,而是"零测试 + 高注释依赖"的组合:全部行为契约(降级矩阵、瀑布流算法、序号状态机)都写在注释与文档里,09 篇 D-14 的最小测试集落地之前,任何依赖升级都是在注释的守护下走钢丝。