Skip to content

Vite

vite主要由两部分组成:

  • 一个开发服务器。基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)

  • 一套构建指令。使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源

  • 开发环节

    1. 首先,当启动vite开发服务器时,vite会检测入口文件
    2. 当你在浏览器中访问程序时,vite将拦截对入口文件及被模块导入的文件的请求
    3. vite利用浏览器内置的es模块系统来加载模块,这是浏览器原生支持的一种模块加载方式,这与传统的基于打包的构建工具(如webpack)有所不同
    4. vite会运行一个开发服务器,通过代理的形式将模块请求重定向到实际的模块文件路径,不需要webpack一样先打包,再加载
    5. 当代码有修改时,vite会根据代码的依赖关系对只有发生变化的模块进行重新构建,并将变化传递给浏览器,实现热更新
  • 生产环节:

    1. vite会使用预编译的方式来构建应用程序
    2. 根据入口文件和依赖关系,分析模块的导入关系
    3. vite会为每个模块都生成单独的、按需加载的优化过的es模块,这样每个模块都可以单独缓存加载
    4. 还会生成用于生产环境的最终打包文件,将所有的模块和资源文件合并并压缩

webpack和vite的对比

  1. webpack的痛点
  • 在构建大型项目的时候,非常的慢
  • 因为在启动 webpack 项目的时候,webpack 会先对项目进行打包,然后运行的是打包后的文件
  1. vite的思路
  • 不打包,利用浏览器的 import 机制,按需获取内容
  • 针对 .vue 这样的模块文件,需要做预编译,编译为 JS 文件再返回给浏览器
  • Vite 中热更新的实现,底层实际上使用的是 websocket 来实现的

功能

  • 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 提供即时、准确的更新,而无需重新加载页面或清除应用程序状态。

  • ts支持:

    • Vite 使用 esbuild 将 TypeScript 转译到 JavaScript
    • 仅执行 .ts 文件的转译工作,并 不 执行任何类型检查(你可以在构建脚本中运行 tsc --noEmit 或者安装 vue-tsc 然后运行 vue-tsc --noEmit 来对你的 *.vue 文件做类型检查)
  • Vue

    • jsx:JSX 的转译同样是通过 esbuild,官方提供的 @vitejs/plugin-vue-jsx 插件
  • CSS

    • @import内联和变基
    • PostCSS
    • CSS Module
    • CSS 预处理器
  • 静态资源处理

    • 禁用 CSS 注入页面
    • JSON
    • Glob 导入:支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块
    • 动态导入
    • WebAssembly: 预编译的 .wasm 文件可以通过 ?init 来导入。默认导出一个初始化函数,返回值为所导出 wasm 实例对象的 Promise
    • Web Worker
      • 一个 Web Worker 可以使用 new Worker() 和 new SharedWorker() 导入
      • 你可以在导入请求上添加 ?worker 或 ?sharedworker 查询参数来直接导入一个 web worker 脚本
  • 构建优化

    • CSS 代码分割
    • 预加载指令生成
    • 异步 Chunk 加载优化

依赖预构建(Pre-bundling dependencies)

在你首次使用 Vite 启动项目的时候,会把你的项目依赖预先构建一次

依赖预构建仅会在开发模式下应用,并会使用 esbuild 将依赖转为 ESM 模块。在生产构建中则会使用 @rollup/plugin-commonjs

将只会在你的依赖或配置发生变化时执行

思考🤔:前面不是说 Vite 相比 Webpack 的优点不就是不打包么?这里预构建又是怎么一回事儿?

原因

存在的问题:

  • 某些依赖仍然是以 CommonJS 格式发布的,它们并不兼容原生 ESM 环境
  • 依赖文件过多,导致请求过多

1. CommonJS / UMD 兼容性 转换为 ESM 格式。

2. Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。

例如,lodash-es 有超过 600 个内置模块!当我们执行 import { debounce } from 'lodash-es' 时,浏览器同时发出 600 多个 HTTP 请求!尽管服务器在处理这些请求时没有问题,但大量的请求会在浏览器端造成网络拥塞,导致页面的加载速度相当慢。通过预构建 lodash-es 成为一个模块,我们就只需要一个 HTTP 请求了!

预构建阶段所使用的打包工具是 esbuild,这是一个用 Go 语言编写的构建工具,效率极高,大部分工作都是并行处理的,esbuild 能够迅速将依赖转换为有效的 ES 模块格式,并进行打包,从而优化依赖管理和加载效率。esbuild 所做的事情:

  • 转换:将一些 CommonJS、UMD 格式的模块转换为 ES 模块格式。
  • 打包:针对依赖进行打包,减少浏览器在开发环境请求的次数。
  • 最小化和压缩:这个是在构建阶段,针对代码的最小化和压缩也是 esbuild 来做的。

