登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

Vite 依赖预构建缓存失效时的排查步骤

来源:17golang原创

时间:2026-10-03 21:46:34 451浏览 收藏

“我已经改了依赖,为什么 Vite 还在用旧代码?”遇到这类问题,先不要把 node_modules 整个删掉。Vite 的依赖预构建只发生在开发模式,异常通常落在三层之一:决定预构建结果的输入指纹、node_modules/.vite 文件系统缓存,或者浏览器对优化依赖请求的强缓存。先判断层级,再选择处理动作,通常几分钟内就能定位。

官方文档:https://vite.dev/guide/dep-pre-bundling

先确认失效发生在哪一层

Vite 首次启动开发服务器时,会扫描源码里的裸模块导入,并把发现的依赖预构建后写入 node_modules/.vite。它既要把 CommonJS、UMD 依赖转换成开发环境可使用的 ESM,也会把内部模块很多的 ESM 依赖合并,减少浏览器请求数量。

因此,“缓存失效”并不是一个单点问题。可以先按现象做快速判断:

  • 每次启动都重新执行依赖优化:优先检查锁文件、补丁目录、Vite 配置或 NODE_ENV 是否持续变化。
  • 终端显示已经重新优化,但页面仍像旧版本:优先排查浏览器缓存。
  • 新增依赖后启动阶段发生第二次重载:多半是初始扫描没有发现该依赖。
  • 本地链接包修改后没有生效:需要检查 linked package 的 ESM 形态,并主动强制重新优化。

Vite 依赖预构建缓存三层结构图

图1:Vite 依赖预构建的输入指纹、磁盘缓存与浏览器缓存三层结构。

Vite 依据什么判断需要重新预构建

官方文档列出的文件系统缓存输入包括包管理器锁文件内容、依赖补丁目录的修改时间、vite.config 中影响依赖优化的字段,以及 NODE_ENV。只要这些输入发生变化,下一次启动就可能重新执行预构建。反过来说,如果你修改的是不会进入这些输入的本地链接关系,Vite 也可能不知道需要更新。

排查时先比较“上一次正常启动”和“当前启动”之间发生了什么,而不是直接清空所有依赖:

# 查看锁文件是否出现了意外改动
git diff -- package-lock.json pnpm-lock.yaml yarn.lock

# 查看 Vite 配置是否被脚本或环境变量改写
git diff -- vite.config.js vite.config.ts

# 确认当前开发进程使用的环境值
echo "$NODE_ENV"

如果团队成员的包管理器版本不同,锁文件格式或内容可能反复变化;如果 Vite 配置根据时间、随机值或机器路径生成 optimizeDeps,也会让依赖哈希不稳定。应当修复这些输入,而不是把清缓存写成每次启动都执行的脚本。

怎样用最短路径恢复一次正确预构建

确认依赖内容确实改变后,优先使用 Vite 提供的 --force。它会忽略已有的优化依赖缓存并重新预构建,比删除整个 node_modules 更小、更可控。

# 直接启动 Vite,并强制重新执行依赖预构建
npx vite --force

# 通过项目中的 dev 脚本把 --force 继续传给 Vite
npm run dev -- --force

如果项目需要临时通过配置强制优化,也可以使用 optimizeDeps.force。不过它适合短期诊断,不建议长期保持为 true,否则每次启动都会失去缓存收益。

import { defineConfig } from 'vite'

export default defineConfig({
  optimizeDeps: {
    // 仅用于定位缓存问题,确认后应改回 false 或删除该项
    force: true,
  },
})

若强制预构建后页面仍然显示旧代码,要处理浏览器这一层。Vite 会给已解析的依赖请求设置长期强缓存,并通过 URL 上的版本查询参数自动失效。调试本地依赖时,可临时在开发者工具的 Network 面板禁用缓存,随后用 --force 重启 Vite,再重新加载页面。只删 node_modules/.vite 而不刷新浏览器,可能仍看到旧响应。

配置导致的重复预构建怎么处理

Vite 默认扫描 HTML 入口;如果配置了构建输入,则使用对应入口。当插件转换、按条件加载或深层导入让依赖无法在初始扫描中出现时,开发服务器启动后才发现新依赖,就可能立即重新预构建并触发页面重载。

