浅析Vite本地构建原理

vite · 浏览次数 : 0

小编点评

前言 随着Vue3的逐渐普及以及Vite的逐渐成熟,我们有必要来了解一下关于vite的本地构建原理。Vite选择跳过打包,直接以原生ESM方式提供源码,这样可以在很大程度上提高构建速度。 与webpack对比 在Vite官网有两张对比图能够非常直观的对比两者的区别。这张图代表的是基于打包器的构建方式(webpack就是其中之一),它在启动服务之前,需要从入口开始扫描整个项目的依赖关系,然后基于依赖关系构建整个应用生成bundle,最后才会启动开发服务器。这就是这类构建方式为什么慢的原因,并且整个构建时间会随着项目的变大变的越来越长! 这张图代表的是基于ESModule的构建方式(比如:Vite),这张图是不是能够很直观说明为什么Vite会非常快,因为它上来就直接启动开发服务器,然后在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前页面上实际使用时才会被处理。也就是它不需要扫描整个项目并且打包,不打包的话那它是如何让浏览器拿到分散在项目中的各个模块呢?这一切都要得益于浏览器支持ESM的模块化方案,当浏览器识别到模块内的ESM方式导入的模块时,会自动去帮我们查找对应的内容这就是为什么vite项目的模板文件中的script标签需要加上type=module,而webpack项目不需要。 Vite快的原因 Vite之所以比webpack快,主要是因为以下几点: 1. 基于ESM模块化方案 2. 预构建使用预构建 Vite使用依赖预构建的原因主要有以下两点: - 兼容CommonJS与UMD - 性能 总结 Vite实际上就是“走一步看一步”,不像webpack上来就扫描整个项目进行打包编译,所以vite的构建速度会比较快! 实现一个简易的vite工具 我们可以参考上述内容,使用Node.js和Webpack的相关功能,实现一个简易的vite工具。首先,我们需要创建一个`vite.config.js`配置文件,然后在该文件中编写Vite的插件和配置选项。接着,我们可以使用Node.js的`http`模块和`fs`模块,创建一个简单的HTTP服务器,用于启动Vite服务器。最后,我们可以使用Webpack的`build`和`run`方法,模拟Vite的开发和构建过程。 需要注意的是,由于这是一个简易版的vite工具,因此可能无法支持所有Vite的功能和特性。在实际开发中,我们还需要根据具体需求,对简易版vite工具进行扩展和改进,以满足实际开发需求。

正文

前言

随着Vue3的逐渐普及以及Vite的逐渐成熟,我们有必要来了解一下关于vite的本地构建原理。

对于webpack打包的核心流程是通过分析JS文件中引用关系,通过递归得到整个项目的依赖关系,并且对于非JS类型的资源,通过调用对应的loader将其打包编译生成JS 代码,最后再启动开发服务器。

了解到webpack的耗时主要花费在打包上,Vite选择跳过打包,直接以 原生 ESM 方式提供源码,这样岂不是可以非常快!

与webpack对比

Vite官网有两张对比图能够非常直观的对比两者的区别。

这张图代表的是基于打包器的构建方式(webpack就是其中之一),它在启动服务之前,需要从入口开始扫描整个项目的依赖关系,然后基于依赖关系构建整个应用生成bundle,最后才会启动开发服务器。 这就是这类构建方式为什么慢的原因,并且整个构建时间会随着项目的变大变的越来越长!

这张图代表的是基于ES Module的构建方式(比如:Vite),这张图是不是能够很直观说明为什么Vite会非常快,因为它上来就直接启动开发服务器,然后在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前页面上实际使用时才会被处理。

也就是它不需要扫描整个项目并且打包,不打包的话那它是如何让浏览器拿到分散在项目中的各个模块呢?

这一切都要得益于浏览器支持ESM的模块化方案,当浏览器识别到模块内的 ESM 方式导入的模块时,会自动去帮我们查找对应的内容

这就是为什么vite项目的模版文件中的script标签需要加上type=module,而webpack项目不需要。

<script type="module" src="/src/main.ts"></script>

vite快的原因

