人人都说 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 快在哪了吗?

相关推荐
web行路人几秒前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
超雄代码狂23 分钟前
ajax关于axios库的运用小案例
前端·javascript·ajax
长弓三石31 分钟前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
小马哥编程32 分钟前
【前端基础】CSS基础
前端·css
嚣张农民1 小时前
推荐3个实用的760°全景框架
前端·vue.js·程序员
周亚鑫1 小时前
vue3 pdf base64转成文件流打开
前端·javascript·pdf
Justinc.1 小时前
CSS3新增边框属性(五)
前端·css·css3
neter.asia2 小时前
vue中如何关闭eslint检测?
前端·javascript·vue.js
~甲壳虫2 小时前
说说webpack中常见的Plugin?解决了什么问题?
前端·webpack·node.js
嚣张农民2 小时前
JavaScript中Promise分别有哪些函数?
前端·javascript·面试