前端Vite的出现解决了什么?

前言

在 ESM 出现之前,Javascript 是没有一个标准的模块方案。

比如说 CJS 是用于 Node 服务端的模块化方案,AMD 是用于浏览器的模块化方案。为了解决这个模块共用性问题,出现了 UMD 用于兼容这两种模块规范。

鉴于上面共用性问题,实际开发中配置的打包方式,采用的还是 UMD 模式。因为这样可以避免打包而产生的规范问题,并且在 ESM 不能使用的情况下也会选择 UMD。

ESMES Module)的出现,则为 Javascript 提出了一个标准模块系统的方案。ESM 可以替代 CJS 与 AMD,并且兼备 UMD 特性(任何环境都可使用)。

ESM 自身的静态化特点,在编译时加载,也使得页面加载速度更快,相比 CJS、AMD 与 UMD 更有优势。

ESM 也真正意义上做到了按需使用。使用import并不会直接执行模块,而是生成一个动态的只读引用,等到真的需要用到时,才会到模块里面去读取。

主流构建工具

目前主流构建工具是先打包生成 Bundle,然后再启动开发服务器,如 webpack。同时 HMR 也是需要把改动的模块代码及相关依赖全部编译后,才会更新界面。

这也是为什么项目代码量越来越大的时候,项目启动时间会变的越来越长 ,而且稍微改一点代码,也会好长时间才热更新界面

而 Vite 的出现,则解决了这个状况。

Vite简介

Vite 是一个基于 ES 模块的构建工具,旨在提供快速的开发和构建环境。

主要由两部分组成:

开发服务器:基于本地搭建的服务器,借助浏览器原生的 ESM 能力。

构建指令:采用 Rollup 进行打包,同时继承了 Rollup plugin,可以使用 Rollup 的生态。

Vite 核心的理念目前主要体现在开发服务器,原因我们往下看。

开发服务器

相比目前主流的打包工具,无论在启动还是 HMR 都会先 Bundle,这样就会导致更新界面的速度不如 ESM

ESM

Vite 开发环境的服务是直接冷启动的,省略了 Bundle 打包的过程。

使用浏览器原生 ESM 的能力,浏览器直接去解析 imports,省略了开发环境的打包过程,在服务端直接按需编译返回。这里的编译只是编译当前文件返回给浏览器,不需要管理依赖或者解析整个项目代码的依赖。

Vite 中 HMR 也是在原生 ESM 上执行的,所以 HMR 速度也非常快,且 HMR 速度不会随着模块增多而变慢。

esbuild

对于一些较大的依赖和文件,或者不同的模块规范(如 CJS 、 AMD 、ESM)的处理。

Vite 采用了 esbuild 预构建依赖。esbuild 会统一将文件(如CJS)转换成浏览器支持的 ESM 形式。如下图:

对于这些预构建的文件,vite 会统一放在 node_modules 中的 .vite 文件夹下。

esbuild 的构建速度非常快。它不仅可以编译 JavaScript 代码,而且由于 esbuild 底层是用 Go 编写的,Go 天生具备多线程运行能力,所以比使用 JavaScript 编写的打包器预构建依赖快 10-100 倍。

虽然目前 JavaScript 编写的打包器,也可以实现充分利用 GPU 打包编译,但是 JavaScript 本质上依然是一门解释型语言,每次执行都需要将源码翻译成机器语言执行。相反 Go 是一种编译型语言,在编译阶段就已经将源码转译为机器码,启动时只需要执行即可,所以 Go 相比 JavaScript 少一步编译的过程。

http 缓存

Vite 的另一个特性之一就是使用了 http 缓存的能力。

说到 http 缓存,不得不说一下浏览器缓存过程

  • 浏览器第一次加载资源,服务器返回200,浏览器此时会将资源从服务器下载,同时将 response header 与返回时间一起缓存。
  • 再次请求加载资源的时候,浏览器会比较与上一次下载资源的时间差,如果没有超过 Cache-Control 设置的 max-age ,则没有过期,此时就会从本地缓存读取资源。如果浏览器不支持 HTTP1.1,那么则会用 Expires 判断是否过期(这一过程称为强缓存)。