3. 缓存

  • 文件系统缓存:Vite 会将预构建的依赖缓存到 node_modules/.vite。它根据几个源来决定是否需要重新运行预构建步骤:
    • package.json 的依赖项发生变化
    • 包管理器的lockfile发生变化
    • vite.config.js 相关字段发生变化
    • 如果出于某些原因,你想要强制 Vite 重新构建依赖,你可以用 --force 命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录
  • 浏览器缓存:
    • 解析后的依赖请求会以 HTTP 头 max-age=31536000,immutable 强缓存,以提高在开发时的页面重载性能。
    • 调试:
      • NetWork: disableCache: true
      • 重启server --force
      • 重新载入页面

自定义行为

默认的依赖项发现为启发式可能并不总是可取的。在你想要显式地从列表中包含/排除依赖项的情况下, 请使用 optimizeDeps 配置项。

当你遇到不能直接在源码中发现的 import 时,optimizeDeps.include 或 optimizeDeps.exclude 就是典型的用例。

构建

不同于 依赖预构建 用到的 esbuild,生产构建使用的工具是 rollup. 因为 rollup 提供了一些特性和优势,特别适合用于生产环境的代码打包和优化

静态资源处理

将资源引入为URL

引入一个静态资源会返回解析后的公共路径:例如,imgUrl 在开发时会是 /img.png,在生产构建后会是 /assets/img.2d8efhg.png。

行为类似于 Webpack 的 file-loader。区别在于导入既可以使用绝对公共路径(基于开发期间的项目根路径),也可以使用相对路径。

  • url() 在 CSS 中的引用也以同样的方式处理
  • 如果 Vite 使用了 Vue 插件,Vue SFC 模板中的资源引用都将自动转换为导入
  • 常见的图像、媒体和字体文件类型被自动检测为资源。你可以使用 assetsInclude 选项 扩展内部列表。
  • 引用的资源作为构建资源图的一部分包括在内,将生成散列文件名,并可以由插件进行处理以进行优化
  • 较小的资源体积小于 assetsInlineLimit 选项值 则会被内联为 base64 data URL,以减少 HTTP 请求
  • 显示URL引入:未被包含在内部列表或 assetsInclude 中的资源,可以使用 ?url 后缀显式导入为一个 URL
  • 将资源引入为字符串:资源可以使用 ?raw 后缀声明作为字符串引入
  • 导入脚本作为 Worker: 脚本可以通过 ?worker?sharedworker 后缀导入为 web worker。
  • public: 目录默认是 <root>/public,但可以通过 publicDir 选项 来配置
    • 打包时会被完整复制到目标目录的根目录下
    • public 中的资源不应该被 JavaScript 文件引用

new URL(url, import.meta.url)

import.meta.url 是一个 ESM 的原生功能,会暴露当前模块的 URL。

将它与原生的 URL 构造器 组合使用,在一个 JavaScript 模块中,通过相对路径我们就能得到一个被完整解析的静态资源 URL:

js
const imgUrl = new URL('./img.png', import.meta.url).href

document.getElementById('hero-img').src = imgUrl
  • 在现代浏览器中能够原生使用 - 实际上,Vite 并不需要在开发阶段处理这些代码
  • 在生产构建时,Vite 才会进行必要的转换保证 URL 在打包和资源哈希后仍指向正确的地址
js
// 这个 URL 字符串必须是静态的, Vite 不会转换这个
const imgUrl = new URL(imagePath, import.meta.url).href
  • 无法在 SSR 中使用, 因为 import.meta.url 在浏览器和 Node.js 中有不同的语义

打包后的图片路径(vite自动依赖分析)

  • css静态链接路径
  • 标签静态资源链接路径
  • import()中的带有变量的路径
    • import(`./assets/${val}.jpg`),vite会将./assets/下所有jpg文件打包成一个js内引用jpg
    • 因此import不能是全部变量import(${val}),这样什么都分析不出来
  • URL构造器:不会生成js,更简洁, new URL('/path/${var}', import.meta.url)

构建生产版本

当需要将应用部署到生产环境时,只需运行 vite build 命令。

默认情况下,它使用 <root>/index.html 作为其构建入口点,并生成能够静态部署的应用程序包。

浏览器兼容性

默认情况下,Vite 的目标是能够 支持原生 ESM script 标签、支持原生 ESM 动态导入 和 import.meta 的浏览器:

  • Chrome >=87
  • Firefox >=78
  • Safari >=13
  • Edge >=88

build.target 配置项 指定构建目标,最低支持 es2015

默认情况下 Vite 只处理语法转译,且 默认不包含任何 polyfill

