一、背景
Vue作者尤雨溪在今年4月提出了一个由Vue3搭载的前端开发工具Vite。Vite主要提供了前端开发服务器的功能以及生产环境打包的功能,而其主要突破则是在前端开发服务器这一方面,提供了一种基于ES Module的快速的本地开发服务器。
二、Vite简介
2.1 什么是Vite
在本文编辑时,Vite版本仍处于1.0.0-rc.9,尚未正式发布,并且Vite目前主要支持Vue3项目,尚不识别Vue2语法。下面是引用尤雨溪在微博上对Vite的介绍。
Vite,一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。同时不仅有 Vue 文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。针对生产环境则可以把同一份代码用 rollup 打。虽然现在还比较粗糙,但这个方向我觉得是有潜力的,做得好可以彻底解决改一行代码等半天热更新的问题。
2.1 如何使用Vite
使用下面的命令即可快速搭建一个使用Vite作为开发服务器的项目,使用十分方便,类似于Vue-cli。
$ npm init vite-app <project-name> $ cd <project-name> $ npm install $ npm run dev
第一行命令的目的就是从npm仓库拉取 create-vite-app
这个包,然后全局安装,最后使用它创建基于Vite的模板项目。
三、Vite特性
官方文档介绍,Vite主要有下面三个特性
- 快速的冷启动
- 及时的热模块更新
- 真正的按需加载 我们对比
create-vite-app
和Vue-cli
创建的项目,来看一下Vite快在哪里,下图分别是使用Vite和Vue-cli(webpack)
启动本地开发服务器的过程。
可以看出Vite相对于Vue-cli(webpack)在本地服务器启动时省略了打包步骤,因而做到了冷启动秒开的效果,并且这个速度提升会随着项目模块增多而愈加明显。下面我们就看一下Vite是如何实现其快速的特性。
四、Vite原理
Vite 是基于浏览器原生 ES imports 的开发服务器。因而我们首先需要了解浏览器是如何支持ES Module的
4.1 ES Module
首先,我们来看一下在浏览器如何使用ES Module。打开浏览器调试面板, 清空network,使用下面代码在页面动态插入一段 <script>
代码
const script = document.createElement('script')script.setAttribute('type', 'module');script.innerHTML = 'import {test} from "./test.js"'document.body.append(script)
在运行上述代码后,浏览器向当前服务器目录发送了 http://km.oa.com/test.js
的请求。我们都知道本地项目中我们使用ES import会从文件系统读取相应路径的模块,浏览器则是将模块路径转换为Url。
浏览器解析ES module的过程如上图所示。
- 识别带有熟悉
type="module"
的<script>标签
- 获取并解析该标签内的js内容。
- 识别
import
语法,生成请求url,向服务器请求该地址的模块
可以说浏览器对于ES Module的支持实现了真正的按需加载,省略了前端打包的过程,对于减少首屏加载时间是有极大帮助的。但是我们要在生产环境中使用它必须知道浏览器的支持度到底如何。
下面是一张caniuse中说明的浏览器对于 ES Module的静态import语法的支持情况。可以看出除了IE外的主流浏览器基本上都支持了 ES Module的import语法。
那么,对于不支持ES Module的浏览器,难道我们就让项目跑不起来吗?
当然不是,在 script 标签中使用 nomodule 属性,可以确保向后兼容。
像上图一样提供ES Module方式和非ES Module方式的代码,对于支持ES Module的浏览器,其会忽视 nomodule
类型的script,而对于无法识别 Es Module的浏览器则会直接使用 nomodule
的script代码。因此我们只需提供一份打包好的代码,放在 nomodule
标签内就可以实现向后兼容
值得注意的是,浏览器只能解析以’/’, ‘./’, 或 '…/'开头的模块路径,对于像引用nodemodules中的模块,比如像下面引用Vue的方式,浏览器无法识别,会报错。因此对于nodemodules的引用,需要另外处理,而Vite也给出了解决方案。
import Vue from 'vue'
4.2 Vite开发服务器如何使用ES Module
我们启动Vite本地开发服务器,用浏览器打开入口页面,观察浏览器的NetWork面板.如下图所示,
- 浏览器加载了入口html,解析发现
main.js
, - 发现
main.js
中包含ES Module, 解析import
语法,发现有三个import
- 根据
import
,发出所依赖的模块的Http请求 - 依次类推,边解析边请求。
对比源码和网络请求,我们会发现网络请求数明显要多于源码中 import
的个数。多出的网络请求主要是两类
.vue
文件相关 对于一个Vue组件SFC(Single File Components),其主要包含三类代码,模板、script、样式。由于浏览器是无法识别vue文件的,一个vue文件会被拆分为三个请求.vue
,.vue?type=template
,.vue?type=style
,这些都需要借助Vite的本地服务器实现,具体实现方法下文会详细阐述。- 热更新相关 我们看到请求中有个
clent.js
,还有websocket请求,这些都是为热更新服务的,而在代码中插入建立websocket连接需要的clent.js
逻辑也是由Vite开发服务器实现的。
4.3 Vite 开发服务器模块处理
对于浏览器不识别的node_module引用如何处理?对于 .vue
文件如何处理都是由Vite开发服务器实现的。首先我们看一下Vite开发服务器架构图
Vite开发服务器是基于Koa框架的,利用Koa中间件实现模块解析以及热更新的主要功能。这一节我们主要看一下Vite是如何处理模块的。
nodemodules 模块处理过程 对nodemodules的处理主要由中间件
serverPluginModuleRewrite
完成,其主要过程如下
- 在 koa 中间件里获取请求 body
- 通过 es-module-lexer 解析资源 ast 拿到 import 的内容
- 判断 import 的资源是否是绝对路径,绝对视为 npm 模块
- 返回处理后的资源路径:“vue” => “/@modules/vue”
vue文件处理过程 对vue组件的处理由
serverPluginVue
来实现,其处理流程如下
- 分析请求路径,是否包含查询字段type
- 不包含type的请求视为script内容请求, 返回类型为
js
- 对于type为template请求则返回.vue文件的
template
内容的渲染函数,返回类型为js
- 对于type为style的请求则返回.vue文件中
style
标签内样式的动态插入函数
4.4 Vite 热更新
如上图所示,Vite热更新也是基于Websocket。在Vite服务器启动时,Vite利用中间件 serverPluginHtml
在html中插入 client.js
. 这个js文件主要用于在建立浏览器和Vite服务器之间的Websocket通信。热更新的步骤如下
- Vite服务器监听本地文件更新
- 对比缓存中的文件和变动后的文件,组织更新内容
- 服务器通过PostMessage向浏览器通知更新消息,更新消息包含跟新类型,更新后模块的最新地址,时间戳
- 浏览器请求热更新文件
- 根据跟新类型处理返回的文件
clientjs监听的更新消息类型
- connected: WebSocket 连接成功
- vue-reload: Vue 组件重新加载(当你修改了 script 里的内容时)
- vue-rerender: Vue 组件重新渲染(当你修改了 template 里的内容时)
- style-update: 样式更新
- style-remove: 样式移除
- js-update: js 文件更新
- full-reload: fallback 机制,网页重刷新
总结
Vite 提供了一个更快的开发环境服务器, 其实现原理基于ES模块,通过开发环境去打包将构建时间从 O(n) 减少到 O(1), 其搭载Vue3发布,借助Vue生态,在未来有更广泛的使用场景。