跳至内容

功能

在最基本的层面上,使用 Vite 进行开发与使用静态文件服务器并没有太大区别。但是,Vite 在原生 ESM 导入的基础上提供了许多增强功能,以支持通常在基于打包器的设置中看到的各种功能。

NPM 依赖解析和预打包

原生 ES 导入不支持以下裸模块导入

js
import { someMethod } from 'my-dep'

以上代码将在浏览器中抛出错误。Vite 将检测所有已服务源文件中的此类裸模块导入,并执行以下操作

  1. 预打包 它们以提高页面加载速度,并将 CommonJS / UMD 模块转换为 ESM。预打包步骤使用 esbuild 完成,这使得 Vite 的冷启动时间比任何基于 JavaScript 的打包器都要快得多。

  2. 将导入重写为有效的 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 毫秒的时间内反映在浏览器中。

使用 仅类型导入和导出 语法来避免潜在的问题,例如仅类型导入被错误地打包,例如

ts
import type { T } from 'only/types'
export type { T }

TypeScript 编译器选项

tsconfig.jsoncompilerOptions 下的一些配置字段需要特别注意。

isolatedModules

应设置为 true

这是因为 esbuild 仅执行转译,不包含类型信息,它不支持某些功能,例如 const enum 和隐式仅类型导入。

您必须在 tsconfig.jsoncompilerOptions 下设置 "isolatedModules": true,以便 TS 会警告您有关在隔离转译中不起作用的功能。

如果依赖项不能很好地与 "isolatedModules": true 协同工作。您可以使用 "skipLibCheck": true 来暂时抑制错误,直到它在 upstream 修复。

useDefineForClassFields

从 Vite 2.5.0 开始,如果 TypeScript 目标是 ESNextES2022 或更新版本,则默认值为 true。它与 tsc 4.3.2 及更高版本的行为 一致。它也是标准的 ECMAScript 运行时行为。

其他 TypeScript 目标将默认设置为 false

但这对于那些来自其他编程语言或较旧版本的 TypeScript 的人来说可能不直观。您可以在 TypeScript 3.7 版本说明 中阅读有关过渡的更多信息。

如果您使用的是严重依赖类字段的库,请注意该库对其的预期用法。

大多数库都期望 "useDefineForClassFields": true,例如 MobX

但是,一些库尚未过渡到这个新的默认值,包括 lit-element。在这些情况下,请明确将 useDefineForClassFields 设置为 false

target

Vite 默认情况下不会使用配置的 target 值来转译 TypeScript,这与 esbuild 的行为相同。

可以使用 esbuild.target 选项,该选项默认设置为 esnext 以进行最小化转译。在构建中,build.target 选项具有更高的优先级,也可以根据需要设置。

useDefineForClassFields

如果 target 不是 ESNextES2022 或更新版本,或者没有 tsconfig.json 文件,useDefineForClassFields 将默认设置为 false,这可能会与 esbuild.target 的默认值 esnext 产生问题。它可能会转译为 静态初始化块,这些块可能不受您的浏览器支持。

因此,建议将 target 设置为 ESNextES2022 或更新版本,或者在配置 tsconfig.json 时明确将 useDefineForClassFields 设置为 true

其他影响构建结果的编译器选项

skipLibCheck

Vite 启动模板默认情况下具有 "skipLibCheck": "true",以避免对依赖项进行类型检查,因为它们可能选择仅支持特定版本的 TypeScript 和配置。您可以在 vuejs/vue-cli#5688 中了解更多信息。

客户端类型

Vite 的默认类型用于其 Node.js API。要在 Vite 应用程序中模拟客户端代码的环境,请添加一个 d.ts 声明文件

typescript
/// <reference types="vite/client" />

或者,您可以在 tsconfig.json 中的 compilerOptions.types 中添加 vite/client

json
{
  "compilerOptions": {
    "types": ["vite/client"]
  }
}

这将提供以下类型垫片

  • 资产导入(例如,导入 .svg 文件)
  • Vite 注入的 环境变量import.meta.env 上的类型
  • HMR APIimport.meta.hot 上的类型

提示

要覆盖默认类型,请添加一个包含您的类型的类型定义文件。然后,在 vite/client 之前添加类型引用。

例如,要使 *.svg 的默认导入成为 React 组件

  • vite-env-override.d.ts(包含您的类型的文件)
    ts
    declare module '*.svg' {
      const content: React.FC<React.SVGProps<SVGElement>>
      export default content
    }
  • 包含对 vite/client 的引用的文件
    ts
    /// <reference types="./vite-env-override.d.ts" />
    /// <reference types="vite/client" />

