前端框架源码解读之Vite

前端工具链十年盘点:https://mp.weixin.qq.com/s/FBxVpcdVobgJ9rGxRC2zfg

Webpack、Rollup 、Esbuild、Vite ?

  • webpack: 基于 JavaScript 开发的前端打包构建框架,通过依赖收集,模块解析,生成 chunk,最终输出生成的打包产物,是一个BundleBased的框架,优点是大而全,缺点是配置繁琐

  • Rollup: Rollup 是专门针对类库进行打包,它的优点是小巧而专注,现在很多我们熟知的库都都使用它进行打包,比如:Vue、React 和 three.js 等。

  • Esbuild: 一个基于 Go 编写的高性能构建工具,和其他构建工具相比,速度快到  10-100x,其内置了一些 Loader 能解析编译常见的 JS(X)、TS(X)等文件,同时支持通过插件的形式处理其他类型的文件。

  • Vite: Vite 基于_ESMBased_devServer 在开发环境实现了快速启动、按需编译、即时模块热更新等能力,同时针对同一份代码,在生产环境通过 Rollup 进行打包,生成线上产物。

Vite 简介

ae4f8e26a8ea5b0790ac7a8c1c05308e.png

背景驱动

目前比较成熟的前端开发构建工具,如 webpack 等,基本是通过“打包”的方式来进行源码构建,即通过对源码进行依赖收集、构建处理,最终生成可在浏览器运行的 JS 文件,然而随着项目增长,他们存在以下问题

  1. 打包构建时间也会随着增长,项目本地启动缓慢

  1. 更新缓慢,即使使用 HMR 开发,也需要几秒的时间代码变更才能反映到页面上,严重影响开发体验

得益于现在前端生态系统的快速发展,Vite 基于下面两个新特性去解决上述存在的问题

  1. 浏览器开始支持 原生 ES 模块

  1. 越来越多 JavaScript 工具使用编译型语言如 Go 等进行编写,加快了构建速度

核心功能

020b8c2dbcca6511f78766694abb6c5e.png

804ff5b19eba15c988efcbc65f988f8c.png

  • 本地开发环境,利用浏览器支持原生 ESM 文件的特性,不对源代码进行打包操作,浏览器直接动态引入资源,并在 devServer 对请求的资源进行处理,最终返回浏览器可运行的内容

  • 依赖预构建,首次启动的时候,通过 Esbuild 对项目的依赖进行预构建,并缓存在本地,后续浏览器请求的时候可以直接返回

  • 更高效的HMR模块,利用浏览器的缓存特性,优化资源的请求,使得无论应用大小如何,HMR 始终能保持快速更新

  • 生产产物构建时,基于 Rollup 进行打包,并提供了一套  构建优化  的  构建命令,开箱即用。

Vite 核心模块原理

本次分享主要介绍最核心的两个功能的实现原理

  • 依赖预构建

  • 浏览器模块加载流程

源码初识

源码版本:v2.8.2

之前有简单了解过 Webpack 的源码,看的一头雾水,这一层层的 callback 都是些啥?然而 vite 框架的源码看起来就很简洁明了,非常易懂

./src
├── client # 客户端运行时WEB SOCKET以及HMR相关的代码
│   ├── client.ts
│   ├── env.ts
│   ├── overlay.ts
│   └── tsconfig.json
└── node # 本地服务器相关代码├── __tests__├── build.ts # 生产环境rollup build代码├── certificate.ts├── cli.ts # cli,入口├── config.ts├── constants.ts├── http.ts├── importGlob.ts├── index.ts # 导出出口├── logger.ts├── optimizer # 依赖预构建├── packages.ts├── plugin.ts├── plugins # 插件├── preview.ts # build构建后,在预览模式下启动Vite Server,以模拟生产部署├── server # server文件夹,dev环境主要代码├── ssr├── tsconfig.json└── utils.ts7 directories, 18 files

我们主要关注server目录下的代码,框架通过在本地启动一个 http+connect 的服务器,然后在启动之前做一些优化操作主入口在src/server/index.tscreateServer函数中,这个函数里做了以下几件事情

6cab0c515cdd144bfcd9422bea76674e.png

流程初始化

1)调用resolveConfig函数,解析合并各种配置

