跳至内容

特性

从最基本的层面上来说,使用 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 暂时抑制错误,直到上游修复。

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 不是 ESNextES2022 或更新版本,或者没有 tsconfig.json 文件,则 useDefineForClassFields 将默认为 false,这对于默认的 esbuild.targetesnext 可能会出现问题。它可能会转译为 静态初始化块,您的浏览器可能不支持该块。

因此,建议将 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" />
使用 compilerOptions.types

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

tsconfig.json
json
{
  "compilerOptions": {
    "types": ["vite/client", "some-other-global-lib"]
  }
}

请注意,如果指定了 compilerOptions.types,则只有这些包会包含在全局范围内(而不是所有可见的 “@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 引用的文件(通常是 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-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 时非常有用。

框架

所有现代框架都维护与 Vite 的集成。大多数框架插件都由每个框架团队维护,但官方 Vue 和 React Vite 插件除外,它们在 vite 组织中维护

查看插件指南以获取更多信息。

JSX

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

您选择的框架将开箱即用地配置 JSX(例如,Vue 用户应使用官方 @vitejs/plugin-vue-jsx 插件,该插件提供 Vue 3 特定的特性,包括 HMR、全局组件解析、指令和插槽)。

如果将 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() 引用,即使导入的文件位于不同的目录中,也会自动重定基准以确保正确性。

@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 文件。导入此类文件将返回相应的模块对象

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

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

如果设置了 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 约束,不支持以变量或插值开头的重定基准 url() 引用。

由于其 API 约束,Stylus 不支持 @import 别名和 url 重定基准。

您还可以通过在文件扩展名中添加 .module 将 CSS modules 与预处理器结合使用,例如 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

注意

自 Vite 5 起,从 CSS 文件中的默认和命名导入(例如 import style from './foo.css')已删除。请改用 ?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 Modules,您将使用 css.lightningcss.cssModules 而不是 css.modules(它配置 PostCSS 处理 CSS modules 的方式)。

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

静态资源

导入静态资源将返回在提供时解析的公共 URL

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

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

js
// Explicitly load assets as URL (automatically inlined depending on the file size)
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'

Glob 导入

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

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

以上代码将被转换为以下代码

js
// code produced by vite
const modules = {
  './dir/bar.js': () => import('./dir/bar.js'),
  './dir/foo.js': () => import('./dir/foo.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 __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 数组,例如

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/bar.js': () => import('./dir/bar.js').then((m) => m.setup),
  './dir/foo.js': () => import('./dir/foo.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 __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 以导入默认导出。

ts
const 
modules
= import.meta.
glob
('./dir/*.js', {
import
: 'default',
eager
: true,
})
ts
// 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 导入

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/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']),
}

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

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

基础路径

您还可以使用 base 选项为导入提供基础路径

ts
const 
modulesWithBase
= import.meta.
glob
('./**/*.js', {
base
: './base',
})
ts
// 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 也支持带有变量的动态导入。

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

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

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() 声明中使用时才有效。此外,所有 options 参数都必须是静态值(即字符串字面量)。

使用查询后缀导入

可以通过将 ?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 会将具有指定值的 nonce 属性添加到任何 <script><style> 标签,以及样式表和模块预加载的 <link> 标签。此外,当设置此选项时,Vite 将注入一个 meta 标签 (<meta property="csp-nonce" nonce="PLACEHOLDER" />)。

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

警告

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

data:

默认情况下,在构建期间,Vite 将小资源内联为数据 URI。允许相关指令(例如 img-srcfont-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 通常会生成 “公共” 块 - 在两个或多个其他块之间共享的代码。与动态导入结合使用时,以下场景非常常见

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 许可下发布。(083ff36d)