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'],
  },
})
相关推荐
PineappleCoder几秒前
为什么说发布 - 订阅是代码的 “万能胶水”?解耦逻辑全解析
前端·javascript·算法
我叫黑大帅4 分钟前
微信小程序分包:告别加载慢,像拆快递一样简单!
前端·微信小程序
今禾9 分钟前
深入解析HTTP协议:从OSI模型到HTTP/3.0的演进与实战优化
前端·http·面试
sure28210 分钟前
react native 编写一个歌词组件
前端·react native
言兴10 分钟前
面试题深度解析:localStorage、sessionStorage 与 Cookie —— 前端存储的三大基石
前端·javascript·面试
言兴11 分钟前
HTTP 各版本演进史:从文本传输到极致性能 —— 深度解析协议进化与工程实践
前端·javascript·面试
跟橙姐学代码12 分钟前
学Python像学做人:从基础语法到人生哲理的成长之路
前端·python
临期程序员13 分钟前
TypeError: crypto.getRandomValues is not a function
前端·vue.js
鹏多多.15 分钟前
flutter-使用device_info_plus获取手机设备信息完整指南
android·前端·flutter·ios·数据分析·前端框架
杨荧1 小时前
基于Python的电影评论数据分析系统 Python+Django+Vue.js
大数据·前端·vue.js·python