其实上面已经能够说明vite为什么会比webpack快了,但还有另外一个点在上图中并没有表现出来。

Vite会在一开始将项目中的所有模块分为源码依赖两类

  • 源码指的是我们自己写的代码,这类代码可能需要转换(例如 JSX,CSS 或者 Vue/Svelte 组件),并且时常会被编辑。Vite 会以 原生 ESM 方式提供源码,同时并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)。
  • 依赖大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS)。Vite 将会使用 esbuild预构建依赖。esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。

总结来说就是:基于ESM模块化方案 + 预构建

使用预构建的原因

Vite使用依赖预构建的原因主要有以下两点:

  • 兼容CommonJS与UMD:在开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将以 CommonJS 或 UMD 形式提供的依赖项转换为 ES 模块。
  • 性能:为了提高后续页面的加载性能,Vite将那些具有许多内部模块的 ESM 依赖项转换为单个模块。

可以来看个例子:

我们引入lodash-es工具包中的debounce方法,此时它理想状态应该是只发出一个请求

import  { debounce }  from 'lodash-es'

事实也是这样

但这是预构建的功劳,如果我们对lodash-es关闭预构建呢?

vite配置文件加上如下代码,再来试试:

// vite.config.js
optimizeDeps: {
    exclude: ['lodash-es']
  }

可以看到,此时发起了600多个请求,这是因为lodash-es 有超过 600 个内置模块!

vite通过将 lodash-es 预构建成单个模块,只需要发起一个HTTP请求!可以很大程度地提高加载性能

基本原理

跟着debug来一步一步看vite本地是如何工作的

首先从package.json出发,找到项目启动命令:

可以看到,dev对应的命令直接就是vite,然后我们再找到node_modules下面的vite下面的bin文件夹下面的vite.js文件,这就是vite运行的入口文件。

这里有一个start方法,从这打上断点开始慢慢往下走,就能够知道整个运行的基本原理

从上面我们知道,vite首先是会启动一个本地服务,基于该服务对文件的请求进行处理返回

接着往下走,我们可以看到有一个处理url的方法,此时运行栈里面的address变量也能够看到是127.0.0.1:5173,这就是我们等会要访问的本地服务,当然现在浏览器还什么也看不到,因为还没开始处理/路由,该路由需要返回一个html文件,也就是我们的模版文件(项目基于Vue3)

继续往下走,就可以看到有一个applyHtmlTransforms方法用来处理html文件并返回,可以看到当前请求的原始路径是/,返回的文件是项目根目录下的index.html文件

里面有一个脚本文件<script type="module" src="/src/main.ts"></script>,接下来就该请求并处理入口文件main.ts

main.ts文件如下:

import { createApp } from 'vue'
// import './style.css'
import  { debounce }  from 'lodash-es'

console.log('--lodash--', debounce)
import App from './App.vue'

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

经过处理之后变成了:

它其实也没做啥处理,只是把依赖的引用路径处理成了预构建下的路径(.vite/deps/),把源码的引用路径处理成了绝对路径。

🤔这里是不是会好奇,浏览器不是不能识别处理vue文件吗,这个不需要处理吗?(接着往下看!)

来看看此时浏览器中的加载顺序是怎样的吧:

整个文件的加载顺序是不是都对上了,注意看这个App.vue文件,虽然是.vue结尾,但文件类型依然是一个JavaScript文件

App.vue经过编译后文件类型已经转成JS了!

App.vue文件内容如下:

<script setup lang="ts">
import { ref } from 'vue'
const userName = ref('前端南玖')
</script>

<template>
  <div class="user_name">{{ userName }}</div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

编译后:

再接着往下走,看下style被编译成了什么内容:

最后整个页面就可以渲染出来了!

总结

vite整体思路:启动一个 connect 服务器拦截由浏览器请求 ESM的请求。通过请求的路径找到目录下对应的文件做一下编译最终以 ESM的格式返回给浏览器。

对于node_modules下面的依赖,vite会使用esbuild进行预构建,主要是为了兼容CommonJS与UMD,以及提高性能。