2)初始化一个http+connect服务器

3)创建插件容器 ,createPluginContainer方法,把插件的各个钩子函数串联起来,后续在请求处理的过程中直接执行挂载好的钩子函数

4)生成一个server对象,包含配置信息、服务器信息、一些辅助函数等

5)配置一系列内置中间件,各个中间件做的事情,可以参考文章https://www.modb.pro/db/966326)返回 server 对象

调用 server 的 listen 方法

1)运行插件containerbuildStart钩子,进而运行所有插件的buildStart钩子

2)进行依赖预构建,运行runOptimize函数。

3)开启服务,监听端口

请求处理流程

1)主要处理流程在tansformMiddleware中间件处理,这部分后面的内容会详细介绍

依赖预构建

进行依赖预构建有两个目的:

  1. CommonJS 和 UMD 兼容性: 开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。

  1. 性能:Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。例如将 lodash 中的小模块打包成一个大的文件

参数配置

首先看一下,vite 配置中关于 optimizeDeps 的入参

export interface DepOptimizationOptions {/*** 入口文件,默认从html文件进行解析收集依赖,如果配置了的话,就从配置文件开始进行解析*/entries?: string | string[]/*** 需要进行预构建的文件*/include?: string[]/*** 不需要进行预构建的依赖*/exclude?: string[]/*** 预构建是通过esbuild进行的,所以可以自定义配置esbuild参数*/esbuildOptions?: Omit<EsbuildBuildOptions,| 'bundle'| 'entryPoints'| 'external'| 'write'| 'watch'| 'outdir'| 'outfile'| 'outbase'| 'outExtension'| 'metafile'>
}

预构建结果

预构建的结果默认保存在node_modules/.vite中,具体预构建的依赖列表在_metadata.json 文件中,其中_metadata.json 的内容为一个 json 结构

{// 配置的hash值hash :  afcda65e ,/*** 主要用于浏览器获取预构建的 npm 依赖时,添加的查询字符串* 在依赖变化时,浏览器能更新缓存*/browserHash :  c369dd06 ,optimized : { // 预构建的优化列表react : {// 构建后的文件地址file :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/react.js ,// 原始文件地址src :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/react/index.js ,// 记录那些在依赖预构建时,使用了commonjs语法的依赖// 如果使用了commonjs语法,那么 needsInterop 为 trueneedsInterop : true},react-dom : {file :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/react-dom.js ,src :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/react-dom/index.js ,needsInterop : true},lodash : {file :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/lodash.js ,src :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/lodash/lodash.js ,needsInterop : true},react/jsx-dev-runtime : {file :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/react_jsx-dev-runtime.js ,src :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/react/jsx-dev-runtime.js ,needsInterop : true}}
}

预构建过程

入口文件:src/node/optimizer/index.ts,入口函数:optimizeDeps,构建过程如下

033b490e82276e429c1033a3a0062e0f.png

  1. 调用getDepHash()函数去计算当前依赖相关的的 hash 值,影响依赖预构建 hash 值的内容有

    1. 包管理器的 lockfile,例如 package-lock.json,yarn.lock,或者 pnpm-lock.yaml

    2. vite.config.js 中的部分相关配置,如 plugins、optimizeDeps 的 include 和 exclude 等

  1. 读取本地_metadata.json 中的 hash 值,判断和计算出来的是否一致,一致且未设置强制构建的话,则直接结束预构建过程,否则需要进入预构建过程

  2. 通过({ deps, missing } = await ``scanImports``(config));进行依赖扫描,得到需要处理的依赖,deps 是一个对象,是依赖的包名和文件系统中的路径的映射,如下图所示

019e7770735fcc05c1bf77d58f896d72.png

其中,scanImports方法会扫描根目录下的所有 .html 文件或者用户配置对 optimizeDeps.entries 文件,然后找到文件中所有的 script 标签,这样就找到了入口 js 文件,之后调用 esbuild,通过配置的插件,就可以一层层的找到对应的依赖项了
  1. 使用es-module-lexerparse处理所有的 deps,获得其中exportsData内容 ,并得到依赖 id 到exportsData的映射,用于之后esbuild构建时进行依赖图分析并打包到一个文件里面,parse 解析后的结构如下图所示

    698c4630c68cd6062c62d05ec402fa45.png


  1. 调用esbuild进行依赖的预构建,并将构建之后的文件写入缓存目录node_modules/.vite,得益于 esbuild 比传统构建工具快 10-100 倍的速度,所以依赖预构建也是非常快的

  2. 47039a4f659eb14018b0c7f070387a46.png

  1. 将 metadata 信息写进本地缓存目录下,后续可以直接使用缓存的依赖

