人人都说 vite 快,但它究竟为啥快?

在前端技术一浪拍死一浪的洪流中,vite 已经逐渐取代 webpack 成为各大框架推荐的打包工具。

大家都说 vite 快,那它究竟为啥快呢?

今天就和大家伙一起好好扒一扒 vite 的底细。

demo 准备

首先,我们通过 create-vite 来创建一个 vite 的模板文件:

sql 复制代码
npm create vite@latest vite-webpack --template vue

然后将其中的 svg 图片、无用的组件等删除,只留下这些:

lua 复制代码
  vite-webpack
  |- vite.config.js
  |- package.json
  |- package-lock.json
  |- index.html
  |- /src
    |- App.vue
    |- main.js
    |- style.css

其中 main.js 文件如下:

js 复制代码
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

createApp(App).mount('#app')

接着,我们将 App.vue 文件内容修改为如下:

js 复制代码
<script setup>
  const data = 'hello world'
</script>

<template>
  <div>
    <span class="red">{{ data }}</span>
  </div>
</template>

<style scoped>
.red {
  font-size: 24px;
  color: red;
}
</style>

通过 npm install 安装相关依赖后,再通过 npm run dev 来启动本地服务器:

一切运作正常,红色的 hello world 成功的显示在了屏幕中。

vite 打包

基本原理

接下来,我们打开控制台,看看浏览器都发起了哪些请求:

图中的 127.0.0.1 实际上请求的就是我们入口的 index.html 文件:

html 复制代码
<!doctype html>
<html lang="en">
  <head>
    <script type="module" src="/@vite/client"></script>

    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Vue</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

client 以及 vue.js 文件对应的是 vitevue 的相关源码,我们先忽略它;将关注点放在 main.js 文件上,文件内容如下:

javascript 复制代码
import { createApp } from "/node_modules/.vite/deps/vue.js?v=2eb74db6"
import "/src/style.css"
import App from "/src/App.vue?t=1697631544163"

createApp(App).mount('#app')

细心的小伙伴应该发现了, import 语句的导入路径发生了变化,被自动补全成了完整的绝对路径

实际上,补全路径这个操作就是 vite 在打包时默默帮我们做的;

这样一来,支持模块化的现代浏览器就能根据这个导入路径,发起 http 请求来获取对应的文件

.vue 文件处理

下面,我们来看看 vite 是如何处理 App.vue 文件的:

文件内容如下:

这个文件中一堆没见过的变量、函数,乍一看还让人怪晕乎的;但是其实我们只需要关注下面两部分:

js 复制代码
// setup 中编写的 js 部分
const data = 'hello world'

// 模板部分编译成的渲染函数
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, [
    _createElementVNode("span", { class: "red" }, _toDisplayString($setup.data))
  ]))
}

上面代码中的 hello worldspan 是不是很眼熟呢?

没错,这里的内容就是经过 vue 编译器解析编译后的 js 与渲染函数 ;对应的就是我们在 setup<template> 下编写的代码。

js 和模板有了,那 css 的对应的内容又在哪呢?仔细观察不难发现,在 App.vue 文件的最下方还 import 了一个文件:

js 复制代码
import "/src/App.vue?t=1697631578152&vue&type=style&index=0&scoped=7a7a37b1&lang.css"

这里导入的就是 css 的部分;vite 开启的服务器会根据问号(?)后面的 query 参数来判断请求的资源类型等信息;

比如 type=style 表示请求的是 style 样式文件,vite 底层就会做相应的处理并返回对应的文件。

webpack 打包

同样针对这个 demo,接下来我们下载 webpack 以及相关的插件、loader;并完成 webpack.config.js 相关配置。

具体配置在这里就不展开细说了。

然后通过 webpack 来打包后,同样打开控制台看看浏览器都请求了什么:

图中的 localhost 就是我们的 html 文档:

html 复制代码
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Vue</title>
    <script defer src="index.bundle.js"></script></head>
  <body>
    <div id="app"></div>
  </body>
