前置知识
ES Modules
浏览器原生支持的,支持直接在浏览器中执行import。
developer.mozilla.org/en-US/docs/…
ESbuild
一个类似webpack的构建工具,使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。编译快的原因主要有以下几点:
- 它是用 Go 语言编写的,该语言可以编译为原生代码;
- 解析,打印和源映射生成全部完全并行化;
- 无需昂贵的数据转换,只需很少的几步即可完成所有操作;
- 编写代码时处处注意速度表现,并尽量避免不必要的配置。
Vite是什么?
Vite,一个基于浏览器原生 ES imports 的构建工具。在开发环境下利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用;在生产环境下使用rollup进行打包。主要由两部分组成:
- 一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)。
- 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。
vs webpack?
webpack在启动时会从入口开始逐步经历语法解析、依赖手机、代码转译、打包合并等一系列操作,并将打包的产物保存在内存中。由于多了打包构建这一层,所以当我们的应用规模越来越大时,使用 JavaScript 开发的工具通常需要很长时间才能启动开发服务器,即使使用 HMR,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。
而Vite相较于webpack,则没有了打包的过程,在启动后主要做了两件事情来改进启动速度:
- 按需编译:利用浏览器原生 ES Module 的支持,浏览器直接向 dev server 逐个请求各个模块,而不需要提前把所有文件打包。
- 依赖预编译:借助 esbuild 超快的编译速度把第三方库进行预编译。
按需编译
核心是利用原生 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 信息通知客户端去请求新的模块代码。
参考:
author:@舒兰