这样完整走一遍,是不是对Vite的理解又更深一步了,它实际上就是“走一步看一步”,不像webpack上来就扫描整个项目进行打包编译,所以vite的构建速度会比较快!

了解完vite工作原理,我们是不是可以来实现一个简易的vite工具!

与浅析Vite本地构建原理相似的内容:

浅析Vite本地构建原理

前言 随着Vue3的逐渐普及以及Vite的逐渐成熟,我们有必要来了解一下关于vite的本地构建原理。 对于webpack打包的核心流程是通过分析JS文件中引用关系,通过递归得到整个项目的依赖关系,并且对于非JS类型的资源,通过调用对应的loader将其打包编译生成JS 代码,最后再启动开发服务器。

浅析MySQL 8.0直方图原理

本文将对直方图概念进行介绍,借助举例描述直方图的使用方式,对创建/删除直方图的原理进行浅析,并通过例子说明其应用场景。

[转帖]浅析Nginx配置获取客户端真实IP的proxy_set_header、X-Real-IP、$remote_addr、X-Forwarded-For、$proxy_add_x_forwarded_for分别是什么意思

https://www.cnblogs.com/goloving/p/15588668.html 一、问题背景 在实际应用中,我们可能需要获取用户的ip地址,比如做异地登陆的判断,或者统计ip访问次数等,通常情况下我们使用 request.getRemoteAddr() 就可以获取到客户端ip,但是

[转帖]浅析IP地址及localhost、127.0.0.1和0.0.0.0的区别

https://www.cnblogs.com/goloving/p/7202151.html 127.0.0.1和0.0.0.0这两个IP地址再熟悉不过了,看起来好像就那么回事,但真正较起真来,这两个IP地址到底有什么作用以及到底有什么不同?貌似谁可以轻松回答,但张嘴却又不知从何说起(这要是面试,

[转帖]浅析nginx的server及server_name的意义详解

https://www.cnblogs.com/goloving/p/7010713.html 一、server_name 详解 当Nginx接到请求后,会匹配其配置中的server模块。匹配方法就是靠请求携带的host和port正好对应其配置中的server_name 和listen。如果做过ip

[转帖]浅析./configure、make、make install之间的关系

https://www.cnblogs.com/zcj-0928/articles/16261389.html 写在前面: 可能我们都知道linux中安装软件方式的一种是:将源码sourcecode.tar.gz进行解压,然后输入./configure,接着make,最后make install,一

【转帖】浅析经典JVM垃圾收集器-Serial/ParNew/Parallel Scavenge/Serial Old/Parallel Old/CMS/G1

https://zhuanlan.zhihu.com/p/481256418 在讲述垃圾收集器之前,我们得先知道JVM中常见的垃圾收集算法有什么,具体请参考我的这篇博文。如果说收集算法是内存回收的方法论, 那垃圾收集器就是内存回收的实践者。下面就来详细概述下Serial、ParNew、Paralle

[转帖]浅析TiDB二阶段提交

https://cloud.tencent.com/developer/article/1608073 关键内容说明: TiDB 对于每个事务,会涉及改动的所有key中,选择出一个作为当前事务的Primary Key,其他的则为Secondary keys。 当Primary Key提交成功,标识整

浅析 SplitChunksPlugin 及代码分割的意义

起因 有同事分享webpack的代码分割,其中提到了SplitChunksPlugin,对于文档上的描述大家有着不一样的理解,所以打算探究一下。 Q:什么是 SplitChunksPlugin?SplitChunksPlugin 是用来干嘛的? A: 最初,chunks(以及内部导入的模块)是通过内

浅析 Jetty 中的线程优化思路

本文介绍了 Jetty 中 ManagedSelector 和 ExecutionStrategy 的设计实现,通过与原生 select 调用的对比揭示了 Jetty 的线程优化思路。Jetty 设计了一个自适应的线程执行策略(EatWhatYouKill),在不出现线程饥饿的情况下尽量用同一个线程侦测 I/O 事件和处理 I/O 事件,充分利用了 CPU 缓存并减少了线程切换的开销。这种优化思路