</html>

index.bundle.js 又是什么呢?

我们点开这个文件,同时按住 ctrl + f 分别在文件中搜索 hello world 以及 .red;会发现无论是js还是css,这些都已经被包含在这一个文件中了

这是因为,webpack打包的时候就会去梳理文件之间的依赖关系,根据我们的配置将它们整合成一个或者多个包,并且将这些包的请求路径添加到 html 文档的 <script>

同时,浏览器在请求这些包时也无需再分析其中的 import 等语句,都是 开箱即用 的。

vite VS webpack

经过上面的分析,相信大家已经看出点门路了;

相比较于 webpackvite 在打包时省去了包之间的依赖关系分析、依赖图谱构建等步骤;利用现代浏览器能够分析模块依赖的特点,让浏览器自己发起网络请求获取对应资源

这便是 vite 快的原因之一;除此之外,vite 还在其它方面也做了一系列优化,我们来一起看看:

第三方依赖的处理

在前面的文章中,我们分析比较了 vitewebpack 对源码的打包过程,由此来说明 vite 在源码打包时的优势;

预构建

而除了源码之外,在项目运行的过程中少不了第三方依赖的参与(一般是存放在 node_module 文件夹下的文件),这些文件可能采用的是 ESM 也可能是 CommonJSUMD 等等;

vite 会使用 esbuild 来对这些依赖进行预构建,将其编译转换为浏览器能够直接执行的文件。

并且 vite 还会将预构建的依赖项缓存到 node_modules/.vite 文件夹中。

使用 esbuild 的好处是 ------ 它采用 Go 语言编写,比传统 JavaScript 编写的打包器构建速度要快上许多。

依赖项合并

在对依赖进行预构建时,vite 还会针对那些文件内又包含了许多其它导入语句的模块进行合并,为什么要这么做呢?

前面我们说过,vite 是通过浏览器对模块化的识别能力在请求文件的;比如说我们在项目中使用 import { debounce } from 'lodash-es' 导入了 debounce 这个模块,而在 debounce 模块中又导入了其它的模块......

这样周而复始,这些 import 语句都会被浏览器识别,浏览器为了请求文件就会发出大量的 htttp 请求最终导致网络拥堵;

为了解决这个问题,vite 会分析这些文件将它们转换合并为单个模块。这样一来,当执行 import { debounce } from 'lodash-es' 时,只需要发送一个 htttp 请求来获取整个 lodash-es 的代码就可以啦

利用浏览器缓存

在代码首次运行成功后,我们再次刷新浏览器查看对应请求结果:

看到请求头中那个熟悉的 304 Not Modified 了吗?

没错,针对预构建的依赖会通过使用 HTTP 头的 max-age=31536000, immutable,来进行强缓存,后续如果文件没有发生变更则会直接命中缓存,从而提升开发期间的性能。

以上就是文章的全部内容啦,现在你明白 vite 快在哪了吗?

相关推荐
冰糖雪梨dd2 分钟前
【JavaScript】 substring()方法详解
开发语言·前端·javascript
John Song7 分钟前
npm查看全局安装了哪些命令
前端·npm·node.js
清汤饺子15 分钟前
用了大半年 Claude Code,我总结了 16 个实用技巧
前端·javascript·后端
mCell7 小时前
【短文】不是最强,是最适合
前端·aigc·deepseek
余瑜鱼鱼鱼8 小时前
HTML常用标签总结
前端·html
Jave21088 小时前
Vue 中 mixins 混合开发的主要使用场景有哪些?
前端·vue.js
徐同保8 小时前
openclaw安装
前端
JEECG低代码平台9 小时前
JeecgBoot低代码平台 Ant Design Vue 4.x 升级避坑指南
前端·vue.js·低代码
yashuk9 小时前
Go-Gin Web 框架完整教程
前端·golang·gin
唐叔在学习9 小时前
e.preventDefault()到底怎么用?
前端·javascript