特性
从最基本的层面上来说,使用 Vite 进行开发与使用静态文件服务器并没有太大区别。然而,Vite 提供了许多对原生 ESM 导入的增强功能,以支持通常在基于打包器的设置中看到的各种特性。
npm 依赖解析和预构建
原生 ES 导入不支持裸模块导入,例如以下代码
import { someMethod } from 'my-dep'
以上代码会在浏览器中抛出错误。Vite 会检测所有提供的源文件中的此类裸模块导入,并执行以下操作
预构建它们以提高页面加载速度,并将 CommonJS / UMD 模块转换为 ESM。预构建步骤使用 esbuild 执行,这使得 Vite 的冷启动时间比任何基于 JavaScript 的打包器都快得多。
将导入重写为有效的 URL,如
/node_modules/.vite/deps/my-dep.js?v=f3sf2ebd
,以便浏览器可以正确导入它们。
依赖被强缓存
Vite 通过 HTTP 标头缓存依赖请求,因此如果您希望在本地编辑/调试依赖,请按照此处的步骤操作。
热模块替换
Vite 通过原生 ESM 提供了一个 HMR API。具有 HMR 功能的框架可以利用该 API 提供即时、精确的更新,而无需重新加载页面或清除应用程序状态。Vite 为 Vue 单文件组件和 React Fast Refresh 提供了一流的 HMR 集成。还有通过 @prefresh/vite 提供的 Preact 的官方集成。
请注意,您无需手动设置这些 - 当您通过 create-vite
创建一个应用时,所选模板已经为您预配置了这些。
TypeScript
Vite 支持开箱即用地导入 .ts
文件。
仅转译
请注意,Vite 仅对 .ts
文件执行转译,而不执行类型检查。它假定类型检查由您的 IDE 和构建过程处理。
Vite 不执行类型检查作为转换过程的一部分的原因是,这两项工作在根本上是不同的。转译可以按文件进行,并且与 Vite 的按需编译模型完美契合。相比之下,类型检查需要了解整个模块图。将类型检查强行塞进 Vite 的转换管道中,不可避免地会损害 Vite 的速度优势。
Vite 的工作是以尽可能快的速度将您的源模块转换为可以在浏览器中运行的形式。为此,我们建议将静态分析检查与 Vite 的转换管道分开。此原则适用于其他静态分析检查,例如 ESLint。
对于生产构建,您可以除了 Vite 的构建命令之外,还可以运行
tsc --noEmit
。在开发过程中,如果您需要的不仅仅是 IDE 提示,我们建议在一个单独的进程中运行
tsc --noEmit --watch
,或者如果您希望直接在浏览器中报告类型错误,请使用 vite-plugin-checker。
Vite 使用 esbuild 将 TypeScript 转译为 JavaScript,这比原生的 tsc
快约 20~30 倍,并且 HMR 更新可以在 50 毫秒内反映在浏览器中。
使用仅类型导入和导出语法,以避免潜在的问题,例如仅类型导入被错误地打包,例如
import type { T } from 'only/types'
export type { T }
TypeScript 编译器选项
tsconfig.json
中 compilerOptions
下的一些配置字段需要特别注意。
isolatedModules
应设置为 true
。
这是因为 esbuild
仅执行转译而不包含类型信息,它不支持某些特性,例如 const enum 和隐式仅类型导入。
您必须在 tsconfig.json
的 compilerOptions
下设置 "isolatedModules": true
,以便 TS 警告您不支持独立转译的特性。
如果一个依赖项不能很好地与 "isolatedModules": true
一起工作。您可以使用 "skipLibCheck": true
暂时抑制错误,直到上游修复。
useDefineForClassFields
如果 TypeScript 目标是 ES2022
或更新版本(包括 ESNext
),则默认值为 true
。这与 TypeScript 4.3.2+ 的行为一致。其他 TypeScript 目标将默认为 false
。
true
是标准的 ECMAScript 运行时行为。
如果您正在使用一个严重依赖于类字段的库,请注意该库对它的预期用法。虽然大多数库都期望 "useDefineForClassFields": true
,但如果您的库不支持它,您可以显式地将 useDefineForClassFields
设置为 false
。
target
Vite 忽略 tsconfig.json
中的 target
值,遵循与 esbuild
相同的行为。
要指定开发中的目标,可以使用 esbuild.target
选项,该选项默认为 esnext
以进行最小化转译。在构建中,build.target
选项的优先级高于 esbuild.target
,如果需要也可以设置。
useDefineForClassFields
如果 tsconfig.json
中的 target
不是 ESNext
或 ES2022
或更新版本,或者没有 tsconfig.json
文件,则 useDefineForClassFields
将默认为 false
,这对于默认的 esbuild.target
值 esnext
可能会出现问题。它可能会转译为 静态初始化块,您的浏览器可能不支持该块。
因此,建议将 target
设置为 ESNext
或 ES2022
或更新版本,或者在配置 tsconfig.json
时显式地将 useDefineForClassFields
设置为 true
。
影响构建结果的其他编译器选项
extends
importsNotUsedAsValues
preserveValueImports
verbatimModuleSyntax
jsx
jsxFactory
jsxFragmentFactory
jsxImportSource
experimentalDecorators
alwaysStrict
skipLibCheck
Vite 起始模板默认具有 "skipLibCheck": "true"
,以避免类型检查依赖项,因为它们可能选择仅支持特定版本的 TypeScript 及其配置。您可以在 vuejs/vue-cli#5688 了解更多信息。
客户端类型
Vite 的默认类型是用于其 Node.js API 的。要对 Vite 应用程序中客户端代码的环境进行填充,请添加一个 d.ts
声明文件
/// <reference types="vite/client" />
使用 compilerOptions.types
或者,您可以将 vite/client
添加到 tsconfig.json
中的 compilerOptions.types
中
{
"compilerOptions": {
"types": ["vite/client", "some-other-global-lib"]
}
}
请注意,如果指定了 compilerOptions.types
,则只有这些包会包含在全局范围内(而不是所有可见的 “@types” 包)。
vite/client
提供以下类型填充
提示
要覆盖默认类型,请添加一个包含您的类型定义的文件。然后,在 vite/client
之前添加类型引用。
例如,要使 *.svg
的默认导入成为 React 组件
vite-env-override.d.ts
(包含您的类型定义的文件)tsdeclare module '*.svg' { const content: React.FC<React.SVGProps<SVGElement>> export default content }
- 包含对
vite/client
引用的文件(通常是vite-env.d.ts
)ts/// <reference types="./vite-env-override.d.ts" /> /// <reference types="vite/client" />
HTML
HTML 文件是 Vite 项目的 核心,充当应用程序的入口点,从而简化了构建单页和 多页应用程序。
您的项目根目录中的任何 HTML 文件都可以通过其相应的目录路径直接访问
<root>/index.html
->http://localhost:5173/
<root>/about.html
->http://localhost:5173/about.html
<root>/blog/index.html
->http://localhost:5173/blog/index.html
HTML 元素(例如 <script type="module" src>
和 <link href>
)引用的资源会作为应用程序的一部分进行处理和打包。下面是支持的元素的完整列表
<audio src>
<embed src>
<img src>
和<img srcset>
<image href>
和<image xlink:href>
<input src>
<link href>
和<link imagesrcset>
<object data>
<script type="module" src>
<source src>
和<source srcset>
<track src>
<use href>
和<use xlink:href>
<video src>
和<video poster>
<meta content>
- 仅当
name
属性与msapplication-tileimage
、msapplication-square70x70logo
、msapplication-square150x150logo
、msapplication-wide310x150logo
、msapplication-square310x310logo
、msapplication-config
或twitter:image
匹配时 - 或仅当
property
属性与og:image
、og:image:url
、og:image:secure_url
、og:audio
、og:audio:secure_url
、og:video
或og:video:secure_url
匹配时
- 仅当
<!doctype html>
<html>
<head>
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/src/styles.css" />
</head>
<body>
<img src="/src/images/logo.svg" alt="logo" />
<script type="module" src="/src/main.js"></script>
</body>
</html>
要选择不对某些元素进行 HTML 处理,您可以在元素上添加 vite-ignore
属性,这在引用外部资源或 CDN 时非常有用。
框架
所有现代框架都维护与 Vite 的集成。大多数框架插件都由每个框架团队维护,但官方 Vue 和 React Vite 插件除外,它们在 vite 组织中维护
- 通过 @vitejs/plugin-vue 支持 Vue
- 通过 @vitejs/plugin-vue-jsx 支持 Vue JSX
- 通过 @vitejs/plugin-react 支持 React
- 通过 @vitejs/plugin-react-swc 支持使用 SWC 的 React
查看插件指南以获取更多信息。
JSX
.jsx
和 .tsx
文件也开箱即用。JSX 转译也通过 esbuild 处理。
您选择的框架将开箱即用地配置 JSX(例如,Vue 用户应使用官方 @vitejs/plugin-vue-jsx 插件,该插件提供 Vue 3 特定的特性,包括 HMR、全局组件解析、指令和插槽)。
如果将 JSX 与您自己的框架一起使用,则可以使用 esbuild
选项配置自定义的 jsxFactory
和 jsxFragment
。例如,Preact 插件将使用
import { defineConfig } from 'vite'
export default defineConfig({
esbuild: {
jsxFactory: 'h',
jsxFragment: 'Fragment',
},
})
更多详细信息请参见 esbuild 文档。
您可以使用 jsxInject
(这是一个仅限 Vite 的选项)注入 JSX 助手,以避免手动导入
import { defineConfig } from 'vite'
export default defineConfig({
esbuild: {
jsxInject: `import React from 'react'`,
},
})
CSS
导入 .css
文件会通过支持 HMR 的 <style>
标签将其内容注入到页面中。
@import
内联和重定基准
Vite 预配置为支持通过 postcss-import
进行 CSS @import
内联。Vite 别名也适用于 CSS @import
。此外,所有 CSS url()
引用,即使导入的文件位于不同的目录中,也会自动重定基准以确保正确性。
@import
别名和 URL 重定基准也支持 Sass 和 Less 文件(请参阅CSS 预处理器)。
PostCSS
如果项目包含有效的 PostCSS 配置(postcss-load-config 支持的任何格式,例如 postcss.config.js
),它将自动应用于所有导入的 CSS。
请注意,CSS 压缩将在 PostCSS 之后运行,并将使用 build.cssTarget
选项。
CSS Modules
任何以 .module.css
结尾的 CSS 文件都被认为是 CSS modules 文件。导入此类文件将返回相应的模块对象
.red {
color: red;
}
import classes from './example.module.css'
document.getElementById('foo').className = classes.red
CSS modules 行为可以通过 css.modules
选项进行配置。
如果设置了 css.modules.localsConvention
以启用 camelCase 局部变量(例如 localsConvention: 'camelCaseOnly'
),您还可以使用命名导入
// .apply-color -> applyColor
import { applyColor } from './example.module.css'
document.getElementById('foo').className = applyColor
CSS 预处理器
由于 Vite 仅面向现代浏览器,因此建议将原生 CSS 变量与实现 CSSWG 草案的 PostCSS 插件(例如 postcss-nesting)一起使用,并编写符合未来标准的纯 CSS。
也就是说,Vite 确实提供了对 .scss
、.sass
、.less
、.styl
和 .stylus
文件的内置支持。无需为它们安装 Vite 特定的插件,但必须安装相应的预处理器本身
# .scss and .sass
npm add -D sass-embedded # or sass
# .less
npm add -D less
# .styl and .stylus
npm add -D stylus
如果使用 Vue 单文件组件,这也将自动启用 <style lang="sass">
等。
Vite 改进了 Sass 和 Less 的 @import
解析,以便 Vite 别名也适用。此外,与根文件位于不同目录中的导入 Sass/Less 文件中的相对 url()
引用也会自动重定基准以确保正确性。由于其 API 约束,不支持以变量或插值开头的重定基准 url()
引用。
由于其 API 约束,Stylus 不支持 @import
别名和 url 重定基准。
您还可以通过在文件扩展名中添加 .module
将 CSS modules 与预处理器结合使用,例如 style.module.scss
。
禁用 CSS 注入到页面中
可以通过 ?inline
查询参数关闭 CSS 内容的自动注入。在这种情况下,处理后的 CSS 字符串将像往常一样作为模块的默认导出返回,但样式不会注入到页面中。
import './foo.css' // will be injected into the page
import otherStyles from './bar.css?inline' // will not be injected
注意
自 Vite 5 起,从 CSS 文件中的默认和命名导入(例如 import style from './foo.css'
)已删除。请改用 ?inline
查询。
Lightning CSS
从 Vite 4.4 开始,实验性地支持 Lightning CSS。您可以通过将 css.transformer: 'lightningcss'
添加到您的配置文件并安装可选的 lightningcss
依赖项来选择使用它
npm add -D lightningcss
如果启用,CSS 文件将由 Lightning CSS 而不是 PostCSS 处理。要配置它,您可以将 Lightning CSS 选项传递给 css.lightningcss
配置选项。
要配置 CSS Modules,您将使用 css.lightningcss.cssModules
而不是 css.modules
(它配置 PostCSS 处理 CSS modules 的方式)。
默认情况下,Vite 使用 esbuild 来压缩 CSS。Lightning CSS 也可以用作 CSS 压缩器,使用 build.cssMinify: 'lightningcss'
。
静态资源
导入静态资源将返回在提供时解析的公共 URL
import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl
特殊查询可以修改资源的加载方式
// Explicitly load assets as URL (automatically inlined depending on the file size)
import assetAsURL from './asset.js?url'
// Load assets as strings
import assetAsString from './shader.glsl?raw'
// Load Web Workers
import Worker from './worker.js?worker'
// Web Workers inlined as base64 strings at build time
import InlineWorker from './worker.js?worker&inline'
更多详细信息请参见 静态资源处理。
JSON
JSON 文件可以直接导入 - 也支持命名导入
// import the entire object
import json from './example.json'
// import a root field as named exports - helps with tree-shaking!
import { field } from './example.json'
Glob 导入
Vite 支持通过特殊的 import.meta.glob
函数从文件系统导入多个模块
const modules = import.meta.glob('./dir/*.js')
以上代码将被转换为以下代码
// code produced by vite
const modules = {
'./dir/bar.js': () => import('./dir/bar.js'),
'./dir/foo.js': () => import('./dir/foo.js'),
}
然后,您可以迭代 modules
对象的键以访问相应的模块
for (const path in modules) {
modules[path]().then((mod) => {
console.log(path, mod)
})
}
默认情况下,匹配的文件通过动态导入进行延迟加载,并且将在构建期间拆分为单独的块。如果您宁愿直接导入所有模块(例如,依赖于这些模块中的副作用首先应用),您可以将 { eager: true }
作为第二个参数传递
const modules = import.meta.glob('./dir/*.js', { eager: true })
以上代码将被转换为以下代码
// code produced by vite
import * as __vite_glob_0_0 from './dir/bar.js'
import * as __vite_glob_0_1 from './dir/foo.js'
const modules = {
'./dir/bar.js': __vite_glob_0_0,
'./dir/foo.js': __vite_glob_0_1,
}
多个模式
第一个参数可以是 glob 数组,例如
const modules = import.meta.glob(['./dir/*.js', './another/*.js'])
否定模式
也支持否定 glob 模式(以 !
开头)。要从结果中忽略某些文件,您可以将排除 glob 模式添加到第一个参数
const modules = import.meta.glob(['./dir/*.js', '!**/bar.js'])
// code produced by vite
const modules = {
'./dir/foo.js': () => import('./dir/foo.js'),
}
命名导入
可以使用 import
选项仅导入模块的一部分。
const modules = import.meta.glob('./dir/*.js', { import: 'setup' })
// code produced by vite
const modules = {
'./dir/bar.js': () => import('./dir/bar.js').then((m) => m.setup),
'./dir/foo.js': () => import('./dir/foo.js').then((m) => m.setup),
}
与 eager
结合使用时,甚至可以为这些模块启用 tree-shaking。
const modules = import.meta.glob('./dir/*.js', {
import: 'setup',
eager: true,
})
// code produced by vite:
import { setup as __vite_glob_0_0 } from './dir/bar.js'
import { setup as __vite_glob_0_1 } from './dir/foo.js'
const modules = {
'./dir/bar.js': __vite_glob_0_0,
'./dir/foo.js': __vite_glob_0_1,
}
将 import
设置为 default
以导入默认导出。
const modules = import.meta.glob('./dir/*.js', {
import: 'default',
eager: true,
})
// code produced by vite:
import { default as __vite_glob_0_0 } from './dir/bar.js'
import { default as __vite_glob_0_1 } from './dir/foo.js'
const modules = {
'./dir/bar.js': __vite_glob_0_0,
'./dir/foo.js': __vite_glob_0_1,
}
自定义查询
您还可以使用 query
选项为导入提供查询,例如,将资源作为字符串导入或作为 URL 导入
const moduleStrings = import.meta.glob('./dir/*.svg', {
query: '?raw',
import: 'default',
})
const moduleUrls = import.meta.glob('./dir/*.svg', {
query: '?url',
import: 'default',
})
// code produced by vite:
const moduleStrings = {
'./dir/bar.svg': () => import('./dir/bar.svg?raw').then((m) => m['default']),
'./dir/foo.svg': () => import('./dir/foo.svg?raw').then((m) => m['default']),
}
const moduleUrls = {
'./dir/bar.svg': () => import('./dir/bar.svg?url').then((m) => m['default']),
'./dir/foo.svg': () => import('./dir/foo.svg?url').then((m) => m['default']),
}
您还可以为其他插件提供自定义查询以供使用
const modules = import.meta.glob('./dir/*.js', {
query: { foo: 'bar', bar: true },
})
基础路径
您还可以使用 base
选项为导入提供基础路径
const modulesWithBase = import.meta.glob('./**/*.js', {
base: './base',
})
// code produced by vite:
const modulesWithBase = {
'./dir/foo.js': () => import('./base/dir/foo.js'),
'./dir/bar.js': () => import('./base/dir/bar.js'),
}
基础选项只能是相对于导入器文件的目录路径,或者是相对于项目根目录的绝对路径。不支持别名和虚拟模块。
只有作为相对路径的 globs 才被解释为相对于解析后的基础路径。
如果提供了基础路径,则所有生成的模块键都将被修改为相对于基础路径。
Glob 导入注意事项
请注意
- 这是一个仅限 Vite 的功能,不是 Web 或 ES 标准。
- glob 模式被视为导入说明符:它们必须是相对路径(以
./
开头)或绝对路径(以/
开头,相对于项目根目录解析)或别名路径(请参阅resolve.alias
选项)。 - glob 匹配通过
tinyglobby
完成。 - 您还应该意识到
import.meta.glob
中的所有参数都必须作为字面量传递。您不能在其中使用变量或表达式。
动态导入
与 glob 导入类似,Vite 也支持带有变量的动态导入。
const module = await import(`./dir/${file}.js`)
请注意,变量仅表示一级深度的文件名。如果 file
是 'foo/bar'
,则导入将失败。对于更高级的用法,您可以使用 glob 导入功能。
WebAssembly
预编译的 .wasm
文件可以使用 ?init
导入。默认导出将是一个初始化函数,该函数返回 WebAssembly.Instance
的 Promise
import init from './example.wasm?init'
init().then((instance) => {
instance.exports.test()
})
init 函数还可以接受一个 importObject,该对象作为第二个参数传递给 WebAssembly.instantiate
init({
imports: {
someFunc: () => {
/* ... */
},
},
}).then(() => {
/* ... */
})
在生产构建中,小于 assetInlineLimit
的 .wasm
文件将作为 base64 字符串内联。否则,它们将被视为静态资源并按需获取。
注意
目前不支持 WebAssembly 的 ES 模块集成提案。请使用 vite-plugin-wasm
或其他社区插件来处理此问题。
访问 WebAssembly 模块
如果您需要访问 Module
对象,例如,多次实例化它,请使用显式 URL 导入来解析资源,然后执行实例化
import wasmUrl from 'foo.wasm?url'
const main = async () => {
const responsePromise = fetch(wasmUrl)
const { module, instance } =
await WebAssembly.instantiateStreaming(responsePromise)
/* ... */
}
main()
在 Node.js 中获取模块
在 SSR 中,作为 ?init
导入的一部分发生的 fetch()
可能会失败,并出现 TypeError: Invalid URL
错误。请参阅问题 在 SSR 中支持 wasm。
这是一个替代方案,假设项目基础是当前目录
import wasmUrl from 'foo.wasm?url'
import { readFile } from 'node:fs/promises'
const main = async () => {
const resolvedUrl = (await import('./test/boot.test.wasm?url')).default
const buffer = await readFile('.' + resolvedUrl)
const { instance } = await WebAssembly.instantiate(buffer, {
/* ... */
})
/* ... */
}
main()
Web Workers
使用构造函数导入
可以使用 new Worker()
和 new SharedWorker()
导入 web worker 脚本。与 worker 后缀相比,此语法更接近标准,是创建 worker 的推荐方法。
const worker = new Worker(new URL('./worker.js', import.meta.url))
worker 构造函数还接受选项,这些选项可用于创建 “模块” worker
const worker = new Worker(new URL('./worker.js', import.meta.url), {
type: 'module',
})
worker 检测仅在 new URL()
构造函数直接在 new Worker()
声明中使用时才有效。此外,所有 options 参数都必须是静态值(即字符串字面量)。
使用查询后缀导入
可以通过将 ?worker
或 ?sharedworker
附加到导入请求来直接导入 web worker 脚本。默认导出将是一个自定义 worker 构造函数
import MyWorker from './worker?worker'
const worker = new MyWorker()
worker 脚本也可以使用 ESM import
语句而不是 importScripts()
。注意:在开发期间,这依赖于浏览器原生支持,但在生产构建中,它会被编译掉。
默认情况下,worker 脚本将在生产构建中作为单独的块发出。如果您希望将 worker 作为 base64 字符串内联,请添加 inline
查询
import MyWorker from './worker?worker&inline'
如果您希望将 worker 作为 URL 检索,请添加 url
查询
import MyWorker from './worker?worker&url'
有关配置所有 worker 的捆绑的详细信息,请参阅Worker 选项。
内容安全策略 (CSP)
要部署 CSP,必须设置某些指令或配置,因为 Vite 的内部结构。
'nonce-{RANDOM}'
当设置了 html.cspNonce
时,Vite 会将具有指定值的 nonce 属性添加到任何 <script>
和 <style>
标签,以及样式表和模块预加载的 <link>
标签。此外,当设置此选项时,Vite 将注入一个 meta 标签 (<meta property="csp-nonce" nonce="PLACEHOLDER" />
)。
带有 property="csp-nonce"
的 meta 标签的 nonce 值将在开发和构建后由 Vite 在必要时使用。
警告
确保您为每个请求替换占位符,并使用唯一值。这对于防止绕过资源的策略非常重要,否则很容易做到。
data:
默认情况下,在构建期间,Vite 将小资源内联为数据 URI。允许相关指令(例如 img-src
、font-src
)使用 data:
,或者通过设置 build.assetsInlineLimit: 0
来禁用它,这是必要的。
警告
不要为 script-src
允许 data:
。它将允许注入任意脚本。
构建优化
下面列出的特性会自动应用于构建过程,除非您想禁用它们,否则无需显式配置。
CSS 代码拆分
Vite 自动提取异步块中模块使用的 CSS,并为其生成一个单独的文件。CSS 文件在加载关联的异步块时通过 <link>
标签自动加载,并保证异步块仅在加载 CSS 后进行评估,以避免 FOUC。
如果您希望将所有 CSS 提取到一个文件中,则可以通过将 build.cssCodeSplit
设置为 false
来禁用 CSS 代码拆分。
预加载指令生成
Vite 自动为构建的 HTML 中的入口块及其直接导入生成 <link rel="modulepreload">
指令。
异步块加载优化
在真实世界的应用程序中,Rollup 通常会生成 “公共” 块 - 在两个或多个其他块之间共享的代码。与动态导入结合使用时,以下场景非常常见
在非优化场景中,当导入异步块 A
时,浏览器必须先请求和解析 A
,然后才能确定它也需要公共块 C
。这会导致额外的网络往返
Entry ---> A ---> C
Vite 自动使用预加载步骤重写代码拆分动态导入调用,以便在请求 A
时,并行获取 C
Entry ---> (A + C)
C
可能有进一步的导入,这将在非优化场景中导致更多的往返。Vite 的优化将跟踪所有直接导入,以完全消除往返,而不管导入深度如何。