Vue

Vite 提供一流的 Vue 支持

JSX

.jsx.tsx 文件也开箱即用地支持。JSX 转译也通过 esbuild 处理。

Vue 用户应该使用官方的 @vitejs/plugin-vue-jsx 插件,它提供 Vue 3 特定的功能,包括 HMR、全局组件解析、指令和插槽。

如果在没有 React 或 Vue 的情况下使用 JSX,可以使用 esbuild 选项 配置自定义 jsxFactoryjsxFragment。例如,对于 Preact

js
// vite.config.js
import { 
defineConfig
} from 'vite'
export default
defineConfig
({
esbuild
: {
jsxFactory
: 'h',
jsxFragment
: 'Fragment',
}, })

更多详细信息请参见 esbuild 文档

您可以使用 jsxInject(这是一个 Vite 独有的选项)注入 JSX 帮助程序,以避免手动导入

js
// vite.config.js
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 模块

任何以 .module.css 结尾的 CSS 文件都被视为 CSS 模块文件。导入此类文件将返回相应的模块对象

css
/* example.module.css */
.red {
  color: red;
}
js
import 
classes
from './example.module.css'
document
.
getElementById
('foo').
className
=
classes
.
red

可以通过 css.modules 选项 配置 CSS 模块行为。

如果 css.modules.localsConvention 设置为启用驼峰式局部变量(例如 localsConvention: 'camelCaseOnly'),您也可以使用命名导入

js
// .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 的插件,但必须安装相应的预处理器本身

bash
# .scss and .sass
npm add -D 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 限制,Stylus 不支持 @import 别名和 url 重新定位。

您还可以通过在文件扩展名之前添加 .module 来将 CSS 模块与预处理器结合使用,例如 style.module.scss

禁用将 CSS 注入页面

可以通过 ?inline 查询参数关闭 CSS 内容的自动注入。在这种情况下,处理后的 CSS 字符串将像往常一样作为模块的默认导出返回,但样式不会注入到页面中。

js
import './foo.css' // will be injected into the page
import 
otherStyles
from './bar.css?inline' // will not be injected

注意

从 CSS 文件(例如 import style from './foo.css')中的默认导入和命名导入已从 Vite 5 中删除。请改用 ?inline 查询。

Lightning CSS

从 Vite 4.4 开始,实验性地支持 Lightning CSS。您可以通过将 css.transformer: 'lightningcss' 添加到您的配置文件并安装可选的 lightningcss 依赖项来选择加入。

bash
npm add -D lightningcss

如果启用,CSS 文件将由 Lightning CSS 处理,而不是 PostCSS。要对其进行配置,您可以将 Lightning CSS 选项传递给 css.lightningcss 配置选项。

要配置 CSS 模块,您将使用 css.lightningcss.cssModules 而不是 css.modules(它配置 PostCSS 处理 CSS 模块的方式)。

默认情况下,Vite 使用 esbuild 来缩小 CSS。Lightning CSS 也可以用作 CSS 缩小器,使用 build.cssMinify: 'lightningcss'

注意

使用 Lightning CSS 时,不支持 CSS 预处理器

静态资产

导入静态资产将在其提供服务时返回解析后的公共 URL

js
import 
imgUrl
from './img.png'
document
.
getElementById
('hero-img').src =
imgUrl

特殊查询可以修改资产的加载方式

js
// Explicitly load assets as URL
import 
assetAsURL
from './asset.js?url'
js
// Load assets as strings
import 
assetAsString
from './shader.glsl?raw'
js
// Load Web Workers
import 
Worker
from './worker.js?worker'
js
// Web Workers inlined as base64 strings at build time
import 
InlineWorker
from './worker.js?worker&inline'

更多详细信息请参见 静态资产处理

JSON

JSON 文件可以直接导入 - 也支持命名导入

js
// 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'

全局导入

Vite 支持通过特殊的 import.meta.glob 函数从文件系统导入多个模块

js
const 
modules
= import.meta.
glob
('./dir/*.js')

以上将转换为以下内容

js
// code produced by vite
const modules = {
  './dir/foo.js': () => import('./dir/foo.js'),
  './dir/bar.js': () => import('./dir/bar.js'),
}

然后,您可以遍历 modules 对象的键以访问相应的模块