依赖访问过程

在进行了依赖预构建之后,如何访问这些已经构建的依赖呢

1)在加载资源文件的时候,会通过vite:import-analysis插件进行依赖解析,碰到已经进行预构建的依赖,直接替换,将import React from 'react'替换成import __vite__cjsImport2_react from /node_modules/.vite/react.js?v=0f16c3f0这样的形式

2)在浏览器去请求资源的时候,通过resolvePlugin插件去解析,获得真正的本地文件,匹配到对应的本地缓存资源

模块加载

对于浏览器请求,针对一个文件的访问,vite 会如何进行处理呢?

主要由以下两个中间件来统一处理请求的内容,并在中间件处理的流程中调用 vite 插件容器的相关钩子函数

  • transformMiddleware:核心中间件处理代码

  • indexHtmlMiddleware:html 相关请求处理中间件

vite 插件体系

在这里,我们先了解一下 vite 的插件体系,Vite 插件扩展了设计出色的 Rollup 接口,带有一些 Vite 独有的配置项。

因此,你只需要编写一个 Vite 插件,就可以同时为开发环境和生产环境工作。vite 的插件其实就是定义一个对象,该对象包含了一系列的 hook 函数配置

export default function myPlugin() {const virtualModuleId = '@my-virtual-module'const resolvedVirtualModuleId = '\0' + virtualModuleIdreturn {name: 'my-plugin', // 必须的,将会在 warning 和 error 中显示resolveId(id) {if (id === virtualModuleId) {return resolvedVirtualModuleId}},load(id) {if (id === resolvedVirtualModuleId) {return `export const msg =  from virtual module `}}}
}

Rollup 插件兼容性

相当数量的 Rollup 插件将直接作为 Vite 插件工作,但并不是所有的,因为有些插件钩子在非构建式的开发服务器上下文中没有意义。

一般来说,只要 Rollup 插件符合以下标准,它就应该像 Vite 插件一样工作:

  • 没有使用moduleParsed钩子。

  • 它在打包钩子和输出钩子之间没有很强的耦合。

和rollup保持一致的通用钩子以下钩子在服务器启动时被调用:

  • options

  • buildStart

以下钩子会在每个传入模块请求时被调用:

  • resolveId

  • load

  • transform

以下钩子在服务器关闭时被调用:

  • buildEnd

  • closeBundle

Vite 独有钩子

Vite 插件也可以提供钩子来服务于特定的 Vite 目标。这些钩子会被 Rollup 忽略。

  • config

  • configResolved

  • transformIndexHtml

  • handleHotUpdate

具体插件执行过程

1)在 dev 环境模拟了一套和 rollup 保持一致的插件运行环境,确保在开发环境和生产环节的核心环节执行同样的流程

2)vite 通过createPluginContainer创建了一个插件容器,将每个插件中对应的 hook 收集起来

3)最终在各个生命周期阶段,执行对应的已经收集好的钩子

模块请求加载过程

23d116be2afd5e00817e318e7e180d54.png


GET /

当访问页面的时候,实际是有一个 GET / => /index.html 的重定向进入 indexHtmlMiddleware 这个过程,主要做了一件事情,注入 dev 环境需要的一些依赖,@vite/client 主要用来和服务器进行 ws 通信并处理一些 hmr 相关的工作,@react/refresh这段代码,是 vite-plugin-react 插件注入的代码,用来处理 dev 环境的一些能力

3360316e6f8a7892537ac7f5eda42fa0.png

GET /@vite/client

前面讲到,@vite/client 里面的代码主要用于与服务器进行 ws 通信来进行 hmr 热更新、以及重载页面等操作。

