版本: 3.0.0-alpha.1
(当然,部分内容是基于2.7.0-bate.9
版看的,可能有些许更新,但差别应该不大)
目录结构
vite
| +bin
| — | — openChrome.applescript
| — | — vite.js
| +scripts
| — | - patchTypes.ts
| — | - tsconfig.json
| +src
| — | +client
| — | — | — client.ts
| — | — | — env.ts
| — | — | — overlay.ts
| — | — | — tsconfig.json
| — | +node
| — | — | +__tests__
| — | — | +optimizer
| — | — | +plugins
| — | — | +server
| — | — | +ssr
| — | — | — build.ts
| — | — | — certificate.ts
| — | — | — cli.ts
| — | — | — config.ts
| — | — | — constants.ts
| — | — | — http.ts
| — | — | — index.ts
| — | — | — logger.ts
| — | — | — packages.ts
| — | — | — plugin.ts
| — | — | — preview.ts
| — | — | — publicUtils.ts
| — | — | — tsconfig.json
| — | — | — utils.ts
| +types
| + ... // other files
原理梗概
- 基于浏览器原生
ES Module
的开发服务器,利用浏览器去解析imports
- 对于第三方依赖包(不常变动的)使用
esbuild
进行编译打包后放置于node_modules/.vite
目录下进行缓存- 在服务器端按需编译返回,基本跳过了打包这个概念,服务器随起随用
- 同时搞定热更新,而且热更新的速度不会随着模块增多而变慢
- 针对生产环境可以把同一份代码用
rollup
打包成适应不同环境使用的代码
我们可以注意到两个点:
vite
对应的场景是开发模式,原理是拦截浏览器发出的请求并做相应的处理vite
在开发模式下不需要打包(这里不包含第三方模块),只需要编译浏览器发出的HTTP
请求对应的文件即可,所以热更新速度很快
vite 的实现离不开现代浏览器原生 ES
模块化的支持,当声明一个 <script>
标签,type
属性置为 module
时,浏览器就会发起一个 HTTP
请求来获取,解析内容中的一个 import
语句也同样会发起一个请求来获取。vite
就劫持了这些请求,并在开启的 dev-server
中进行处理再返回给浏览器
由于浏览器只会对需要用到的模块发起请求,所以就不需要像 webpack
一样将所有的内容进行打包 📦 再一起返回,而是只编译浏览器发起 HTTP
请求的模块,这就相当于是 “按需加载”
了
sure-vite
这里我简单实现了一个非常粗糙的简易版 vite ➡️ sure-vite,可以用来大致理解一下 vite 的工作逻辑是什么(参照 vite 1.x
):
基于 Koa 框架启动一个服务,并通过访问静态路径加载 html 入口文件
编写一系列中间件拦截相应的请求或文件进行处理(洋葱模型)
我们写的
.vue
/.tsx
等文件,浏览器是不认识的,我们就需要进行对它们编译,转换成浏览器能够识别的js 文件
我们导入第三方模块时,是直接
import x from 'x'
写它的模块名,而由于 ES Module / 浏览器路径识别的规则,浏览器是无法识别非
./
、../
或/
开头的路径的,那么我们就需要重写它的引用路径jsx// 比如这样: /@modules/ 为任意你想命名的文件夹(当然你得去创建) import React from 'react' /* 浏览器无法识别的路径 */ import React from '/@modules/react' /* 浏览器可以识别的路径 */ // ------------=+=------------ import { createApp } from 'vue' /* 浏览器无法识别的路径 */ import { createApp } from '/@modules/vue' /* 浏览器可以识别的路径 */
上面那步我们重写为
/@modules/
路径(本身是不存在@modules
目录的),但要么去创建这个文件夹(否则请求就会由于找不到路径而返回 404),将打包后的结果放到这个目录下;或者拦截这个路径开头的请求,将它重定向到node_modules
目录下的真实模块路径(我这里是方案二:重定向)然后,再向 html 文件中插入环境变量、添加热更新等额外功能
以上,便是最简单浅显的功能描述了
Bundle vs BundleLess
从下面两张图片,我们可以清晰地看到二者的区别(代表:webpack vs vite):
Bundle Server
的思想就是不管有没有用到这块儿的代码,都会在启动dev-server
之前进行一次性打包,形成一个bundle
后再来启动开发服务器(所以,当我们的一个项目比较大的时候,往往启动它可能就得花费几分钟的时间),另外我们在改动某个模块儿的代码后,热更新也是基于打包后的内容,所以相关依赖会再走一遍打包流程,然后形成新的bundle
交给dev-server
BundleLess Server
的思想就是,你一个文件里的import
更新,那么它只会去重新获取你这个更新的模块儿,其他没有更新的模块儿就会从之前请求的缓存中直接获取,几乎是毫秒级更新页面BundleLess
orUnBundle
?那是因为构建工具(以vite
为例)还是做了打包处理的,我们知道一个import
就会发起一个http
请求来获取,那当我们使用第三方包的时候,它里面又大量引用其他第三方包(以lodash
为例就有几百上千个 api 吧),那加载如此多的模块就会让浏览器资源造成极大的浪费,所以针对第三方不怎么变动的模块及其依赖项就需要进行打包 📦 处理成一个模块,还能结合浏览器的缓存机制将打包后不太变动的包进行缓存,等到依赖变动重新打包才更新缓存文件,从而降低浏览器的压力