Open wbccb opened 1 year ago
当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。基于 JavaScript 开发的工具就会开始遇到性能瓶颈:通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。
Vite 旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES 模块,且越来越多 JavaScript 工具使用编译型语言编写。
调试时敲下命令后到打开页面所需要的时间
当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。
Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。
依赖 大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS)。
Vite 将会使用 esbuild 预构建依赖。esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。
源码 通常包含一些并非直接是 JavaScript 的文件,需要转换(例如 JSX,CSS 或者 Vue/Svelte 组件),时常会被编辑。同时,并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)。
Vite 以 原生 ESM 方式提供源码。这实际上是让浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前屏幕上实际使用时才会被处理。
部分模块更新时,热更新成功所需要的时间
基于打包器启动时,重建整个包的效率很低。原因显而易见:因为这样更新速度会随着应用体积增长而直线下降。
一些打包器的开发服务器将构建内容存入内存,这样它们只需要在文件更改时使模块图的一部分失活[1],但它也仍需要整个重新构建并重载页面。这样代价很高,并且重新加载页面会消除应用的当前状态,所以打包器支持了动态模块热替换(HMR):允许一个模块 “热替换” 它自己,而不会影响页面其余部分。这大大改进了开发体验 —— 然而,在实践中我们发现,即使采用了 HMR 模式,其热更新速度也会随着应用规模的增长而显著下降。
在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活[1](大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。
Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified 进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。
尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。
虽然 esbuild 快得惊人,并且已经是一个在构建库方面比较出色的工具,但一些针对构建 应用 的重要功能仍然还在持续开发中 —— 特别是代码分割和 CSS 处理方面。就目前来说,Rollup 在应用打包方面更加成熟和灵活。尽管如此,当未来这些功能稳定后,我们也不排除使用 esbuild 作为生产构建器的可能。
Vite 意在提供开箱即用的配置,同时它的 插件 API 和 JavaScript API 带来了高度的可扩展性,并有完整的类型支持。
开箱即用意味着很多类型文件的解析不需要像webpack.config.js那样进行配置,比如解析node_modules对应的模块时会自动补全路径
webpack.config.js
node_modules
使用 NPM:
$ npm create vite@latest
使用 Yarn:
$ yarn create vite
使用 PNPM:
$ pnpm create vite
对非常基础的使用来说,使用 Vite 开发和使用一个静态文件服务器并没有太大区别。然而,Vite 还通过原生 ESM 导入提供了许多主要用于打包场景的增强功能。
可以理解为Vite起了一个本地服务器,然后又添加了很多增加功能用于打包,下面将描述提供的增加功能
原生 ES 导入不支持下面这样的裸模块导入:
import { someMethod } from 'my-dep'
但是Vite会检测到这些裸模块的导入,并执行下面的操作:
预构建 它们可以提高页面加载速度,并将 CommonJS / UMD 转换为 ESM 格式。
预构建这一步由 esbuild 执行,这使得 Vite 的冷启动时间比任何基于 JavaScript 的打包器都要快得多。
重写导入为合法的 URL,将node_modules预构建(模块中多个import预构建成为单个文件,减少网络加载的文件数量)的模块放入缓存中,以便浏览器能够正确导入它们。并且依赖是强缓存的。
例如 /node_modules/.vite/deps/my-dep.js?v=f3sf2ebd
Vite 会将预构建的依赖缓存到 node_modules/.vite。它根据几个源来决定是否需要重新运行预构建步骤:
包管理器的 lockfile 内容,例如 package-lock.json,yarn.lock,pnpm-lock.yaml,或者 bun.lockb 补丁文件夹的修改时间 可能在 vite.config.js 相关字段中配置过的 NODE_ENV 中的值 只有在上述其中一项发生更改时,才需要重新运行预构建。
如果出于某些原因,你想要强制 Vite 重新构建依赖,你可以用 --force 命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录。
解析后的依赖请求会以 HTTP 头 max-age=31536000,immutable 强缓存,以提高在开发时的页面重载性能。一旦被缓存,这些请求将永远不会再到达开发服务器。如果安装了不同的版本(这反映在包管理器的 lockfile 中),则附加的版本 query 会自动使它们失效。如果你想通过本地编辑来调试依赖项,你可以:
max-age=31536000,immutable
通过浏览器调试工具的 Network 选项卡暂时禁用缓存; 重启 Vite dev server,并添加 --force 命令以重新构建依赖; 重新载入页面。
--force
Vite 提供了一套原生 ESM 的 HMR API。 具有 HMR 功能的框架可以利用该 API 提供即时、准确的更新,而无需重新加载页面或清除应用程序状态。Vite 内置了 HMR 到 Vue 单文件组件(SFC) 和 React Fast Refresh 中。也通过 @prefresh/vite 对 Preact 实现了官方集成。
注意,你不需要手动设置这些 —— 当你通过 create-vite 创建应用程序时,所选模板已经为你预先配置了这些。
create-vite
Vite 天然支持引入 .ts 文件。
.ts
Vite 仅执行 .ts 文件的转译工作,并不执行 任何类型检查。并假定类型检查已经被你的 IDE 或构建过程处理了。
Vite 之所以不把类型检查作为转换过程的一部分,是因为这两项工作在本质上是不同的。转译可以在每个文件的基础上进行,与 Vite 的按需编译模式完全吻合。相比之下,类型检查需要了解整个模块图。把类型检查塞进 Vite 的转换管道,将不可避免地损害 Vite 的速度优势。
Vite 的工作是尽可能快地将源模块转化为可以在浏览器中运行的形式。为此,我们建议将静态分析检查与 Vite 的转换管道分开。
你可以在构建生产版本时,你可以在 Vite 的构建命令之外运行tsc --noEmit进行类型检查 在开发时,如果你需要更多的 IDE 提示,我们建议在一个单独的进程中运行tsc --noEmit --watch,或者如果你喜欢在浏览器中直接看到上报的类型错误,可以使用vite-plugin-checker。
你可以在构建生产版本时,你可以在 Vite 的构建命令之外运行tsc --noEmit进行类型检查
tsc --noEmit
在开发时,如果你需要更多的 IDE 提示,我们建议在一个单独的进程中运行tsc --noEmit --watch,或者如果你喜欢在浏览器中直接看到上报的类型错误,可以使用vite-plugin-checker。
tsc --noEmit --watch
Vite 使用 esbuild 将 TypeScript 转译到 JavaScript,约是 tsc 速度的 20~30 倍,同时 HMR 更新反映到浏览器的时间小于 50ms。
tsc
tsconfig.json 中 compilerOptions 下的一些配置项需要特别注意。
tsconfig.json
compilerOptions
isolatedModules: true
isolatedModules
true
这是因为 esbuild 只执行没有类型信息的转译,它并不支持某些特性,如 const enum 和隐式类型导入。 你必须在 tsconfig.json 中的 compilerOptions 下设置 "isolatedModules": true。如此做,TS 会警告你不要使用隔离(isolated)转译的功能。 然而,一些库(如:vue)不能很好地与 "isolatedModules": true 共同工作。你可以在上游仓库修复好之前暂时使用 "skipLibCheck": true 来缓解这个错误。 如果你的代码库很难迁移到 "isolatedModules": true,或许你可以尝试通过第三方插件来解决,比如 rollup-plugin-friendly-type-imports。但是,这种方式不被 Vite 官方支持。
这是因为 esbuild 只执行没有类型信息的转译,它并不支持某些特性,如 const enum 和隐式类型导入。
你必须在 tsconfig.json 中的 compilerOptions 下设置 "isolatedModules": true。如此做,TS 会警告你不要使用隔离(isolated)转译的功能。
然而,一些库(如:vue)不能很好地与 "isolatedModules": true 共同工作。你可以在上游仓库修复好之前暂时使用 "skipLibCheck": true 来缓解这个错误。
如果你的代码库很难迁移到 "isolatedModules": true,或许你可以尝试通过第三方插件来解决,比如 rollup-plugin-friendly-type-imports。但是,这种方式不被 Vite 官方支持。
useDefineForClassFields: true
useDefineForClassFields
大多数库都希望 "useDefineForClassFields": true,如 MobX,Vue Class Components 8.x 等。 但是有几个库还没有兼容这个新的默认值,其中包括 lit-element。如果遇到这种情况,请将 useDefineForClassFields 设置为 false。
大多数库都希望 "useDefineForClassFields": true,如 MobX,Vue Class Components 8.x 等。
但是有几个库还没有兼容这个新的默认值,其中包括 lit-element。如果遇到这种情况,请将 useDefineForClassFields 设置为 false。
.jsx 和 .tsx 文件同样开箱即用。JSX 的转译同样是通过 esbuild。
.jsx
.tsx
Vue 用户应使用官方提供的 @vitejs/plugin-vue-jsx 插件,它提供了 Vue 3 特性的支持,包括 HMR,全局组件解析,指令和插槽。
@vitejs/plugin-vue-jsx
你可以使用 jsxInject(这是一个仅在 Vite 中使用的选项)为 JSX 注入 helper,以避免手动导入:
jsxInject
// vite.config.js import { defineConfig } from 'vite' export default defineConfig({ esbuild: { jsxInject: `import React from 'react'`, }, })
导入 .css 文件将会把内容插入到 <style> 标签中,同时也带有 HMR 支持。也能够以字符串的形式检索处理后的、作为其模块默认导出的 CSS。
.css
<style>
Vite 通过 postcss-import 预配置支持了 CSS @import 内联,Vite 的路径别名也遵从 CSS @import。
postcss-import
@import
Sass 和 Less 文件也支持 @import 别名和 URL 自动重新设置基准以确保正确性
Vite 同时提供了对 .scss, .sass, .less, .styl 和 .stylus 文件的内置支持。没有必要为它们安装特定的 Vite 插件,但必须安装相应的预处理器依赖:
.scss
.sass
.less
.styl
.stylus
# .scss and .sass npm add -D sass # .less npm add -D less # .styl and .stylus npm add -D stylus
PostCSS不是一个预处理器(尽管它的行为很像)。 它是一个Node.js工具,它采用有效的CSS并对其进行增强。即使是那些使用Sass、Less或Stylus的人,也经常在初始CSS编译后运行PostCSS步骤。你可能遇到过PostCSS的Autoprefixer插件 ,它可以在需要前缀的CSS属性中自动添加 -webkit, -moz, and -ms 前缀。 就其本身而言,PostCSS什么也不做。它是一个对CSS代码进行标记以创建抽象语法树的解析器。一个插件可以处理这个树并相应地更新属性。一旦所有插件完成了它们的工作,PostCSS将所有内容重新格式化为字符串并输出到CSS文件中。 大约有 350 个插件可用,大多数都是执行单一的任务,如 代码嵌入 @import 声明,简化 calc() 函数, 处理图片资源, 语法提示, 压缩等等。 在PostCSS的插件目录中,有一个更方便用户的插件搜索。
PostCSS不是一个预处理器(尽管它的行为很像)。 它是一个Node.js工具,它采用有效的CSS并对其进行增强。即使是那些使用Sass、Less或Stylus的人,也经常在初始CSS编译后运行PostCSS步骤。你可能遇到过PostCSS的Autoprefixer插件 ,它可以在需要前缀的CSS属性中自动添加 -webkit, -moz, and -ms 前缀。
就其本身而言,PostCSS什么也不做。它是一个对CSS代码进行标记以创建抽象语法树的解析器。一个插件可以处理这个树并相应地更新属性。一旦所有插件完成了它们的工作,PostCSS将所有内容重新格式化为字符串并输出到CSS文件中。
大约有 350 个插件可用,大多数都是执行单一的任务,如 代码嵌入 @import 声明,简化 calc() 函数, 处理图片资源, 语法提示, 压缩等等。 在PostCSS的插件目录中,有一个更方便用户的插件搜索。
如果项目包含有效的 PostCSS 配置 (任何受 postcss-load-config 支持的格式,例如 postcss.config.js),它将会自动应用于所有已导入的 CSS。
任何以 .module.css 为后缀名的 CSS 文件都被认为是一个 CSS modules 文件。导入这样的文件会返回一个相应的模块对象:
.module.css
/* example.module.css */ .red { color: red; }
import classes from './example.module.css' document.getElementById('foo').className = classes.red
配置 CSS modules 的行为。选项将被传递给 postcss-modules。
interface CSSModulesOptions { scopeBehaviour?: 'global' | 'local' globalModulePaths?: RegExp[] generateScopedName?: | string | ((name: string, filename: string, css: string) => string) hashPrefix?: string /** * 默认:null */ localsConvention?: | 'camelCase' | 'camelCaseOnly' | 'dashes' | 'dashesOnly' | null }
import './foo.css' // 样式将会注入页面 import otherStyles from './bar.css?inline' // 样式不会注入页面
导入一个静态资源会返回解析后的 URL:
import imgUrl from './img.png' document.getElementById('hero-img').src = imgUrl
添加一些特殊的查询参数可以更改资源被引入的方式:
// 以字符串形式加载资源 import assetAsString from './shader.glsl?raw'
JSON 可以被直接导入 —— 同样支持具名导入:
// 导入整个对象 import json from './example.json' // 对一个根字段使用具名导入 —— 有效帮助 treeshaking! import { field } from './example.json'
Vite 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块:
import.meta.glob
const modules = import.meta.glob('./dir/*.js')
以上将会被转译为下面的样子:
// vite 生成的代码 const modules = { './dir/foo.js': () => import('./dir/foo.js'), './dir/bar.js': () => import('./dir/bar.js'), }
你可以遍历 modules 对象的 key 值来访问相应的模块:
for (const path in modules) { modules[path]().then((mod) => { console.log(path, mod) }) }
匹配到的文件默认是懒加载的,通过动态导入实现,并会在构建时分离为独立的 chunk。如果你倾向于直接引入所有的模块(例如依赖于这些模块中的副作用首先被应用),你可以传入 { eager: true } 作为第二个参数:
{ eager: true }
const modules = import.meta.glob('./dir/*.js', { eager: true })
以上会被转译为下面的样子:
// vite 生成的代码 import * as __glob__0_0 from './dir/foo.js' import * as __glob__0_1 from './dir/bar.js' const modules = { './dir/foo.js': __glob__0_0, './dir/bar.js': __glob__0_1, }
默认是模块形式导入,也支持字符串形式导入资源/支持将资源作为URL加载
const modules = import.meta.glob('./dir/*.js', { as: 'raw' })
const modules = import.meta.glob('./dir/*.js', { as: 'url' })
也支持多个匹配模式、反面匹配模式、自定义查询等形式导入,这是Vite提供的独特功能 所有 import.meta.glob 的参数都必须以字面量传入。你 不 可以在其中使用变量或表达式。 该 Glob 模式会被当成导入标识符:必须是相对路径(以 ./ 开头)或绝对路径(以 / 开头,相对于项目根目录解析)或一个别名路径(请看 resolve.alias 选项)。
也支持多个匹配模式、反面匹配模式、自定义查询等形式导入,这是Vite提供的独特功能
所有 import.meta.glob 的参数都必须以字面量传入。你 不 可以在其中使用变量或表达式。
该 Glob 模式会被当成导入标识符:必须是相对路径(以 ./ 开头)或绝对路径(以 / 开头,相对于项目根目录解析)或一个别名路径(请看 resolve.alias 选项)。
下面所罗列的功能会自动应用为构建过程的一部分,除非你想禁用它们,否则没有必要显式配置。
Vite 会自动地将一个异步 chunk 模块中使用到的 CSS 代码抽取出来并为其生成一个单独的文件。这个 CSS 文件将在该异步 chunk 加载完成时自动通过一个 标签载入,该异步 chunk 会保证只在 CSS 加载完毕后再执行。
如果你更倾向于将所有的 CSS 抽取到一个文件中,你可以通过设置 build.cssCodeSplit 为 false 来禁用 CSS 代码分割。
Vite 会为入口 chunk 和它们在打包出的 HTML 中的直接引入自动生成 <link rel="modulepreload"> 指令。
<link rel="modulepreload">
Entry ---> A ---> C
Vite 将使用一个预加载步骤自动重写代码,来分割动态导入调用,以实现当 A 被请求时,C 也将 同时 被请求:
Entry ---> (A + C)
Vite 可以使用插件进行扩展,这得益于 Rollup 优秀的插件接口设计和一部分 Vite 独有的额外选项。这意味着 Vite 用户可以利用 Rollup 插件的强大生态系统,同时根据需要也能够扩展开发服务器和 SSR 功能。
若要使用一个插件,需要将它添加到项目的 devDependencies 并在 vite.config.js 配置文件中的 plugins 数组中引入它。
// vite.config.js import legacy from '@vitejs/plugin-legacy' import { defineConfig } from 'vite' export default defineConfig({ plugins: [ legacy({ targets: ['defaults', 'not IE 11'], }), ], })
为了与某些 Rollup 插件兼容,可能需要强制修改插件的执行顺序,或者只在构建时使用。这应该是 Vite 插件的实现细节。可以使用 enforce 修饰符来强制插件的位置:
enforce
pre
post
// vite.config.js import image from '@rollup/plugin-image' import { defineConfig } from 'vite'
export default defineConfig({ plugins: [ { ...image(), enforce: 'pre', }, ], })
## 按需应用 默认情况下插件在开发 (serve) 和生产 (build) 模式中都会调用。如果插件在服务或构建期间按需使用,请使用 apply 属性指明它们仅在 'build' 或 'serve' 模式时调用: ```js // vite.config.js import typescript2 from 'rollup-plugin-typescript2' import { defineConfig } from 'vite' export default defineConfig({ plugins: [ { ...typescript2(), apply: 'build', }, ], })
如果你有下列这些资源:
目录默认是 <root>/public,但可以通过 publicDir 选项 来配置。
<root>/publi
publicDir
请注意:
public
public/icon.png
/icon.png
构建过程可以通过多种 构建配置选项 来自定义构建。具体来说,你可以通过 build.rollupOptions 直接调整底层的 Rollup 选项:
// vite.config.js export default defineConfig({ build: { rollupOptions: { // https://rollupjs.org/configuration-options/ }, }, })
你可以通过配置 build.rollupOptions.output.manualChunks 来自定义 chunk 分割策略(查看 Rollup 相应文档)。在 Vite 2.8 及更早版本中,默认的策略是将 chunk 分割为 index 和 vendor。这对一些 SPA 来说是好的策略,但是要对所有应用场景提供一种通用解决方案是非常困难的。从 Vite 2.9 起,manualChunks 默认情况下不再被更改。你可以通过在配置文件中添加 splitVendorChunkPlugin 来继续使用 “分割 Vendor Chunk” 策略:
build.rollupOptions.output.manualChunks
起,manualChunks
splitVendorChunkPlugin
// vite.config.js import { splitVendorChunkPlugin } from 'vite' export default defineConfig({ plugins: [splitVendorChunkPlugin()], })
Vite 在一个特殊的 import.meta.env 对象上暴露环境变量。
import.meta.en
Vite 使用 dotenv 从你的 环境目录 中的下列文件加载额外的环境变量:
dotenv
.env # 所有情况下都会加载 .env.local # 所有情况下都会加载,但会被 git 忽略 .env.[mode] # 只在指定模式下加载 .env.[mode].local # 只在指定模式下加载,但会被 git 忽略
为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。例如下面这些环境变量:
VITE_SOME_KEY=123 DB_PASSWORD=foobar console.log(import.meta.env.VITE_SOME_KEY) // 123 console.log(import.meta.env.DB_PASSWORD) // undefined
//TODO
// TODO
一个 Vite 插件可以额外指定一个 enforce 属性(类似于 webpack 加载器)来调整它的应用顺序。enforce 的值可以是pre 或 post。解析后的插件将按照以下顺序排列:
在开发中,Vite 开发服务器会创建一个插件容器来调用 Rollup 构建钩子,与 Rollup 如出一辙。
以下钩子在服务器启动时被调用:
以下钩子会在每个传入模块请求时被调用:
以下钩子在服务器关闭时被调用:
vite相比较于webpack的优势
vite产生的背景
当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。基于 JavaScript 开发的工具就会开始遇到性能瓶颈:通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。
Vite 旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES 模块,且越来越多 JavaScript 工具使用编译型语言编写。
缓慢的服务器启动
当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。
Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。
依赖 大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS)。
Vite 将会使用 esbuild 预构建依赖。esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。
源码 通常包含一些并非直接是 JavaScript 的文件,需要转换(例如 JSX,CSS 或者 Vue/Svelte 组件),时常会被编辑。同时,并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)。
Vite 以 原生 ESM 方式提供源码。这实际上是让浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前屏幕上实际使用时才会被处理。
缓慢的更新
基于打包器启动时,重建整个包的效率很低。原因显而易见:因为这样更新速度会随着应用体积增长而直线下降。
一些打包器的开发服务器将构建内容存入内存,这样它们只需要在文件更改时使模块图的一部分失活[1],但它也仍需要整个重新构建并重载页面。这样代价很高,并且重新加载页面会消除应用的当前状态,所以打包器支持了动态模块热替换(HMR):允许一个模块 “热替换” 它自己,而不会影响页面其余部分。这大大改进了开发体验 —— 然而,在实践中我们发现,即使采用了 HMR 模式,其热更新速度也会随着应用规模的增长而显著下降。
在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活[1](大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。
Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified 进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。
vite的缺点
开发服务器和生产环境构建之间输出和行为可能不一致
生产环境仍然需要打包
尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。
生产环境不使用ESBuild打包
虽然 esbuild 快得惊人,并且已经是一个在构建库方面比较出色的工具,但一些针对构建 应用 的重要功能仍然还在持续开发中 —— 特别是代码分割和 CSS 处理方面。就目前来说,Rollup 在应用打包方面更加成熟和灵活。尽管如此,当未来这些功能稳定后,我们也不排除使用 esbuild 作为生产构建器的可能。
vite的组成
Vite 意在提供开箱即用的配置,同时它的 插件 API 和 JavaScript API 带来了高度的可扩展性,并有完整的类型支持。
vite的初始化
使用 NPM:
使用 Yarn:
使用 PNPM:
vite的功能
对非常基础的使用来说,使用 Vite 开发和使用一个静态文件服务器并没有太大区别。然而,Vite 还通过原生 ESM 导入提供了许多主要用于打包场景的增强功能。
NPM依赖解析和预构建
原生 ES 导入不支持下面这样的裸模块导入:
但是Vite会检测到这些裸模块的导入,并执行下面的操作:
预构建 它们可以提高页面加载速度,并将 CommonJS / UMD 转换为 ESM 格式。
重写导入为合法的 URL,将
node_modules
预构建(模块中多个import预构建成为单个文件,减少网络加载的文件数量)的模块放入缓存中,以便浏览器能够正确导入它们。并且依赖是强缓存的。预构建缓存
.vite
Vite 会将预构建的依赖缓存到 node_modules/.vite。它根据几个源来决定是否需要重新运行预构建步骤:
包管理器的 lockfile 内容,例如 package-lock.json,yarn.lock,pnpm-lock.yaml,或者 bun.lockb 补丁文件夹的修改时间 可能在 vite.config.js 相关字段中配置过的 NODE_ENV 中的值 只有在上述其中一项发生更改时,才需要重新运行预构建。
如果出于某些原因,你想要强制 Vite 重新构建依赖,你可以用 --force 命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录。
浏览器缓存
解析后的依赖请求会以 HTTP 头
max-age=31536000,immutable
强缓存,以提高在开发时的页面重载性能。一旦被缓存,这些请求将永远不会再到达开发服务器。如果安装了不同的版本(这反映在包管理器的 lockfile 中),则附加的版本 query 会自动使它们失效。如果你想通过本地编辑来调试依赖项,你可以:通过浏览器调试工具的 Network 选项卡暂时禁用缓存; 重启 Vite dev server,并添加
--force
命令以重新构建依赖; 重新载入页面。模块热更新
Vite 提供了一套原生 ESM 的 HMR API。 具有 HMR 功能的框架可以利用该 API 提供即时、准确的更新,而无需重新加载页面或清除应用程序状态。Vite 内置了 HMR 到 Vue 单文件组件(SFC) 和 React Fast Refresh 中。也通过 @prefresh/vite 对 Preact 实现了官方集成。
注意,你不需要手动设置这些 —— 当你通过
create-vite
创建应用程序时,所选模板已经为你预先配置了这些。TypeScript
Vite 天然支持引入
.ts
文件。不进行类型检测!
Vite 仅执行 .ts 文件的转译工作,并不执行 任何类型检查。并假定类型检查已经被你的 IDE 或构建过程处理了。
Vite 之所以不把类型检查作为转换过程的一部分,是因为这两项工作在本质上是不同的。转译可以在每个文件的基础上进行,与 Vite 的按需编译模式完全吻合。相比之下,类型检查需要了解整个模块图。把类型检查塞进 Vite 的转换管道,将不可避免地损害 Vite 的速度优势。
Vite 的工作是尽可能快地将源模块转化为可以在浏览器中运行的形式。为此,我们建议将静态分析检查与 Vite 的转换管道分开。
使用esbuild转译
Vite 使用 esbuild 将 TypeScript 转译到 JavaScript,约是
tsc
速度的 20~30 倍,同时 HMR 更新反映到浏览器的时间小于 50ms。转译时的注意事项
tsconfig.json
中compilerOptions
下的一些配置项需要特别注意。isolatedModules
:true
useDefineForClassFields
:true
Vue
JSX
.jsx
和.tsx
文件同样开箱即用。JSX 的转译同样是通过 esbuild。你可以使用
jsxInject
(这是一个仅在 Vite 中使用的选项)为 JSX 注入 helper,以避免手动导入:CSS
导入
.css
文件将会把内容插入到<style>
标签中,同时也带有 HMR 支持。也能够以字符串的形式检索处理后的、作为其模块默认导出的 CSS。Vite 通过
postcss-import
预配置支持了 CSS@import
内联,Vite 的路径别名也遵从 CSS@import
。Sass 和 Less 文件也支持
@import
别名和 URL 自动重新设置基准以确保正确性CSS预处理器
Vite 同时提供了对
.scss
,.sass
,.less
,.styl
和.stylus
文件的内置支持。没有必要为它们安装特定的 Vite 插件,但必须安装相应的预处理器依赖:PostCSS
如果项目包含有效的 PostCSS 配置 (任何受 postcss-load-config 支持的格式,例如 postcss.config.js),它将会自动应用于所有已导入的 CSS。
CSS Modules
任何以
.module.css
为后缀名的 CSS 文件都被认为是一个 CSS modules 文件。导入这样的文件会返回一个相应的模块对象:配置 CSS modules 的行为。选项将被传递给 postcss-modules。
禁用CSS注入页面
静态资源处理
导入一个静态资源会返回解析后的 URL:
添加一些特殊的查询参数可以更改资源被引入的方式:
JSON
JSON 可以被直接导入 —— 同样支持具名导入:
Glob导入
Vite 支持使用特殊的
import.meta.glob
函数从文件系统导入多个模块:以上将会被转译为下面的样子:
你可以遍历 modules 对象的 key 值来访问相应的模块:
匹配到的文件默认是懒加载的,通过动态导入实现,并会在构建时分离为独立的 chunk。如果你倾向于直接引入所有的模块(例如依赖于这些模块中的副作用首先被应用),你可以传入
{ eager: true }
作为第二个参数:以上会被转译为下面的样子:
默认是模块形式导入,也支持字符串形式导入资源/支持将资源作为URL加载
内置的构建优化
CSS代码分割
Vite 会自动地将一个异步 chunk 模块中使用到的 CSS 代码抽取出来并为其生成一个单独的文件。这个 CSS 文件将在该异步 chunk 加载完成时自动通过一个 标签载入,该异步 chunk 会保证只在 CSS 加载完毕后再执行。
预加载指令生成
Vite 会为入口 chunk 和它们在打包出的 HTML 中的直接引入自动生成
<link rel="modulepreload">
指令。异步Chunk加载优化
Vite 将使用一个预加载步骤自动重写代码,来分割动态导入调用,以实现当 A 被请求时,C 也将 同时 被请求:
插件
Vite 可以使用插件进行扩展,这得益于 Rollup 优秀的插件接口设计和一部分 Vite 独有的额外选项。这意味着 Vite 用户可以利用 Rollup 插件的强大生态系统,同时根据需要也能够扩展开发服务器和 SSR 功能。
添加插件
若要使用一个插件,需要将它添加到项目的 devDependencies 并在 vite.config.js 配置文件中的 plugins 数组中引入它。
强制插件排序
为了与某些 Rollup 插件兼容,可能需要强制修改插件的执行顺序,或者只在构建时使用。这应该是 Vite 插件的实现细节。可以使用
enforce
修饰符来强制插件的位置:pre
:在 Vite 核心插件之前调用该插件post
:在 Vite 构建插件之后调用该插件export default defineConfig({ plugins: [ { ...image(), enforce: 'pre', }, ], })
其它
public 目录
如果你有下列这些资源:
目录默认是
<root>/publi
c,但可以通过publicDir
选项 来配置。请注意:
public
中的资源永远应该使用根绝对路径 —— 举个例子,public/icon.png
应该在源码中被引用为/icon.png
。public
中的资源不应该被 JavaScript 文件引用。自定义构建
构建过程可以通过多种 构建配置选项 来自定义构建。具体来说,你可以通过 build.rollupOptions 直接调整底层的 Rollup 选项:
产物分块策略
你可以通过配置
build.rollupOptions.output.manualChunks
来自定义 chunk 分割策略(查看 Rollup 相应文档)。在 Vite 2.8 及更早版本中,默认的策略是将 chunk 分割为 index 和 vendor。这对一些 SPA 来说是好的策略,但是要对所有应用场景提供一种通用解决方案是非常困难的。从 Vite 2.9起,manualChunks
默认情况下不再被更改。你可以通过在配置文件中添加splitVendorChunkPlugin
来继续使用 “分割 Vendor Chunk” 策略:环境变量
Vite 在一个特殊的
import.meta.en
v 对象上暴露环境变量。Vite 使用
dotenv
从你的 环境目录 中的下列文件加载额外的环境变量:为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。例如下面这些环境变量:
vite开发环境与生产环境之间的区别
//TODO
vite SSR
// TODO
参考