Vite基础知识总结
本文介绍了 Vite 的一些常用功能笔记,方便后续记忆与复习。总有种写这个不如直接看文档的感觉QAQ
Vite 工作基本原理
传统基于
js
的bundler based
构建工具的慢启动以及热更新缓慢的问题。- 原因:
- 开发服务器需要将所有模块全部打包后才能在浏览器中呈现。
- 原生
js
的性能问题。
- 总结:
bundler-based
在大型项目下性能低下的原因在于,其开发服务器原理必须先打包才能使用,即便是HMR
也是离不开打包这一环节,随着项目体积或者模块体积的增大,打包这一环节会耗费大量的时间。
- 原因:
Vite 的解决方案
- 将项目的代码分为两类:
dependencies
与source code
:- 依赖:即
node_modules
文件夹下的文件,不常修改的,项目的依赖文件,vite
采用使用go
编写的esbuild
来打包这些文件,提升开发体验。 - 源码:通常包括
ts
,tsx
,scss
等文件,对这些源码的提供是通过原生的ESM
来实现的,vite
只需要转换并按需提供代码,让浏览器接管了bundler
的工作,实现了源码文件的按需加载。
- 依赖:即
Vite 中 HMR 的更新方式以及浏览器缓存解决方案
- 基于 ESM 的 HMR 解决方案:Vite 只需要将被修改的模块与其最近的 HMR 边界之间的链路失活,再次请求相应的模块文件(PS:通过
fetch
实现)。 - 使用 HTTP 请求头来实现重新加载页面的相关文件的缓存设置,使用响应头:
Etag
与 HTTP 状态码304 Not Modified
来判断源码文件是否更新,对依赖文件设置响应头Cache-Control: max-age=31536000,immutable
进行强制缓存。
- 基于 ESM 的 HMR 解决方案:Vite 只需要将被修改的模块与其最近的 HMR 边界之间的链路失活,再次请求相应的模块文件(PS:通过
尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。
了解Vite HMR基本原理
Vite HMR
是通过 动态import
和WebSocket
实现的。Vite HMR
的原理是这样的:Vite
在node
端使用chokidar
监听文件的变化,当文件发生变化时,会触发HMR
事件,并将变化的文件名和模块 ID 发送给客户端。Vite
在浏览器端使用WebSocket
与服务端建立连接,接收HMR
事件。当收到HMR
事件时,会根据文件名和模块 ID 找到对应的模块,并使用动态import
重新请求该模块的代码,返回更新回调。Vite
在浏览器端使用原生的ES Module
功能加载模块,当模块的代码更新时,会触发模块的更新函数(执行回调),实现热替换。
详细了解协商缓存
举个例子,假设客户端第一次请求一个图片文件,服务器返回 200 OK 的状态码,以及图片的内容,同时在响应头中设置了
Last-Modified: Wed, 10 Nov 2021 07:00:51 GMT
和Etag: "1234567890"
,表示该图片的最后修改时间和唯一标识。客户端会将这些信息和图片一起缓存到本地。当客户端再次请求该图片时,会在请求头中添加If-Modified-Since: Wed, 10 Nov 2021 07:00:51 GMT
和If-None-Match: "1234567890"
,表示只有当图片在这个时间之后被修改过,或者图片的标识发生变化时,才需要重新获取图片。如果服务器检查发现图片没有变化,就会返回304 Not Modified
的状态码,不会返回图片的内容,客户端就可以直接使用本地缓存的图片。如果服务器检查发现图片有变化,就会返回200 OK
的状态码,以及新的图片内容,客户端就会更新本地缓存,并显示新的图片。- 将项目的代码分为两类:
Vite 快速上手
基本命令
社区维护的模板awesome-vite
Vite 的特性
NPM 依赖解析和预构建
浏览器不支持类似下面的bare module imports
为了解决这个问题Vite
会完成两个工作
pre-bundle
:Vite
会使用esbuild
对这种类似的依赖进行打包。- 重写
url
:将url
重写为/node_modules/.vite/deps/my-dep.js?v=f3sf2ebd
这种格式方便浏览器识别引用(?v=f3sf2ebd
主要用来标识模块的版本,防止应依赖缓存导致的模块无法更新)。
客户端类型
由于Vite
的默认的类型定义为Node.js
环境下的API
,要补充到客户端的应用代码环境需要添加一个d.ts
声明文件
- 资源导入 (例如:导入一个
.svg
文件) import.meta.env
上Vite
注入的环境变量的类型定义import.meta.hot
上的HMR API
类型定义
需要覆盖默认的类型定义需要像下面这样做
vite-env-override.d.ts
env.d.ts
JSON 导入
对json
文件使用解构赋值能有效帮助tree-shaking
Glob 导入
Vite
支持使用特殊的import.meta.glob
函数从文件系统导入多个模块:匹配到的文件默认是懒加载的,通过动态导入实现,并会在构建时分离为独立的
chunk
。如果你倾向于直接引入所有的模块(例如依赖于这些模块中的副作用首先被应用),你可以传入{ eager: true }
作为第二个参数Glob 导入形式
import.meta.glob
都支持以字符串形式导入文件,类似于 以字符串形式导入资源。多个匹配模式
反面匹配模式
同样也支持反面
glob
匹配模式(以!
作为前缀)。若要忽略结果中的一些文件,你可以添加“排除匹配模式”作为第一个参数:具名导入
自定义查询
你也可以使用
query
选项来提供对导入的自定义查询,以供其他插件使用。你还需注意,所有
import.meta.glob
的参数都必须以字面量传入。你 不 可以在其中使用变量或表达式。
动态导入
注意变量仅代表一层深的文件名。如果 file
是 foo/bar
,导入将会失败。对于更进阶的使用详情,你可以使用 glob
导入 功能。
静态资源处理
显式 URL 引入
将资源引入为字符串
公共基础路径
公共基础路径是指你的项目在部署时的根路径,它会影响到你的静态资源的引用和加载。例如,如果你的项目是部署在
https://example.com/my-app/
下,那么你的公共基础路径就是/my-app/
。
配置 base
项或者配置启动参数 vite build --base=/my/public/path/
。
环境变量与模式
环境变量
Vite
在一个特殊的 import.meta.env
对象上暴露环境变量。这里有一些在所有情况下都可以使用的内建变量:
import.meta.env.MODE
:{string}
应用运行的模式。import.meta.env.BASE_URL
:{string}
部署应用时的基本URL
。他由base
配置项决定。import.meta.env.PROD: {boolean}
应用是否运行在生产环境。import.meta.env.DEV: {boolean}
应用是否运行在开发环境 (永远与import.meta.env.PROD
相反)。import.meta.env.SSR: {boolean}
应用是否运行在server
上。
.env
文件
Vite
使用 dotenv
从你的 环境目录 中的下列文件加载额外的环境变量:
为了防止意外地将一些环境变量泄漏到客户端,只有以VITE\_
为前缀的变量才会暴露给经过 vite
处理的代码。例如下面这些环境变量:
HTML 环境变量替换
Vite
还支持在 HTML
文件中替换环境变量。import.meta.env
中的任何属性都可以通过特殊的 %ENV_NAME%
语法在 HTML
文件中使用:
CSS 变量的导入
css.preprocessorOptions
类型: Record<string, object>
指定传递给 CSS 预处理器的选项。文件扩展名用作选项的键。每个预处理器支持的选项可以在它们各自的文档中找到:
sass/scss
- 选项。less
- 选项。styl/stylus
- 仅支持define
,可以作为对象传递。
所有预处理器选项还支持 additionalData
选项,可以用于为每个样式内容注入额外代码。请注意,如果注入的是实际的样式而不仅仅是变量时,那么这些样式将会在最终的打包产物中重复出现。
给 Vite 设置 proxy 代理转发
vite proxy
代理中的proxyoptions
的对象的配置是用来设置代理服务器的规则和行为的。proxyoptions
的对象可以包含以下的属性:
target
: 一个字符串,表示要转发请求的目标服务器的地址,必须以http://
或https://
开头,例如'http://localhost:3000'
。changeOrigin
: 一个布尔值,表示是否修改请求头中的origin
字段,使其与目标服务器的域名一致。这样可以避免一些基于域名的虚拟主机或者跨域检查的问题。默认为false
。rewrite
: 一个函数,表示是否重写请求的路径,去掉或添加一些前缀。这样可以让后端服务器正确地处理请求,而不会出现404
或者其他错误。函数的参数是请求的路径,返回值是重写后的路径,例如path => path.replace(/^\/api/, '')
。ws
: 一个布尔值,表示是否支持WebSocket
代理。默认为false
。secure
: 一个布尔值,表示是否验证目标服务器的SSL
证书。默认为true
。headers
: 一个对象,表示要添加或修改的请求头。对象的键是请求头的名称,值是请求头的内容,例如{'User-Agent': 'Mozilla/5.0'}
。followRedirects
: 一个布尔值,表示是否跟随目标服务器的重定向。默认为false
。timeout
: 一个数字,表示代理请求的超时时间,单位是毫秒。默认为0
,表示无限制。logLevel
: 一个字符串,表示代理服务器的日志级别,可以是'silent','error','warn','info','debug','verbose'
之一。默认为'silent'
,表示不输出任何日志。
请注意,如果使用了非相对的 基础路径 base
,则必须在每个 key
值前加上该 base
。
跨域请求是浏览器自己的行为,服务器与服务器之间的请求不存在跨域行为,所谓前端开发工具设置proxy
代理来实现避免同源策略的干扰的原理就是,设置一个本地服务器,将请求发往本地服务器,本地服务器再转发给远程服务器,从而避开跨域请求。核心为:服务器与服务器之间的请求不受浏览器同源策略的限制。