npm init vite-app <project-name> cd <project-name> npm install npm run dev
或者
yarn create vite-app <project-name> cd <project-name> yarn yarn dev
vite 是一个基于 Vue3 单文件组件的非打包开发服务器,它做到了本地快速开发启动:
并且vite也成功地革了webpack的命,让webpack开发者直接喊大哥:
那么vite是如何做到这些的呢?
通过运行npm run dev
,可以观察到这个项目是秒级打开,打开调试器可以看到:
浏览器直接请求了.vue
文件,并且后面带了一些type参数。点击这些请求,简单查看一下文件返回内容:
//main.js import { createApp } from '/@modules/vue.js' import App from '/src/App.vue' // import '/src/index.css?import' // createApp(App).mount('#app')
最直观地看到这里:
/@modules/vue.js
./App.vue
转换为/src/App.vue
./index.css
转化为/src/index.css?import
//HelloWorld.vue?type=style&index=0 import { updateStyle } from "/vite/hmr" const css = "\np{color: red;}\n" updateStyle("62a9ebed-0", css) export default css
这里编译了Helloworld.vue中的style样式,将p{color:red}
进行了编译;
//index.css?import import { updateStyle } from "/vite/hmr" const css = "#app {\n font-family: Avenir, Helvetica, Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-align: center;\n color: #2c3e50;\n margin-top: 60px;\n}\n" updateStyle("\"2418ba23\"", css) export default css
同时还对全局样式进行了更新监听。
既然浏览器直接请求了.vue
文件,那么文件内容是如何做出解析的呢。项目是如何在不使用webpack等打包工具的条件下如何直接运行vue文件。
从上面的代码片段中可以看到,最明显的特征就是使用了ES Module,代码以模块的形式引入到文件,同时实现了按需加载。
其最大的特点是在浏览器端使用 export import
的方式导入和导出模块,在 script 标签里设置 type="module"
,然后使用 ES module。
正因如此,vite高度依赖module script
特性,也就意味着从这里开始抛弃了IE市场,参见Javascript MDN。
在这种操作下,伴随的另一个效果就是去掉了webpack打包步骤,不用再将各个模块文件打包成一个bundle,以便支持浏览器的模块化加载。那么vite是如何处理这些模块的呢?
关键在于vite使用Koa
构建的服务端,在createServer
中主要通过中间件注册相关功能。
vite 对 import
都做了一层处理,其过程如下:
"vue" => "/@modules/vue"
将处理的template,script,style等所需的依赖以http请求的形式,通过query参数形式区分并加载SFC文件各个模块内容。
为什么这里需要@modules
?
举个栗子:
import vue from 'vue'
vue模块安装在node_modules
中,浏览器ES Module
是无法直接获取到项目下node_modules目录中的文件。所以vite
对import
都做了一层处理,重写了前缀使其带有@modules
,以便项目访问引用资源;另一方面,把文件路径都写进同一个@modules中,类似面向切片编程,可以从中再进行其他操作而不影响其他部分资源,比如后续可加入alias等其他配置。
通过koa middleware正则匹配上带有@modules
的资源,再通过require('XXX')获取到导出资源并返给浏览器。
单页面文件的请求有个特点,都是以*.vue
作为请求路径结尾,当服务器接收到这种特点的http请求,主要处理
ctx.path
确定请求具体的vue文件parseSFC
解析该文件,获得descriptor
,一个descriptor
包含了这个组件的基本信息,包括template
、script
和styles
等属性 下面是Comp.vue
文件经过处理后获得的descriptor
然后根据descriptor
和ctx.query.type
选择对应类型的方法,处理后返回ctx.body
script
标签,使用compileSFCMain
方法返回js
内容template
时表示处理template
标签,使用compileSFCTemplate
方法返回render
方法style
s时表示处理style
标签,使用compileSFCStyle
方法返回css
文件内容在浏览器里使用 ES module 是使用 http 请求拿到的模块,所以 vite 必须提供一个 web server
去代理这些模块,上文中提到的 koa中间件
就是负责这个事情,vite 通过对请求路径query.type
的劫持获取资源的内容返回给浏览器,然后通过拼接不同的处理单页面文件解析后的各个资源文件,最后响应给浏览器进行渲染。
从另一方面来看,这也是一个非常有趣的方法,webpack之类的打包工具会把各种各样的模块提前打包进bundle中,但打包结果是静态的,不管某个模块的代码是否用得到,它都要被打包进去,显而易见的坏处就是随着项目越来越大,打包文件也越来越大。vite的优雅之处就在于需要某个模块时动态引入,而不是提前打包,自然而然提高了开发体验。
vite的热更新主要有四步:
在client端,Websocket监听了一些更新的消息类型,然后分别处理:
HMRRuntime.reload
HMRRuntime.rerender
window.reload
刷新页面在server端,通过watcher监听页面改动,根据文件类型判断是js Reload还是vue Reload。通过解析器拿到当前文件内容,并与缓存里的上一次解析结果进行比较,如果发生改变则执行相应的render。
本文简述了vite
的启动链路和背后的简易原理,虽然短时间内vite
不会替代webpack
,但是能够看到vite
的强大潜力和不可阻挡的趋势。vite
的更新实在是太快了,特别佩服尤大的勤奋和开源精神,目前还在快速迭代中,后续将会有单独的文章来分析各个内部原理实现,希望大家多多期待,有任何问题欢迎随时交流。
欢迎大家关注作者公众号:前端优选