这个请求会直接进入 transformMiddleware 中间件中,进入中间件的处理过程:中间件会调用transformRequest(url, server, options = {})函数

  1. @vite/client 是如何映射到对应的内容呢,在调用pluginContainer.resolveId的过程中会遇到 aliasPlugin 插件的钩子,执行名称替换,最终替换成vite/dist/client/client.mjs

  1. 继续将改写过的路径传给下一个插件,最终进入resolvePlugin插件的tryNodeResolve函数,最终解析获得该文件的 id 为/Users/zhachunliu/.nvm/versions/node/v14.17.0/lib/node_modules/vite/dist/client/client.mjs

  2. 最终通过pluginContainer.load获取加载本地文件,然后通过pluginContainer.transform进行代码转换,将转换后的代码通过send方法发送给浏览器

GET /src/main.tsx

针对普通的 tsx 文件的请求,流程基本上和上面介绍的GET /@vite/client一致,不同点在于使用的插件钩子内容不一样,因为需要对 tsx 文件进行处理成 js

  1. 通过 resolveId 钩子函数,将/src/main.tsx 映射到本地文件系统

  1. 调用 load 钩子函数,加载本地文件到内存中

  2. 通过 vite:react-babel 插件,将 jsx 语法进行转换,转换成 js 代码

  3. 通过 vite:esbuild 插件,进行代码格式化

  4. 通过 vite:import-analysis 插件,将代码中所有的 import 内容,转换成对应的本地文件,方便后续直接请求

  5. 返回结果

其他的所有请求,都是经过类似的插件处理流程,最终返回给浏览器一段可执行的 JS 代码,就不一一介绍了。

vite 调试工具

vite-plugin-inspect(插件调试工具,强推)

在学习、调试或创作插件时,建议在你的项目中引入vite-plugin-inspect。它可以帮助你检查 Vite 插件的中间状态。安装后,你可以访问localhost:3000/__inspect/来检查你项目的模块和栈信息。请查阅vite-plugin-inspect 文档中的安装说明。

aecabd3f9f2225a7b17990cad74a02bd.png

Vite debug 模式

通过vite --force --debug命令,可以明确的了解到,启动过程和请求过程,经历了什么插件,具体的执行流程等,方便调试

a802791388df818e1df983bb757c4345.png

参考资料

  • 前端工具链十年盘点:https://mp.weixin.qq.com/s/FBxVpcdVobgJ9rGxRC2zfg

  • 如何调试 vite 源码:https://maximomussini.com/posts/debugging-javascript-libraries

  • 源码理解:https://jishuin.proginn.com/p/763bfbd5f00e


17e17a8bdb23bad775ff6fb5d48d44e6.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,最近组织了源码共读活动,帮助3000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

dc43889f705e613bca06210a2c251572.png

扫码加我微信 ruochuan02、拉你进源码共读

今日话题

目前建有江西|湖南|湖北 籍 前端群,想进群的可以加我微信 ruochuan12 进群。分享、收藏、点赞、在看我的文章就是对我最大的支持~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/274731.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

hp-ux_UX中的格式塔-或-为什么设计师如此讨厌间距

hp-uxI’ve been lucky so far in my design career to have worked with engineers that seem genuinely interested in learning about design. Perhaps, as mentioned in the title, it’s more about them trying to figure out why it matters so much to us that there i…

JavaScript 数组新增 4 个非破坏性方法!

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

自行车改装电动车怎么样_电动车听起来应该是什么样?

自行车改装电动车怎么样The sound of an all-electric car accelerating doesn’t have to sound like a standard combustion engine, It could sound like anything.全电动汽车加速的声音不必听起来像是标准的内燃机&#xff0c;它可以听起来像任何东西。 These were the wor…

谷歌pay破解_Google Pay缺少Google闻名的一件事-UX案例研究

谷歌pay破解Disclaimer: The views expressed in the blog post is purely based on personal experience. It was not influenced by any external factor.When Google launched Tez (now Google Pay) in India during 2017, their primary goal was to design a simple payme…

进阶高级前端,这位大前端架构师一定不能错过

今天给大家介绍一位好朋友&#xff1a;这波能反杀&#xff1a;一位拥有十年工作经验&#xff0c;对学习方法有独到理解的资深大前端架构师。一、博客早在 2017 年初&#xff0c;波神在简书平台以《前端基础进阶》为名&#xff0c;更新了一系列优质文章&#xff0c;获得大量认可…