Expires 是 HTTP1.0 的,Cache-Control 优先级高于 Expires,可以理解为 Expires 是处理浏览器兼容的。

  • 如果对比时间后,发现已过期。服务器则会查看请求的 header 中 If-None-Match 里值,与该请求资源的 Etag 做比较,如果相同则代表资源没有发生改变,返回304。否则,直接返回新的资源,并返回200(这一过程称为协商缓存)。
  • 如果服务器收到的请求 header 中,没有 Etag 值,则会读取 If-Modified-Since 和被请求文件的最后修改时间做对比,如果相同则代表没有发生改变,返回304。否则,直接返回新的资源,并返回200(这一过程称为协商缓存)。

下载资源的同时,在 response header 中会携带 Etag(资源唯一标识,资源发生改变,标识也随之改变)、Last-Modified(资源文件最后一次更改时间),而浏览器会把这两个保存下来。
向服务端发送资源请求时,在 request header 中,会把保存的 ETag 值放到 If-None-Match 中,把保存的 Last-Modified 值放到 If-Modified-Since 中发给服务端。

Vite 使用 http 缓存,对资源文件做了缓存处理:

  • 源码模块的请求会根据 304 Not Modified 进行协商缓存。
  • 依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存。

这样就具备一个优势,一旦被缓存它们将不需要再次请求,除非依赖或者代码发生变化。

但是紧接着也会暴露问题出来

在生产环境中,存在着使用 ESM import 这种大量嵌套的文件,就会产生大量的网络请求。随着代码量的不断增加,也会导致网络请求的不断增加。即使 Vite 采用了最新的 HTTP2.X 中的多路复用与首部压缩,仍不能解决性能低的问题。

所以,这也是其中一个为什么生产环境还需要打包的原因

当然还有其他原因,我们接着往下看。。。

构建指令

相比开发环境使用 esbuild 构建依赖,生产环境则使用借鉴了更为成熟的 Rollup 来打包。

这里的构建指令,指的是使用 Rollup 预配置指令来构建打包。

目的

鉴于大量嵌套的文件下的 ESM import,会导致生产环境产生的大量的网络请求。

为了在生产环境中获得最佳的加载性能,所以仍然需要对代码进行tree-shaking、懒加载以及chunk分割,以获得更好的缓存。

说到这里大家可能觉得 Vite 算是个半成品,这样的优势,竟然在生产服不能使用。

但是想一想,每次启动和 HMR 项目代码的时候,相比 Bundle 的漫长等待,Vite 的快速响应,为我们解决了大量等待的开发时间,尤其是代码量特别大的项目,这种优势更明显。

为什么使用 Rollup 构建打包

有的小伙伴可能会有疑问,生产环境为什么用 Rollup 打包,而不用 esbuild 打包

这是因为 esbuild 目前还不够成熟,虽然 esbuild 预构建速度很快,但针对应用级别的代码分割、CSS 处理仍然不够稳定,同时也未能兼容一些未提供 ESM 的 SDK。

我在项目开发中也遇到过不稳定的情况,每次都是重新启动一下就好了。如果大家遇到这个问题,希望对大家有所借鉴帮助。

而对于这些不稳定因素,所以目前只能放弃使用 esbuild。

有的小伙伴可能会说 esbuild 不成熟,那为什么生产环境使用 Rollup,而不用 webpack 呢?。

这是因为相比其他打包工具,Rollup 能打出更小体积的文件。而且因为 Rollup 基于 ES6 模块,比 webpack 使用的 CommonJS 模块机制更高效,而且毕竟开发环境也是基于 ESM 预构建运行的。

Vite 对于构建指令也做了其他方面的努力。比如说 Vite 兼容了 Rollup 的插件生态,从而使开发人员可以在 Vite 中使用 Rollup 成熟的插件。

相关推荐
小远yyds8 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
程序媛小果26 分钟前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
小光学长31 分钟前
基于vue框架的的流浪宠物救助系统25128(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
数据库·vue.js·宠物
guai_guai_guai1 小时前
uniapp
前端·javascript·vue.js·uni-app
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
理想不理想v3 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云3 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
暮毅3 小时前
10.Node.js连接MongoDb
数据库·mongodb·node.js
GIS程序媛—椰子4 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
我血条子呢5 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js