Skip to main content

Vite

浏览器原生支持 ES Module

基于 ES Module

生产环境使用 Rollup 打包,开发环境使用 ESBuild 打包。

还有一个正在进行中的工作,即构建一个名为 Rolldown 的 Rust 版本的 Rollup。一旦 Rolldown 准备就绪,它就可以在 Vite 中取代 Rollup 和 ESBuild,显著提高构建性能,并消除开发和构建之间的不一致性。

warning

在搭建 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 配置

配置路径别名

文档

vite.config.ts
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

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
index.js
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的逻辑:

index.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 已经加载了。