memcached应用策略(转)

memcached应用策略&#xff08;转&#xff09;(2012-04-05 11:10:02) 转载▼标签&#xff1a; memcached 应用策略 it分类&#xff1a; linux_c memcached应用策略memcached 主要的作用是为减轻大访问量对数据库的冲击&#xff0c;所以一般的逻辑是首先从memcached中读取数据&a…

突然讨厌做前端,讨厌代码_为什么用户讨厌重新设计

突然讨厌做前端,讨厌代码重点 (Top highlight)The core of design thinking is to only design something that will bring value and fill the gap in consumer needs. Right? Why else would one design something that no one asked for? While that may be true to some …

那些年我面过的「六年经验」的初级工程师

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以 点此加我微信ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

更多信息请关注微信公众号_为什么我们更多地关注表面异常?

更多信息请关注微信公众号Don’t you feel lucky to find a single seasoned curly fry in your bunch of plain old boring french fries? Do you remember highlighting important texts of your study materials before the exams? Both situations might seem irrelevant…

eclipse中的汉字极小的解决方案(转载)

eclipse中的汉字极小的解决方案(转载) 可能新装了eclipse后&#xff0c;写java代码的时候发现&#xff0c;写注释的时候发现&#xff0c;汉字小的可怜&#xff0c;网上搜一下&#xff0c;又是改字体又是设置字体大小&#xff0c;试用后发现都不是针对这个的方法。 无奈在自己摸…

面试官经常问的观察者模式如何实现~

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以 点此加我微信ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

旅行者 问题_门槛项目:没有旅行者回到他的原籍城市。

旅行者 问题Sohini Mukherjee| MFA| Spring 2020Sohini Mukherjee | 外交部| 2020年Spring Artivive app to see the full Artivive应用程序可查看完整的#AR experience.#AR体验。 Prompt:提示&#xff1a; As second semester, first year graduate students, you are at a …

产品经理懂技术=流氓会武术(zz)

最近七年&#xff0c;我都在做互联网产品&#xff0c;其中前五年分别在创业公司和上市公司里&#xff0c;做别人的产品&#xff1b;近两年在创业&#xff0c;做自己的产品。 我的体会是&#xff1a;产品经理需要懂技术&#xff0c;创业者尤其需要。但前提是你总觉得有股憋不住的…

技术人的七大必备特质

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以 点此加我微信ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

figma下载_在Figma中进行原型制作的技巧和窍门

figma下载自定义过渡和微交互 (Custom transitions and micro-interactions) Yep, I know that there are a lot of useful built-in transition effects in Figma already, but here I want to talk about custom micro-interactions, complicated transitions and show you h…

不想当全栈的设计师不是_但我不想成为产品设计师

不想当全栈的设计师不是重点 (Top highlight)I’ve made a huge mistake, I thought to myself, as a realization washed over me in the middle of an interview for a product design role.我对自己想&#xff0c;我犯了一个巨大的错误&#xff0c;因为在接受产品设计职务的…

学习 WCF (6)--学习调用WCF服务的各种方法

来自&#xff1a;http://www.cnblogs.com/gaoweipeng/archive/2009/07/26/1528263.html 根据不同的情况&#xff0c;我们可以用不同的方法调用WCF服务&#xff0c;本文简单总结了一下调用WCF的一些方法(代理类&#xff0c;Ajax...)&#xff0c;分享给大家。开发工具调用WCF 这中…

[科普文] Vue3 到底更新了什么?

Vue3 已经发布一段时间了&#xff0c;这个版本从底层实现到上层 API 设计都发生了非常大的变化&#xff0c;但具体改变了些什么呢&#xff1f;一起简单盘点下&#xff1a;一、Composition API使用传统的option配置方法写组件的时候问题&#xff0c;随着业务复杂度越来越高&…

ipados_如何设计具有最新iPadOS 14功能的出色iPad应用

ipadosWe all know that iPad Pro already has a seriously powerful computing power and that it’s possible to create meaningful stuff with Apple Pen.我们都知道iPad Pro已经具有强大的计算能力&#xff0c;并且可以使用Apple Pen创建有意义的东西。 But do we really…

67行JS代码实现队列取代数组,面试官刮目相看

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以 点此加我微信ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…