Vite
浏 览器原生支持 ES Module
基于 ES Module
生产环境使用 Rollup
打包,开发环境使用 ESBuild
打包。
还有一个正在进行中的工作,即构建一个名为 Rolldown
的 Rust 版本的 Rollup。一旦 Rolldown 准备就绪,它就可以在 Vite 中取代 Rollup 和 ESBuild,显著提高构建性能,并消除开发和构建之间的不一致性。
在搭建 Vite 项目时,执行npm create vite@latest
命令,这里的 create vite
只是内置了 Vite 的脚手架,并不是 Vite 本身,通过脚手架可以快速创建一个 Vite 项目。
vite 为什么比 webpack 快?
痛点:随着项目越来越大,webpack 需要很长时间才能启动开发服务器,即使使用 HMR,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。迟钝的反馈会影响开发效率。
webpack 支持多种模块化规范,一开始就必须要统一模块化代码,这意味着需要将所有的依赖全部读一遍。处理后得到一个 Bundle,然后启动开发服务器。而 Vite 是一开始就启动开发服务器,在浏览器请求源码时进行转换( 如转换 jsx、ts、less、vue 组件等)并按需提供源码,根据情景动态导入代码。
Vite 利用了浏览器原生支持的 ES 模块。只要在 script 标签上添加 type="module"
标记, main.js 就可以直接使用 import 语法(动态导入)去引入 js 文件。
路径补全
vite 在处理的过程中如果遇到了不是绝对路径、也不是相对路径的引用,则会尝试开启路径补全。
// main.ts 中使用的代码
import { createApp } from 'vue';
// 打开控制台 Network,发现会自动补全路径
import { createApp } from '/node_modules/.vite/deps/vue.js?v=19b29bc2';
会自动去 node_modules 中寻找依赖
依赖预构建
在首次启动 vite 时,Vite 会在本地加载站点之前预构建项目依赖。依赖预构建仅适用于开发模式。
首先 vite 会找到对应的依赖,然后调用 esbuild 将其他规范的代码转换成 ES Module 规范,然后放到当前目录下的 node_modules/.vite/deps
,同时对 ES Module 规范的各个模块进行统一集成。
- CommonJS 和 UMD 兼容性
- 解决网络多包传输的性能问题
在开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。在项目中使用的第三方依赖包可能是 CommonJS 或者 UMD 规范的。因此,Vite 需要先将它们转换为 ES Module。
例如在使用 lodash 时,lodash 可能也 import 了其他的第三方依赖包,浏览器会加载很多模块,造成性能问题。而 Vite 会将它们全部预构建,然后通过一个入口文件将这些模块合并成一个文件,减少网络传输。
假设项目中使用了 lodash-es
:
import lodashES from 'lodash-es';
console.log(lodashES);
查看 node_modules/lodash-es/lodash.js
会发现有一大堆的 export
。但是查看控制台的 Network,找到 http://localhost:5173/node_modules/.vite/deps/lodash-es.js?v=1bf1d6d1
这个请求,查看发现内容被 vite 重写了。而且浏览器没有同时发出大量的 HTTP 请求,vite 将其集成到一个模块,只发出了一次 HTTP 请求。
接下来验证一下,在 vite.config.js 中配置 optimizeDeps.exclude
来排除依赖预构建。
export default defineConfig({
optimizeDeps: {
exclude: ['lodash-es']
}
});
这样 lodash-es 依赖不会被预构建,而是直接从 node_modules 中加载。查看控制台发现发出了大量的 HTTP 请求,会造成性能问题。
环境变量
在项目根目录新建以下文件:
.env.development
开发环境.env.production
生产环境.env.staging
预发布环境
内容可以设置如下,配置各个环境的变量:
VITE_BASE_URL="http://baseapi.com"
VITE_FILE_URL="http://fileapi.com"
在终端执行 pnpm vite --mode staging
启动预发布环境,可以在 package.json 中配置 scripts 脚本:"staging": "vite --mode staging"
。
在项目中使用环境变量:
process.cwd()
:返回当前 node 进程的工作目录import.meta.env.VITE_BASE_URL
如果是客户端,vite 会将环境变量注入到 import.meta.env
里去
环境变量默认是以 VITE
开头的,如果想要更改这个前缀,可以使用 envPrefix
配置
配置路径别名
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }]
}
});
如果引入path
后 TS 报错,就需要安装 @types/node
vite 是怎么让浏览器识别到 vue 文件的?
使用 vite 创建一个 vue 项目,启动开发服务器后,可以看到有一个 http://localhost:5173/src/App.vue
的请求,那么浏览器是怎么识别 vue 文件的呢?
在项目的根目录有一个 index.html
,里面有:
<script type="module" src="/src/main.js"></script>
我们也创建一个 main.js
,内容先空着。然后创建一个 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite</title>
</head>
<body>
<div id="app">Vite</div>
<script type="module" src="./main.js"></script>
</body>
</html>
安装 Koa,搭建开发服务器
pnpm add koa
const Koa = require('koa');
const fs = require('fs');
const path = require('path');
const app = new Koa();
app.use(async ctx => {
if (ctx.request.url === '/') {
// 在服务端不会这么用,一般用中间件去处理
const indexContent = await fs.promises.readFile(path.resolve(__dirname, './index.html'));
ctx.response.body = indexContent;
ctx.response.set('Content-Type', 'text/html');
}
});
app.listen(5173, () => console.log('listen on 5173'));
执行命令:node index.js
,可以在 package.json
中添加 script 启动脚本。
然后打开 http://localhost:5173
,可以看到读取的 index.html
内容。
在 vite 创建的 vue 项目中,在浏览器查看 App.vue
这个请求结果是 js 内容。这已经是经过编译后的内容。
创建一个 App.vue
:
console.log('App');
修改 main.js
的内容如下:
import './App.vue';
接着处理加载 main.js
的逻辑:
app.use(async ctx => {
if (ctx.request.url === '/main.js') {
const mainJsContent = await fs.promises.readFile(path.resolve(__dirname, './main.js'));
ctx.response.body = mainJsContent;
ctx.response.set('Content-Type', 'text/javascript');
}
});
重启服务,刷新页面,可以看到 main.js 已经可以加载了,但是 App.vue 依然没有加载。
在读取到 vue 文件时,是经过 AST 转换的,下面是简易表示:
if (ctx.request.url === '/App.vue') {
const appContent = await fs.promises.readFile(path.resolve(__dirname, './App.vue'));
ctx.response.body = appContent;
// 告诉浏览器即使遇到了 `.vue` 文件,也用 js 类型解析
ctx.response.set('Content-Type', 'text/javascript');
}
重启服务,然后就可以看到 App.vue 已经加载了。