功能

对非常基础的使用来说,使用 Vite 开发和使用一个静态文件服务器并没有太大区别。然而,Vite 还通过原生 ESM 导入提供了许多主要用于打包场景的增强功能。

NPM 依赖解析和预构建

原生 ES 引入不支持下面这样的裸模块导入:

  1. import { someMethod } from 'my-dep'

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

  1. 预构建 他们以提升页面重载速度,并将 CommonJS / UMD 转换为 ESM 格式。预构建这一步由 esbuild 执行,这使得 Vite 的冷启动时间比任何基于 javascript 的打包程序都要快得多。

  2. 重写导入为合法的 URL,例如 /node_modules/.vite/my-dep.js?v=f3sf2ebd 以便浏览器能够正确导入它们。

依赖是强缓存的

Vite 通过 HTTP 头来缓存请求得到的依赖,所以如果你想要 编辑/调试 一个依赖,请跟随 这里 的步骤。

模块热重载

Vite 提供了一套原生 ESM 的 HMR API。 具有 HMR 功能的框架可以利用该 API 提供即时、准确的更新,而无需重新加载页面或删除应用程序状态。Vite 提供了第一优先级的 HMR 集成给 Vue 单文件组件(SFC)React Fast Refresh。也有对 Preact 的集成 @prefresh/vite

注意,你不需要手动设置这些 —— 当你 create an app via @vitejs/create-app 创建应用程序时,所选模板已经为你预先配置了这些。

TypeScript

Vite 支持开箱即用地引入 .ts 文件。

Vite 仅执行 .ts 文件的翻译工作,并 执行任何类型检查。并假设类型检查已经被你的 IDE 或构建过程接管了。(你可以在构建脚本中运行 tsc --noEmit 或者安装 vue-tsc 然后运行 vue-tsc --noEmit 来对你的 *.vue 文件做类型检查)。

Vite 使用 esbuild 将 TypeScript 翻译到 JavaScript,约是 tsc 速度的 20~30 倍,同时 HMR 更新反映到浏览器的时间小于 50ms。

注意因为 esbuild 只执行转译工作而不含类型信息,所以它无需支持 TypeScript 的特定功能例如常量枚举和隐式 “type-only” 导入。你必须在你的 tsconfig.json 中的 compilerOptions 里设置 "isolatedModules": true,这样 TS 才会警告你哪些功能无法与独立编译模式一同工作。

Client Types

Vite 默认的类型定义是写给它的 Node.js API 的。要将其补充到一个 Vite 应用的客户端代码环境中,请将 vite/client 添加到 tsconfig 中的 compilerOptions.types 下:

  1. {
  2. "compilerOptions": {
  3. "types": ["vite/client"]
  4. }
  5. }

这将会提供以下类型定义补充:

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

Vue

Vite 为 Vue 提供第一优先级支持:

JSX

.jsx.tsx 文件同样开箱即用。JSX 的翻译同样是通过 ESBuild,默认为 React 16 形式,React 17 形式的 JSX 在 esbuild 中的支持请看 这里

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

如果不是在 React 中使用 JSX,自定义的 jsxFactoryjsxFragment 可以使用 esbuild 选项 进行配置。例如对 Preact:

  1. // vite.config.js
  2. export default {
  3. esbuild: {
  4. jsxFactory: 'h',
  5. jsxFragment: 'Fragment'
  6. }
  7. }

更多细节详见 ESBuild 文档.

你可以使用 jsInject(这是一个 Vite-only 的选项)为 JSX 注入 helper,以避免手动引入:

  1. // vite.config.js
  2. export default {
  3. esbuild: {
  4. jsxInject: `import React from 'react'`
  5. }
  6. }

CSS

导入 .css 文件将会把内容插入到 <style> 标签中,同时也带有 HMR 支持。也能够以字符串的形式检索处理后的、作为其模块默认导出的 CSS。

@import内联和重命名

Vite 通过 postcss-import 预配置支持了 CSS @import 内联,Vite 的路径别名也遵从 CSS @import。换句话说,所有 CSS url() 引用,即使导入的文件在不同的目录中,也总是自动变基,以确保正确性。

Sass 和 Less 文件也支持 @import 别名和 URL 重命名(具体请参阅 CSS Pre-processors)。

PostCSS

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

CSS Modules

任何以 .module.css 为后缀名的 CSS 文件都被认为是一个 CSS modules 文件。导入这样的文件会返回一个相应的模块对象:

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

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

如果 css.modules.localsConvention 设置开启了 camelCase 格式变量名转换(例如 localsConvention: 'camelCaseOnly'),你还可以使用按名导入。

  1. // .apply-color -> applyColor
  2. import { applyColor } from './example.module.css'
  3. document.getElementById('foo').className = applyColor

CSS 预处理器

因为 Vite 只针对现代浏览器,所以建议使用原生 CSS 变量和实现 CSSWG 草案的 PostCSS 插件(例如 postcss-nesting)来编写简单的、符合未来标准的 CSS。

话虽如此,但 Vite 也同时提供了对 .scss, .sass, .less, .styl.stylus 文件的内置支持。没有必要为他们安装特定的 vite 插件,但相应的预处理器依赖本身必须安装:

  1. # .scss and .sass
  2. npm install -D sass
  3. # .less
  4. npm install -D less
  5. # .styl and .stylus
  6. npm install -D stylus

如果是用的是单文件组件,可以通过 <style lang="sass">(或其他与处理器)自动开启。

Vite 为 Sass 和 Less 改进了 @import 解析,因而 Vite 别名也同样受用,另外,url() 中的相对路径引用的,与根文件不同目录中的 Sass/Less 文件会自动变基以保证正确性。