js
for (const path in modules) {
  modules[path]().then((mod) => {
    console.log(path, mod)
  })
}

默认情况下,匹配的文件通过动态导入进行延迟加载,并在构建期间拆分为单独的块。如果您希望直接导入所有模块(例如,依赖于这些模块中的副作用首先应用),您可以将 { eager: true } 作为第二个参数传递

js
const 
modules
= import.meta.
glob
('./dir/*.js', {
eager
: true })

以上将转换为以下内容

js
// code produced by 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,
}

多个模式

第一个参数可以是 glob 数组,例如

js
const 
modules
= import.meta.
glob
(['./dir/*.js', './another/*.js'])

否定模式

也支持否定 glob 模式(以 ! 为前缀)。要从结果中忽略某些文件,您可以将排除 glob 模式添加到第一个参数

js
const 
modules
= import.meta.
glob
(['./dir/*.js', '!**/bar.js'])
js
// code produced by vite
const modules = {
  './dir/foo.js': () => import('./dir/foo.js'),
}

命名导入

可以使用 import 选项仅导入模块的一部分。

ts
const 
modules
= import.meta.
glob
('./dir/*.js', {
import
: 'setup' })
ts
// code produced by vite
const modules = {
  './dir/foo.js': () => import('./dir/foo.js').then((m) => m.setup),
  './dir/bar.js': () => import('./dir/bar.js').then((m) => m.setup),
}

eager 结合使用时,甚至可以为这些模块启用树摇。

ts
const 
modules
= import.meta.
glob
('./dir/*.js', {
import
: 'setup',
eager
: true,
})
ts
// code produced by vite:
import { setup as __glob__0_0 } from './dir/foo.js'
import { setup as __glob__0_1 } from './dir/bar.js'
const modules = {
  './dir/foo.js': __glob__0_0,
  './dir/bar.js': __glob__0_1,
}

import 设置为 default 以导入默认导出。

ts
const 
modules
= import.meta.
glob
('./dir/*.js', {
import
: 'default',
eager
: true,
})
ts
// code produced by vite:
import __glob__0_0 from './dir/foo.js'
import __glob__0_1 from './dir/bar.js'
const modules = {
  './dir/foo.js': __glob__0_0,
  './dir/bar.js': __glob__0_1,
}

自定义查询

您还可以使用 query 选项为导入提供查询,例如,将资产 作为字符串作为 url 导入

ts
const 
moduleStrings
= import.meta.
glob
('./dir/*.svg', {
query
: '?raw',
import
: 'default',
}) const
moduleUrls
= import.meta.
glob
('./dir/*.svg', {
query
: '?url',
import
: 'default',
})
ts
// code produced by vite:
const moduleStrings = {
  './dir/foo.svg': () => import('./dir/foo.js?raw').then((m) => m['default']),
  './dir/bar.svg': () => import('./dir/bar.js?raw').then((m) => m['default']),
}
const moduleUrls = {
  './dir/foo.svg': () => import('./dir/foo.js?url').then((m) => m['default']),
  './dir/bar.svg': () => import('./dir/bar.js?url').then((m) => m['default']),
}

您还可以为其他插件提供自定义查询以供使用

ts
const 
modules
= import.meta.
glob
('./dir/*.js', {
query
: {
foo
: 'bar',
bar
: true },
})

全局导入注意事项

请注意

  • 这是一个 Vite 独有的功能,不是 Web 或 ES 标准。
  • glob 模式被视为导入说明符:它们必须是相对的(以 ./ 开头)或绝对的(以 / 开头,相对于项目根目录解析)或别名路径(请参见 resolve.alias 选项)。
  • 全局匹配是通过 fast-glob 完成的 - 查看其文档以了解 支持的全局模式
  • 您还应该注意,import.meta.glob 中的所有参数都必须作为字面量传递。您不能在其中使用变量或表达式。

动态导入

类似于 全局导入,Vite 也支持使用变量进行动态导入。

ts
const module = await import(`./dir/${file}.js`)

请注意,变量仅表示一层深度的文件名。如果 file'foo/bar',则导入将失败。对于更高级的用法,您可以使用 全局导入 功能。

WebAssembly

预编译的 .wasm 文件可以使用 ?init 导入。默认导出将是一个初始化函数,它返回一个 WebAssembly.Instance 的 Promise。

js
import 
init
from './example.wasm?init'
init
().
then
((
instance
) => {
instance
.
exports
.
test
()
})