传统浏览器可以通过插件 @vitejs/plugin-legacy 来支持,它将自动生成传统版本的 chunk 及与其相对应 ES 语言特性方面的 polyfill。兼容版的 chunk 只会在不支持原生 ESM 的浏览器中进行按需加载

公共基础路径

如果你需要在嵌套的公共路径下部署项目,只需指定 base 配置项,然后所有资源的路径都将据此配置重写

由 JS 引入的资源 URL,CSS 中的 url() 引用以及 .html 文件中引用的资源在构建过程中都会自动调整,以适配此选项

当访问过程中需要使用动态连接的 url 时,可以使用全局注入的 import.meta.env.BASE_URL 变量,它的值为公共基础路径

自定义构建

build.rollupOptions 直接调整底层的 Rollup 选项:

产物分块策略

配置在使用 build.rollupOptions.output.manualChunks 时各个 chunk 是如何分割的

到 Vite 2.8 时,默认的策略是将 chunk 分割为 index 和 vendor。这对一些 SPA 来说是好的策略,但是要对每一种用例目标都提供一种通用解决方案是非常困难的。

从 Vite 2.9 起,manualChunks 默认情况下不再被更改。你可以通过在配置文件中添加 splitVendorChunkPlugin 来继续使用 “分割 Vendor Chunk” 策略

文件变化时重新构建

你可以使用 vite build --watch 来启用 rollup 的监听器。或者,你可以直接通过 build.watch 调整底层的 WatcherOptions 选项:

多页面应用模式

在构建过程中,你只需指定多个 .html 文件作为入口点即可

库模式

js
// vite.config.js
import { resolve } from 'path'
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, 'lib/main.js'),
      name: 'MyLib',
      // the proper extensions will be added
      fileName: 'my-lib'
    },
    rollupOptions: {
      // 确保外部化处理那些你不想打包进库的依赖
      external: ['vue'],
      output: {
        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
        globals: {
          vue: 'Vue'
        }
      }
    }
  }
})

推荐在你库的 package.json 中使用如下格式:

json
{
  "name": "my-lib",
  "type": "module",
  "files": ["dist"],
  "main": "./dist/my-lib.umd.cjs",
  "module": "./dist/my-lib.js",
  "exports": {
    ".": {
      "import": "./dist/my-lib.js",
      "require": "./dist/my-lib.umd.cjs"
    }
  }
}

如果 package.json 不包含 "type": "module",Vite 会生成不同的文件后缀名以兼容 Node.js。.js 会变为 .mjs 而 .cjs 会变为 .js. 在库模式下,所有 import.meta.env.* 用法在构建生产时都会被静态替换。但是,process.env.* 的用法不会被替换,所以你的库的使用者可以动态地更改它。

进阶基础路径选项

实验性

部署静态站点

环境变量和模式

环境变量

Vite 在一个特殊的 import.meta.env 对象上暴露环境变量

  • import.meta.env.MODE: {string} 应用运行的模式。
  • import.meta.env.BASE_URL: {string} 部署应用时的基本 URL。他由base 配置项决定。
  • import.meta.env.PROD: {boolean} 应用是否运行在生产环境。
  • import.meta.env.DEV: {boolean} 应用是否运行在开发环境 (永远与 import.meta.env.PROD相反)。
  • import.meta.env.SSR: {boolean} 应用是否运行在 server 上

在生产环境中,这些环境变量会在构建时被静态替换,因此,在引用它们时请使用完全静态的字符串。动态的 key 将无法生效。例如,动态 key 取值 import.meta.env[key] 是无效的。

.env

Vite 使用 dotenv 从你的 环境目录 中的下列文件加载额外的环境变量.

加载的环境变量也会通过 import.meta.env 以字符串形式暴露给客户端源码. 但出于安全考虑只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码

shell
.env                # 所有情况下都会加载
.env.local          # 所有情况下都会加载,但会被 git 忽略
.env.[mode]         # 只在指定模式下加载
.env.[mode].local   # 只在指定模式下加载,但会被 git 忽略
  • 一份用于指定模式的文件(例如 .env.production)会比通用形式的优先级更高(例如 .env)。
  • 另外,Vite 执行时已经存在的环境变量有最高的优先级,不会被 .env 类文件覆盖。例如当运行 VITE_SOME_KEY=123 vite build 的时候。
  • .env 类文件会在 Vite 启动一开始时被加载,而改动会在重启服务器后生效。

让添加的环境变量获得ts提示

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

interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
  // 更多环境变量...
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

模式

默认情况下,开发服务器 (dev 命令) 运行在 development (开发) 模式,而 build 命令则运行在 production (生产) 模式

模式 是一个更广泛的概念,而不仅仅是开发和生产, 通过传递 --mode 选项标志来覆盖命令使用的默认模式

shell
vite build --mode staging # 这是vite回去加载.env.staging文件

服务端渲染

后端集成

配置参考