大吃一惊!!原来Vite那么简单~

143 阅读5分钟

前言

本文的目的不是讲解Vite的源码,目的在于用通俗的语言,带大家从底层了解vite的实现原理。让大家在脑海中建立一个vite的大致流程。就像看美女一样,从头到脚先打量一番,至于内在,还是要大家自己去要微信号,慢慢接触。

发现美女

首先来打造一个学习环境,创建一个vite的应用,并启动以下命令

pnpm create vite my-vue-app --template vue
cd my-vue-app
pnpm install
pnpm run dev

大家来围观

在项目的package.json文件中,我们可以看到如下内容。

1675317279221.png

精致的脸蛋

找到 Vite 源码,路径packages\vite\bin\vite.js

1675317493298.png

我们可以看到此处会导入cli.js文件,我们进入该文件看看,相对路径为:packages\vite\src\node\cli.ts

cli文件内通过cac(是一个用于构建CLI应用程序的JavaScript库)读取命令行参数,然后执行 action 内的 createServer 函数。

1675318101047.png

S级身材

胸围--本地服务

createServer方法会返回ViteDevServer对象,内部会开启http服务,实现对浏览器请求的响应。

通过浏览器打开 http://127.0.0.1:5173/ 得到的内容是应用的index.html文件内容。如图:

1.png

依据ESM规范在浏览器script标签中的实现,对于<script type="module" src="/src/main.js"></script>内容,当script标签的type属性为module时,浏览器会将请求模块相应内容。

浏览器会发起HTTP请求,ViteDevServer处理http://127.0.0.1:5173/src/main.js 请求后,最终会返回以下内容,如图

1675320231899.png

我们看看main.js源码是什么样子的

1675320296074.png

两者对比一下:

import { createApp } from 'vue'变成了import { createApp } from '/node_modules/.vite/deps/vue.js?v=161dd9e1'

腰围--中间件

由于 import vue 这种模块引入方式,使用的是 Nodejs 特有的模块查找算法(到 node_modules 中取查找),浏览器无法使用,因此 Vite 会将 vue 替换成 /node_modules/.vite/deps/vue.js?v=b92a21b7,当浏览器解析到这行 import 语句时,会发送一个 /node_modules/.vite/deps/vue.js?v=b92a21b7 的请求。

所有请求都会在 ViteDevServer 的中间件处理,而这个请求,会被 static 中间件处理:用于访问静态文件,到会到该目录下,查找文件并返回。

模块的路径是在什么时候被替换的呢?

我们知道,浏览器处理 import 时,会发送一个请求到 Vite Dev Server,然后在中间件处理后,返回模块的内容。

预构建依赖的路径,正是transform 中间件处理过程中被替换的。这里再总结一下:

  • 所有的类 JS 模块(包括 Vue),CSS 模块,都会在 transfrom 中间件中进行处理
  • 每个模块都会经过 resolveIdloadtransform 三个流程,这三个流程,可以通过 Vite 插件去扩展,可以在这三个过程中做一些特殊处理
  • 模块 transform 流程的作用:对代码进行转换,模块路径的替换,正是在这里被修改。

路径替换的插件伪代码:

import { parse } from 'es-module-lexer' // 实现一个 Vite 插件,在 transform 钩子中替换
export default function myPlugin() {
    return {
    // 实现 transform 钩子,code 为当前模块的代码,需要 return 修改过后的代码
        transform(code) {
            // 用 es-module-lexer 解析出模块使用的 import 和 export,里面的信息包含import 语句所在的行数,模块名所在的列数等信息
            // 这些信息可以用来做字符串替换
            let [imports, exports] = parseImports(source) // 根据 import 信息,执行路径替换
            let resCode = /* 路径替换过后的代码 */
            return resCode 
        } 
    }
}

实际上这部分得逻辑,是写在 importAnalysis 插件的,但该插件过于复杂,包含了非常多的功能,因此不会展开叙述,感兴趣的同学也可以自己去查看。

臀围--预构建

路径处理完成后,你会发现这个/node_modules/.vite/deps/vue.js?v=161dd9e1,deps目录下为啥有vue.js文件的。

这就要引出传说中的vite预构建了。

为什么要预构建

首先说明这个vue.js是预构建的产物,那为什么要预构建呢?直接读取node_modules下的vue文件不行吗?

这个问题Vite官网有完整说明

1675324862977.png

原因有如下两点:

  1. 处理兼容性
  2. 减少请求,提高性能

看到上图最后标红的框框没,没错我们只有一个HTTP请求!!!

从项目入口遍历代码,结合AST深度扫描所有绝对路径的import xxx from 'xxx',然后标记所有依赖的第三方库,通过大名鼎鼎的esbuild,将依赖的第三方npm库,打包到各自的文件内!!!然后放到/node_modules/.vite/deps/目录下。

这就是/node_modules/.vite/deps/vue.js?v=161dd9e1文件的由来!斯国一~~

不相信的话,带大家看一下文件内容是否一致

浏览器请求的vue.js文件 1675325335823.png

项目中的文件

1675325402156.png

是不是一模一样!!

下面你应该会问怎么解析.vue后缀的文件呢?

这就要看vite的编译能力了。

点击浏览器查看一下.vue文件里面都是些什么

1675325667311.png

再看看原本长啥样

1675325735256.png

没错它已经被编译成了纯js文件了,只是后缀还是.vue,不信你看

1675325858554.png

Content-Type:在HTTP协议消息头中,使用Content-Type来表示请求和响应中的媒体类型信息。它用来告诉服务端如何处理请求的数据,以及告诉客户端(一般是浏览器)如何解析响应的数据,比如显示图片,解析并展示html等等。

vite偷偷的将.vue文件的 Content-Type 定义为了 application/javascript,也就是告诉浏览器,这个.vue文件其实就是个JS文件。按照JS文件去解析就可以了。

总结

高清无码:

1675327143191.png

最后祝大家新年快乐~

参考: