Vite 构建工具初探

198 阅读5分钟

前置知识

ES Modules

浏览器原生支持的,支持直接在浏览器中执行import。

developer.mozilla.org/en-US/docs/…

ESbuild

www.npmjs.com/package/esb…

一个类似webpack的构建工具,使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。编译快的原因主要有以下几点:

  • 它是用 Go 语言编写的,该语言可以编译为原生代码;
  • 解析,打印和源映射生成全部完全并行化;
  • 无需昂贵的数据转换,只需很少的几步即可完成所有操作;
  • 编写代码时处处注意速度表现,并尽量避免不必要的配置。

Vite是什么?

Vite,一个基于浏览器原生 ES imports 的构建工具。在开发环境下利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用;在生产环境下使用rollup进行打包。主要由两部分组成:

vs webpack?

webpack在启动时会从入口开始逐步经历语法解析、依赖手机、代码转译、打包合并等一系列操作,并将打包的产物保存在内存中。由于多了打包构建这一层,所以当我们的应用规模越来越大时,使用 JavaScript 开发的工具通常需要很长时间才能启动开发服务器,即使使用 HMR,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。

image.png

而Vite相较于webpack,则没有了打包的过程,在启动后主要做了两件事情来改进启动速度:

  • 按需编译:利用浏览器原生 ES Module 的支持,浏览器直接向 dev server 逐个请求各个模块,而不需要提前把所有文件打包。
  • 依赖预编译:借助 esbuild 超快的编译速度把第三方库进行预编译。

image.png

按需编译

核心是利用原生 ES Module,当在入口文件中为script 标签加上属性 type="module"属性时

<script type=``"module"` `src=``"/src/main.js"``></script>

页面加载时浏览器会向服务器发起一个请求http://localhost:3000/src/main.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App.tsx'
ReactDOM.render(<App />, document.getElementById('root'))import React from 'react'

解析到main.js当中的import时,又会再次发起资源请求,服务端拦截请求,并对文件内容做相应的处理后返回。

但是浏览器遇到import React from 'react'依赖( 需要从node_modules中获取的模块)的导入时,是无法正确解析其路径的,因此当服务器拦截这类请求时,会做两件事:

  • 返回依赖预编译后的内容;
  • 重写依赖导入的链接,使其能够正确的被浏览器加载;

那先看一下预编译的过程吧。

依赖预编译

预编译指的是在服务器启动之前,会读取应用中package.json中的dependencies,对这些依赖进行预编译,构建完成后将编译的相关信息放在node_modules/.vite/目录下,在启动时从该目录下读取缓存,从而缩短冷启动的时间。

2. 预编译的作用

  • 兼容CommonJS 和 UMD依赖

因为 Vite 的 DevServer 是基于浏览器的原生ESM实现的,所以依赖如果是 CommonJS 或 AMD 模块,则需要转化为ES Module。

  • 减少请求数量

一些包将它们的 ES 模块构建作为许多单独的文件相互导入。如Lodash-es,当我们执行 import { debounce } from 'lodash-es' 时,浏览器同时发出 600 多个 HTTP 请求!尽管服务器在处理这些请求时没有问题,但大量的请求会在浏览器端造成网络拥塞,导致页面的加载速度相当慢。通过预编译 lodash-es 成为一个模块,我们就只需要一个 HTTP 请求了!

那么预编译是怎么实现的呢?

预编译流程

当预编译完成后,在项目的node_modules/.vite/_metadata.json中,就保存了当前应用所有的(需要预编译的)依赖信息,文件内容结构大致如下:

hash:由预编译的文件内容生成的,文件内容发生变化,hash也会发生变化。由于在预编译过程中比对依赖是否可以使用缓存。

optimized:保存依赖预编译后的信息,包含src(源文件路径)和file(编译后的所在路径)。


{
  "hash": 'd9d8d7v8v',
  "browerHash": "385324875df",
  "optimized": {
    "vue": {
       "file": "/Users/sl/Documents/study/vite/node_modules/.vite/.js",
       "src": "/Users/sl/Documents/study/vite2.0-demo/node_modules/vue/dist/vue.runtime.esm-bundler.js",
       "needsInterop": false
    }
  }
}

重写依赖导入路径

资源解析遇到依赖时,服务器拦截到请求后,检测到此依赖如果被预编译过,那么将会去_metadata.json中找到编译后的依赖所在路径,获取对应路径获取到内容并返回,同时依赖响应内容设置强缓存,从而提高二次访问速度。

热更新

Vite 的热加载原理,其实就是在客户端与服务端建立了一个 websocket 连接。当代码被修改时,服务端监听代码文件的改变,当接收到客户端的消息时,通过不同的消息触发不同的事件,在合适的时机向客户端发送 websocket 信息通知客户端去请求新的模块代码。

参考:

cn.vitejs.dev/guide/#over…

author:@舒兰