跳至内容

功能

在最基本的层面上,使用 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 和隐式仅类型导入。

您必须在 compilerOptions 下的 tsconfig.json 中设置 "isolatedModules": true,以便 TS 会警告您针对不适用于隔离转译的功能。

如果某个依赖项无法很好地与 "isolatedModules": true 配合使用。您可以使用 "skipLibCheck": true 暂时抑制错误,直到上游修复它。

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 忽略 tsconfig.json 中的 target 值,与 esbuild 的行为相同。

要在开发环境中指定目标,可以使用esbuild.target选项,该选项默认为 esnext 以进行最少的转译。在构建过程中,build.target选项优先于 esbuild.target,并且也可以根据需要进行设置。

useDefineForClassFields

如果 tsconfig.json 中的 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

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

这将提供以下类型模拟

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

提示

要覆盖默认类型,请添加一个包含您类型定义的文件。然后,在 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" />

HTML

HTML 文件位于 Vite 项目的 核心位置,作为应用程序的入口点,使得构建单页面和 多页面应用程序 变得简单。

项目根目录中的任何 HTML 文件都可以通过其相应的目录路径直接访问

  • <root>/index.html -> https://127.0.0.1:5173/
  • <root>/about.html -> https://127.0.0.1:5173/about.html
  • <root>/blog/index.html -> https://127.0.0.1:5173/blog/index.html

HTML 元素(如 <script type="module" src><link href>)引用的资源作为应用程序的一部分进行处理和打包。下面列出了所有支持的元素

  • <audio src>
  • <embed src>
  • <img src><img srcset>
  • <image src>
  • <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-tileimagemsapplication-square70x70logomsapplication-square150x150logomsapplication-wide310x150logomsapplication-square310x310logomsapplication-configtwitter:image
    • 或者仅当 property 属性匹配 og:imageog:image:urlog:image:secure_urlog:audioog:audio:secure_urlog:videoog:video:secure_url
html
<!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 时很有用。

Vue

Vite 提供了一流的 Vue 支持

JSX

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

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

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

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

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

您可以使用 jsxInject(这是 Vite 独有的选项)注入 JSX 辅助函数,以避免手动导入

vite.config.js
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() 引用(即使导入的文件位于不同的目录中)始终会自动重定位以确保正确性。

Sass 和 Less 文件也支持 @import 别名和 URL 重定位(请参阅 CSS 预处理器)。

PostCSS

如果项目包含有效的 PostCSS 配置(任何由 postcss-load-config 支持的格式,例如 postcss.config.js),它将自动应用于所有导入的 CSS。

请注意,CSS 压缩将在 PostCSS 之后运行,并将使用 build.cssTarget 选项。

CSS Modules

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

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

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

如果 css.modules.localsConvention 设置为启用 camelCase 本地变量(例如 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-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 限制,Stylus 不支持 @import 别名和 url 重定位。

您还可以将 CSS 模块与预处理器结合使用,方法是在文件扩展名之前添加 .module,例如 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,
}

多个模式

第一个参数可以是全局模式的数组,例如

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

否定模式

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

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 结合使用时,甚至可以为这些模块启用 tree-shaking。

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 标准。
  • 全局模式被视为导入说明符:它们必须是相对的(以 ./ 开头)或绝对的(以 / 开头,相对于项目根目录解析)或别名路径(请参阅 resolve.alias 选项)。
  • 全局匹配通过 tinyglobby 完成。
  • 您还应该注意,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',
})

只有当 new URL() 构造函数直接在 new Worker() 声明中使用时,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> 标签添加一个具有指定值的 nonce 属性,以及样式表和模块预加载的 <link> 标签。此外,当设置此选项时,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 的优化将跟踪所有直接导入,以完全消除往返,而不管导入深度如何。

根据 MIT 许可证发布。(ccee3d7c)