在前端技术一浪拍死一浪的洪流中,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 文件对应的是 vite
和 vue
的相关源码,我们先忽略它;将关注点放在 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 world 与 span 是不是很眼熟呢?
没错,这里的内容就是经过 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
经过上面的分析,相信大家已经看出点门路了;
相比较于 webpack
,vite
在打包时省去了包之间的依赖关系分析、依赖图谱构建等步骤;利用现代浏览器能够分析模块依赖的特点,让浏览器自己发起网络请求获取对应资源。
这便是 vite
快的原因之一;除此之外,vite
还在其它方面也做了一系列优化,我们来一起看看:
第三方依赖的处理
在前面的文章中,我们分析比较了 vite
和 webpack
对源码的打包过程,由此来说明 vite
在源码打包时的优势;
预构建
而除了源码之外,在项目运行的过程中少不了第三方依赖的参与(一般是存放在 node_module
文件夹下的文件),这些文件可能采用的是 ESM
也可能是 CommonJS
、UMD
等等;
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
快在哪了吗?