Vite

vite主要由两部分组成:
一个开发服务器。基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)
一套构建指令。使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源
开发环节
- 首先,当启动vite开发服务器时,vite会检测入口文件
- 当你在浏览器中访问程序时,vite将拦截对入口文件及被模块导入的文件的请求
- vite利用浏览器内置的es模块系统来加载模块,这是浏览器原生支持的一种模块加载方式,这与传统的基于打包的构建工具(如webpack)有所不同
- vite会运行一个开发服务器,通过代理的形式将模块请求重定向到实际的模块文件路径,不需要webpack一样先打包,再加载
- 当代码有修改时,vite会根据代码的依赖关系对只有发生变化的模块进行重新构建,并将变化传递给浏览器,实现热更新
生产环节:
- vite会使用预编译的方式来构建应用程序
- 根据入口文件和依赖关系,分析模块的导入关系
- vite会为每个模块都生成单独的、按需加载的优化过的es模块,这样每个模块都可以单独缓存加载
- 还会生成用于生产环境的最终打包文件,将所有的模块和资源文件合并并压缩
webpack和vite的对比
- webpack的痛点
- 在构建大型项目的时候,非常的慢
- 因为在启动 webpack 项目的时候,webpack 会先对项目进行打包,然后运行的是打包后的文件

- 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:
const imgUrl = new URL('./img.png', import.meta.url).href
document.getElementById('hero-img').src = imgUrl- 在现代浏览器中能够原生使用 - 实际上,Vite 并不需要在开发阶段处理这些代码
- 在生产构建时,Vite 才会进行必要的转换保证 URL 在打包和资源哈希后仍指向正确的地址
// 这个 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 文件作为入口点即可
库模式
// 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 中使用如下格式:
{
"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 处理的代码
.env # 所有情况下都会加载
.env.local # 所有情况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略- 一份用于指定模式的文件(例如 .env.production)会比通用形式的优先级更高(例如 .env)。
- 另外,Vite 执行时已经存在的环境变量有最高的优先级,不会被 .env 类文件覆盖。例如当运行 VITE_SOME_KEY=123 vite build 的时候。
- .env 类文件会在 Vite 启动一开始时被加载,而改动会在重启服务器后生效。
让添加的环境变量获得ts提示
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
// 更多环境变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}模式
默认情况下,开发服务器 (dev 命令) 运行在 development (开发) 模式,而 build 命令则运行在 production (生产) 模式
模式 是一个更广泛的概念,而不仅仅是开发和生产, 通过传递 --mode 选项标志来覆盖命令使用的默认模式
vite build --mode staging # 这是vite回去加载.env.staging文件