由于与其 API 冲突,@import 别名和 URL 变基不支持 Stylus。

你还可以通过在文件扩展名前加上 .module 来结合使用 CSS modules 和预处理器,例如 style.module.scss

静态资源处理

导入一个静态资源会返回解析后的 URL:

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

添加一些特殊的查询参数可以更改资源被引入的方式:

  1. // 显式加载资源为一个 URL
  2. import assetAsURL from './asset.js?url'
  1. // 以字符串形式加载资源
  2. import assetAsString from './shader.glsl?raw'
  1. // 加载为 Web Worker
  2. import Worker from './worker.js?worker'
  1. // 在构建时Web Worker 内联为 base64 字符串
  2. import InlineWorker from './worker.js?worker&inline'

更多细节请见 静态资源处理

JSON

JSON 可以被直接导入 - 同样支持具名导入:

  1. // 导入整个对象
  2. import json from './example.json'
  3. // 对一个根字段使用具名导入 - 有效运用 tree-shaking!
  4. import { field } from './example.json'

Glob 导入

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

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

以上将会被转译为下面的样子:

  1. // vite 生成的代码
  2. const modules = {
  3. './dir/foo.js': () => import('./dir/foo.js'),
  4. './dir/bar.js': () => import('./dir/bar.js')
  5. }

你可以遍历 modules 对象的 key 值来访问相应的模块:

  1. for (const path in modules) {
  2. modules[path]().then((mod) => {
  3. console.log(path, mod)
  4. })
  5. }

匹配到的文件将通过动态导入默认懒加载,并会在构建时分离为独立的 chunk。如果你倾向于直接引入所有的模块(例如依赖于这些模块中的副作用首先被应用),你可以使用 import.meta.globEager 代替:

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

以上会被转译为下面的样子:

  1. // vite 生成的代码
  2. import * as __glob__0_0 from './dir/foo.js'
  3. import * as __glob__0_1 from './dir/bar.js'
  4. const modules = {
  5. './dir/foo.js': __glob__0_0,
  6. './dir/bar.js': __glob__0_1
  7. }

请注意:

  • 这只是一个 Vite 独有的功能而不是一个 Web 或 ES 标准
  • 该 Glob 模式会被当成导入标识符:必须要么是相对路径(以 ./ 开头)或绝对路径(以 / 开头,相对于项目根目录解析)。
  • Glob 匹配是使用 fast-glob 来实现的 —— 阅读它的文档来查阅 支持的 Glob 模式

Web Assembly

预编译的 .wasm 文件可以直接被导入 —— 默认导出将会是一个函数,返回值为所导出 wasm 实例对象的 Promise:

  1. import init from './example.wasm'
  2. init().then((exports) => {
  3. exports.test()
  4. })

这个 init 函数也可以使用将传递给 WebAssembly.instantiate ,作为其第二个参数的 imports 对象:

  1. init({
  2. imports: {
  3. someFunc: () => {
  4. /* ... */
  5. }
  6. }
  7. }).then(() => {
  8. /* ... */
  9. })

在生产构建当中,体积小于 assetInlineLimit.wasm 文件将会被内联为 base64 字符串。否则,它们将作为资源复制到 dist 目录中,并按需获取。

Web Worker

一个 web worker 脚本可以直接通过添加一个 ?worker 查询参数来导入。默认导出将是一个自定义的 worker 构造器:

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

worker 脚本也可以使用 import 语句来替代 importScripts() - 注意,在开发过程中,这依赖于浏览器原生支持,目前只在 Chrome 中适用,而在生产版本中,它已经被编译掉了。

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

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

构建优化

下面所罗列的功能会自动应用为构建过程的一部分,没有必要在配置中显式地声明,除非你想禁用它们。

对动态导入的 Polyfill

Vite 使用 ES 动态导入作为代码分割的断点。生成的代码也会使用动态导入来加载异步 chunk。然而浏览器对原生 ESM 动态导入的功能落地比对 type="module" script 块支持要晚,它们两个功能之间存在着浏览器兼容性差异。Vite 自动会生成一个轻量级的 对动态导入的 polyfill 来抹平二者差异。

如果你确定你的构建目标只有支持原生动态导入的浏览器,你可以通过 build.polyfillDynamicImport 显式地禁用这个功能。

CSS 代码分割

Vite 会自动地将一个异步 chunk 模块中使用到的 CSS 代码抽取出来并为其生成一个单独的文件。这个 CSS 文件将在该异步 chunk 加载完成时自动通过一个 <link> 标签载入,该异步 chunk 会保证只在 CSS 加载完毕后再执行,避免发生 FOUC

如果你更倾向于将所有的 CSS 抽取到一个文件中,你可以通过设置 build.cssCodeSplitfalse 来禁用 CSS 代码分割。

预加载指令生成

Vite 会为入口 chunk 和它们在打包出的 HTML 中的直接引入自动生成 <link rel="modulepreload"> 指令。

异步 Chunk 加载优化

在实际项目中,Rollup 通常会生成 “共用” chunk —— 被两个或以上的其他 chunk 共享的 chunk。与动态导入相结合,会很容易出现下面这种场景:

graph

在无优化的情境下,当异步 chunk A 被导入时,浏览器将必须请求和解析 A,然后它才能弄清楚它首先需要那个共用 chunk C。这会导致额外的网络往返:

  1. Entry ---> A ---> C

Vite 将使用一个预加载步骤自动重写代码,来分割动态导入调用,因而当 A 被请求时,C 也将 同时 被获取到:

  1. Entry ---> (A + C)

C 也可能有更深的导入,在未优化的场景中,这甚至会导致额外网络往返。Vite 的优化将跟踪所有的直接导入,无论导入深度如何,都完全消除不必要的往返。