optimizeDeps.entries 用于明确扫描入口,include 用于强制预构建依赖,exclude 则让适合直接由浏览器加载的小型 ESM 依赖留在优化范围之外。CommonJS 依赖不应直接排除;如果被排除的 ESM 依赖内部又依赖 CommonJS,应把嵌套 CommonJS 依赖加入 include。

import { defineConfig } from 'vite'

export default defineConfig({
  optimizeDeps: {
    // 覆盖默认入口推断,显式扫描应用与管理端入口
    entries: ['index.html', 'admin/index.html'],

    // 强制预构建扫描阶段难以发现或属于 CommonJS 的依赖
    include: ['linked-ui-kit', 'esm-wrapper > legacy-cjs-lib'],

    // 只排除体积小且原生 ESM 完整的依赖
    exclude: ['tiny-esm-helper'],
  },
})

Vite 依赖优化配置边界图

图2:Vite 入口扫描、优化配置和依赖类型之间的静态边界关系。

monorepo 和本地链接包为什么更容易出问题

在 monorepo 中,Vite 会把不从 node_modules 解析出来的 linked package 当作源码处理,默认不会预构建它,而是继续分析其依赖列表。这个 linked package 应当能以 ESM 形式使用;如果它不是 ESM,可以把包名加入 optimizeDeps.include 强制预构建。

修改 linked package 后,应使用 --force 重启开发服务器。Vite 的故障排查文档还特别指出,使用 npm link 建立或解除链接时,缓存不会仅凭链接关系自动失效。能使用包管理器 overrides 或 resolutions 表达依赖替换时,这种方式更容易被锁文件记录,也更利于团队复现。

# linked package 改动后,强制生成新的优化依赖
npm run dev -- --force

# 在 pnpm 工作区中确认实际解析到的依赖版本
pnpm why linked-ui-kit

有哪些常见误区

误区一:任何异常都删除 node_modules

删除全部依赖会掩盖真正变化的输入,还会引入重新安装带来的锁文件或平台差异。优先使用 --force,必要时只删除 node_modules/.vite。

误区二:把 optimizeDeps.force 永久打开

这会让开发启动反复付出预构建成本。正确做法是用它验证缓存是否为原因,然后修正锁文件、配置或依赖发现边界。

误区三:把 CommonJS 依赖放进 exclude

开发环境以原生 ESM 提供代码,CommonJS 依赖需要经过优化转换。应优先放入 include,而不是绕过预构建。

一份可复用的排查清单

  1. 记录现象:是每次启动都重建、启动后二次重载,还是页面仍显示旧依赖。
  2. 检查锁文件、补丁目录、Vite 配置与 NODE_ENV 是否发生预期外变化。
  3. 用 npm run dev -- --force 做一次最小范围的强制预构建。
  4. 在浏览器开发者工具中临时禁用缓存,并重新加载页面。
  5. 若启动后才发现依赖,用 entries 或 include 固化扫描范围。
  6. 若是 monorepo 链接包,确认其 ESM 输出;否则加入 include,修改后用 --force 重启。
  7. 最后再考虑删除 node_modules/.vite,通常无需重装全部依赖。

延伸问题

预构建缓存会影响生产构建吗?

不会直接影响。Vite 的依赖预构建器只用于开发模式,生产构建走独立的构建链路。遇到生产包异常,应从构建配置、插件和产物缓存另行排查。

可以直接删除 node_modules/.vite 吗?

可以,官方也把它列为强制重新预构建的方法之一。但日常排查优先使用 --force,命令意图更明确,也方便写入复现步骤。

为什么改了本地依赖,版本查询参数没有变化?

版本查询参数与预构建结果及锁文件信息相关。本地链接关系尤其是 npm link 的变化,不一定进入自动失效输入,因此要用 --force 重新优化,并同步刷新浏览器缓存。

归根结底,Vite 预构建缓存排查的关键不是“删得更彻底”,而是明确哪一层没有感知变化:输入指纹稳定性、磁盘优化结果、浏览器强缓存,还是依赖扫描边界。按层处理,既能快速恢复开发环境,也能避免把缓存清理变成长期负担。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>