init 函数还可以接受一个 importObject,该对象将作为第二个参数传递给 WebAssembly.instantiate

js
init
({
imports
: {
someFunc
: () => {
/* ... */ }, }, }).
then
(() => {
/* ... */ })

在生产构建中,小于 assetInlineLimit.wasm 文件将被内联为 base64 字符串。否则,它们将被视为 静态资产 并按需获取。

注意

WebAssembly 的 ES 模块集成提案 目前不支持。使用 vite-plugin-wasm 或其他社区插件来处理此问题。

访问 WebAssembly 模块

如果您需要访问 Module 对象,例如多次实例化它,请使用 显式 URL 导入 来解析资产,然后执行实例化。

js
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

以下是一种替代方法,假设项目基础是当前目录。

js
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 的推荐方法。

ts
const worker = new Worker(new URL('./worker.js', import.meta.url))

worker 构造函数还接受选项,可用于创建“模块”worker。

ts
const worker = new Worker(new URL('./worker.js', import.meta.url), {
  type: 'module',
})

worker 检测仅在 new URL() 构造函数直接在 new Worker() 声明中使用时才有效。此外,所有选项参数必须是静态值(即字符串字面量)。

使用查询后缀导入

可以通过在导入请求中追加 ?worker?sharedworker 来直接导入 Web Worker 脚本。默认导出将是一个自定义 worker 构造函数。

js
import 
MyWorker
from './worker?worker'
const
worker
= new
MyWorker
()

worker 脚本也可以使用 ESM import 语句而不是 importScripts()注意:在开发过程中,这依赖于 浏览器原生支持,但在生产构建中会被编译掉。

默认情况下,worker 脚本将在生产构建中作为单独的块发出。如果您希望将 worker 内联为 base64 字符串,请添加 inline 查询。

js
import 
MyWorker
from './worker?worker&inline'

如果您希望将 worker 作为 URL 获取,请添加 url 查询。

js
import 
MyWorker
from './worker?worker&url'

有关配置所有 worker 的捆绑的详细信息,请参阅 Worker 选项

内容安全策略 (CSP)

要部署 CSP,由于 Vite 的内部机制,必须设置某些指令或配置。

'nonce-{RANDOM}'

html.cspNonce 设置后,Vite 会在任何 <script><style> 标签以及样式表和模块预加载的 <link> 标签中添加一个带有指定值的 nonce 属性。此外,当设置此选项时,Vite 将注入一个元标签(<meta property="csp-nonce" nonce="PLACEHOLDER" />)。

具有 property="csp-nonce" 的元标签的 nonce 值将在开发和构建后在必要时由 Vite 使用。

警告

确保您用每个请求的唯一值替换占位符。这对于防止绕过资源的策略非常重要,否则很容易做到。

data:

默认情况下,在构建期间,Vite 将小型资产内联为 data URI。允许 data: 用于相关指令(例如 img-srcfont-src),或者通过设置 build.assetsInlineLimit: 0 来禁用它。

警告

不要允许 data: 用于 script-src。它将允许注入任意脚本。

构建优化

下面列出的功能会自动作为构建过程的一部分应用,除非您想禁用它们,否则无需显式配置。

CSS 代码拆分

Vite 会自动提取异步块中模块使用的 CSS,并为其生成一个单独的文件。当关联的异步块加载时,CSS 文件会通过 <link> 标签自动加载,并且异步块保证仅在 CSS 加载后才进行评估,以避免 FOUC

如果您希望将所有 CSS 提取到一个文件中,可以通过将 build.cssCodeSplit 设置为 false 来禁用 CSS 代码拆分。

预加载指令生成

Vite 会自动为构建的 HTML 中的入口块及其直接导入生成 <link rel="modulepreload"> 指令。

异步块加载优化

在实际应用中,Rollup 通常会生成“公共”块 - 在两个或多个其他块之间共享的代码。结合动态导入,通常会出现以下情况。

Entry async chunk A common chunk C async chunk B dynamic import direct import

在非优化情况下,当导入异步块 A 时,浏览器必须请求和解析 A,然后才能确定它还需要公共块 C。这会导致额外的网络往返。

Entry ---> A ---> C

Vite 会自动使用预加载步骤重写代码拆分动态导入调用,以便当请求 A 时,C并行获取。

Entry ---> (A + C)

C 可能有进一步的导入,这会导致非优化情况下出现更多往返。Vite 的优化将跟踪所有直接导入,以完全消除往返,无论导入深度如何。