Monorepo + vite 怎么热更新

目录

[Monorepo + vite 为啥不能热更新](#Monorepo + vite 为啥不能热更新)

问题一:vite的缓存机制

文件系统缓存

浏览器缓存

[问题二:不会监听 node_modules 中的文件](#问题二:不会监听 node_modules 中的文件)

依赖预构建

原因

自动依赖搜寻

[Monorepo 和链接依赖](#Monorepo 和链接依赖)

自定义行为

optimizeDeps.exclude

optimizeDeps.include


Monorepo + vite 为啥不能热更新

我们在开始之前先来明确一下vite + Monorepo的场景下不能热更新的原因

先来给出解决方案 再来深度解析一下


写一个自定义插件

复制代码
/**
 * Vite 插件:监听 node_modules 中的特定模块
 * 解决 Vite Issue #8619 - 无法监听 node_modules 中特定依赖的问题
 * @param {string[]} modules - 需要监听的模块名数组
 */
export const pluginWatchNodeModules = (modules) => {
  // 将模块名合并为管道分隔的字符串,用于正则表达式的负向前瞻
  const pattern = `/node_modules\\/(?!${modules.join('|')}).*/`

  return {
    name: 'watch-node-modules',
    configureServer: (server) => {
      server.watcher.options = {
        ...server.watcher.options,
        // 忽略除了指定模块外的所有 node_modules 内容
        ignored: [new RegExp(pattern), '**/.git/**']
      }
    }
  }
}

然后使用它,plugins像这样传递到你的数组中:

复制代码
// Import from the separate file you might store this in, e.g. 'utils'
import { pluginWatchNodeModules } from './utils';

plugins: [
	// ... other plugins...
	pluginWatchNodeModules(['your-plugin', 'another-example']),
],

编辑: ps 不要忘记确保排除这些包,optimizeDeps如下所示:

复制代码
optimizeDeps: {
	exclude: [
		'your-plugin',
		'another-example',
	],
},

注意这里作者遇到一个问题如果你排除的包中有CommonJS规范的依赖应该配合include在强制编译为 ES 模块 比如 weixin-js-sdk

复制代码
    optimizeDeps: {
      // 排除workspace依赖的预构建,确保使用最新版本
      exclude: ['@cmpay/utils', '@cmpay/components'],
      // 包含weixin-js-sdk, 因为utils中使用了weixin-js-sdk
      include: ['weixin-js-sdk']
    },

问题一:vite的缓存机制

文件系统缓存

Vite 将预构建的依赖项缓存到 node_modules/.vite 中。它会基于以下几个来源来决定是否需要重新运行预构建步骤:

  • 包管理器的锁文件内容,例如 package-lock.jsonyarn.lockpnpm-lock.yaml,或者 bun.lockb
  • 补丁文件夹的修改时间;
  • vite.config.js 中的相关字段;
  • NODE_ENV 的值。

只有在上述其中一项发生更改时,才需要重新运行预构建。

如果出于某些原因你想要强制 Vite 重新构建依赖项,你可以在启动开发服务器时指定 --force 选项,或手动删除 node_modules/.vite 缓存目录。

浏览器缓存

已预构建的依赖请求使用 HTTP 头 max-age=31536000, immutable 进行强缓存,以提高开发期间页面重新加载的性能。一旦被缓存,这些请求将永远不会再次访问开发服务器。如果安装了不同版本的依赖项(这反映在包管理器的 lockfile 中),则会通过附加版本查询自动失效。如果你想通过本地编辑来调试依赖项,您可以:

  1. 通过浏览器开发工具的 Network 选项卡暂时禁用缓存;
  2. 重启 Vite 开发服务器指定 --force 选项,来重新构建依赖项;
  3. 重新载入页面。

正因为vite的缓存机制导致Vite 在本地加载你的站点之前预构建了项目依赖。并且后期更改

"workspace:^" 工作区的时候不会在重新构建

ps:不在 node_modules 中的,链接的包不会被预构建。使用此选项可强制预构建链接的包。

问题二:不会监听 node_modules 中的文件

Vite 服务器的文件监听器默认会监听 root 目录,同时会跳过 .git/node_modules/,以及 Vite 的 cacheDirbuild.outDir 这些目录。当监听到文件更新时,Vite 会应用 HMR 并且只在需要时更新页面。

监听 node_modules 中的文件

目前没有可行的方式来监听 node_modules 中的文件。若要了解更多详情和可能的临时替代方案,你可以关注 issue #8619

依赖预构建

当你首次启动 vite 时,Vite 在本地加载你的站点之前预构建了项目依赖。默认情况下,它是自动且透明地完成的。

原因

这就是 Vite 执行时所做的"依赖预构建"。这个过程有两个目的:

  1. CommonJS 和 UMD 兼容性: 在开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将以 CommonJS 或 UMD 形式提供的依赖项转换为 ES 模块。

    在转换 CommonJS 依赖项时,Vite 会进行智能导入分析,这样即使模块的导出是动态分配的(例如 React),具名导入(named imports)也能正常工作:

    复制代码
    // 符合预期
    import React, { useState } from 'react'
  2. 性能: 为了提高后续页面的加载性能,Vite 将那些具有许多内部模块的 ESM 依赖项转换为单个模块。

    有些包将它们的 ES 模块构建为许多单独的文件,彼此导入。例如,lodash-es 有超过 600 个内置模块!当我们执行 import { debounce } from 'lodash-es' 时,浏览器同时发出 600 多个 HTTP 请求!即使服务器能够轻松处理它们,但大量请求会导致浏览器端的网络拥塞,使页面加载变得明显缓慢。

    通过将 lodash-es 预构建成单个模块,现在我们只需要一个HTTP请求!

注意

依赖预构建仅适用于开发模式,并使用 esbuild 将依赖项转换为 ES 模块。在生产构建中,将使用 @rollup/plugin-commonjs

自动依赖搜寻

如果没有找到现有的缓存,Vite 会扫描您的源代码,并自动寻找引入的依赖项(即 "bare import",表示期望从 node_modules 中解析),并将这些依赖项作为预构建的入口点。预打包使用 esbuild 执行,因此通常速度非常快。

在服务器已经启动后,如果遇到尚未在缓存中的新依赖项导入,则 Vite 将重新运行依赖项构建过程,并在需要时重新加载页面。

Monorepo 和链接依赖

在一个 monorepo 启动中,该仓库中的某个包可能会成为另一个包的依赖。Vite 会自动侦测没有从 node_modules 解析的依赖项,并将链接的依赖视为源码。它不会尝试打包被链接的依赖,而是会分析被链接依赖的依赖列表。

然而,这需要被链接的依赖被导出为 ESM 格式。如果不是,那么你可以在配置里将此依赖添加到 optimizeDeps.includebuild.commonjsOptions.include 这两项中。

复制代码
export default defineConfig({
  optimizeDeps: {
    include: ['linked-dep'],
  },
  build: {
    commonjsOptions: {
      include: [/linked-dep/, /node_modules/],
    },
  },
})

当对链接的依赖进行更改时,请使用 --force 命令行选项重新启动开发服务器,以使更改生效。

自定义行为

有时候默认的依赖启发式算法(discovery heuristics)可能并不总是理想的。如果您想要明确地包含或排除依赖项,可以使用 optimizeDeps 配置项 来进行设置。

optimizeDeps.includeoptimizeDeps.exclude 的一个典型使用场景,是当 Vite 在源码中无法直接发现 import 的时候。例如,import 可能是插件转换的结果。这意味着 Vite 无法在初始扫描时发现 import ------ 只能在文件被浏览器请求并转换后才能发现。这将导致服务器在启动后立即重新打包。

includeexclude 都可以用来处理这个问题。如果依赖项很大(包含很多内部模块)或者是 CommonJS,那么你应该包含它;如果依赖项很小,并且已经是有效的 ESM,则可以排除它,让浏览器直接加载它。

你可以通过 optimizeDeps.esbuildOptions 选项 进一步自定义 esbuild。例如,添加一个 esbuild 插件来处理依赖项中的特殊文件,或者更改 build target

optimizeDeps.exclude

类型: string[]

在预构建中强制排除的依赖项。

CommonJS

CommonJS 的依赖不应该排除在优化外。如果一个 ESM 依赖被排除在优化外,但是却有一个嵌套的 CommonJS 依赖,则应该为该 CommonJS 依赖添

复制代码
export default defineConfig({
  optimizeDeps: {
    include: ['esm-dep > cjs-dep'],
  },
})

optimizeDeps.include

类型: string[]

默认情况下,不在 node_modules 中的,链接的包不会被预构建。使用此选项可强制预构建链接的包。

实验性: 如果你使用的是一个有很多深层导入的库,你也可以指定一个尾部的 glob 模式来一次性地预构建所有深层导入。这将避免在使用新的深层导入时不断地预构建。可以在此 提供反馈。例如:

复制代码
export default defineConfig({
  optimizeDeps: {
    include: ['my-lib/components/**/*.vue'],
  },
})
相关推荐
Cao_Ron7 分钟前
用 SVG image symbol 画 ECharts graph 圆形节点
前端
hxy060112 分钟前
React-Call让你的React组件像异步函数一样调用
前端·react.js
Hilaku13 分钟前
为什么 AI 写前端时,总是优先选择 React,而不是 Vue?
前端·javascript·程序员
京东云开发者15 分钟前
GPT-5.6、ChatGPT Work 与 Codex 更新食用指南
前端
Cao_Ron26 分钟前
用 ECharts graph 画出卡片式矩形节点
前端
skiyee34 分钟前
换个底座的 Wot Starter 居然让 AI 更懂项目!
前端·ai编程
anyup41 分钟前
这一套绝了!AI 大模型写故事,星云 SDK 驱动 3D 数字人实时演绎
前端·人工智能·aigc
耀耀切克闹灬1 小时前
短链跳转
前端
孟陬1 小时前
高质量全方位的 AI 工具 sse-stuntman — Mock SSE 接口
前端·node.js·openai
Cao_Ron1 小时前
给 ECharts 6 graph 节点叠加角标:从原理到性能优化
前端