(四)什么是Vite——冷启动时vite做了什么(源码、middlewares)

 vite分享ppt,感兴趣的可以下载:

​​​​​​​Vite分享、原理介绍ppt

什么是vite系列目录:

(一)什么是Vite——vite介绍与使用-CSDN博客

(二)什么是Vite——Vite 和 Webpack 区别(冷启动)-CSDN博客

(三)什么是Vite——Vite 主体流程(运行npm run dev后发生了什么?)-CSDN博客

(四)什么是Vite——冷启动时vite做了什么(源码、middlewares)-CSDN博客

(五)什么是Vite——冷启动时vite做了什么(依赖、预构建)-CSDN博客

未完待续。。。

冷启动时vite做了什么:

vite主要遵循的是使用 ESM ( Es modules 模块)的规范来执行代码,由于现代浏览器基本上都支持了 ESM 规范,所以在开发阶段并不需要将代码打包编译成 es5 模块即可在浏览器上运行。我们只需要从入口文件出发, 在遇到对应的 import 语句时,将对应的模块加载到浏览器中就可以了(按需加载)。同时 ts/jsx 等文件的转译工作也会借助了 esbuild 来提升速度。Vite在内部实现上,会启动一个 dev server , 并接受独立模块的HTTP请求,并让浏览器自身去解析和处理模块加载

模块解析

对于引用的模块,vite将其分为了 依赖 和 源码 两类,对其进行不同的处理。

先来看一下对于 源码 的处理。我们打开请求的App.vue文件,可以看到里面的内容已经不是源码了,而是经过处理后的代码。

middlewares 中内容转换

Vite 中源文件的转换是在 dev server 启动以后通过 middlewares 实现的。

当浏览器发起请求以后,dev sever 会通过相应的 middlewares (transformMiddleware 、indexHtmlMiddleware)对请求做处理,然后将处理以后的内容返回给浏览器。

middlewares 对源文件的处理,分为 resolve、load、transform、parser 四个过程:

  1. resolve - 解析 url,找到源文件的绝对路径;
  2. load - 加载源文件。如果是第三方依赖,直接将预构建内容返回给浏览器;如果是业务代码,继续 transform、parser。
  3. transfrom - 对源文件内容做转换,即 ts -> js, less -> css 等。转换完成的内容可以直接返回给浏览器了。
  4. parser - 对转换以后的内容做分析,找到依赖模块,对依赖模块做预转换 - pre transform 操作,即重复 1 - 4。
    1. pre transform 是 Vite 做的一个优化点。预转换的内容会先做缓存,等浏览器发起请求以后,如果已经完成转换,直接将缓存的内容返回给浏览器。

Vite 在处理步骤 3 时,是通过 esbuild.transform 实现的,对比 Webpack 使用各个 loader 处理源文件,那是非常简单、快捷的。

模块请求加载过程:一个请求的 Vite 之旅

当浏览器一个请求到vite服务时,发生了什么?

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

  • transformMiddleware:核心中间件处理代码
  • indexHtmlMiddleware:html 相关请求处理中间件

GET localhost :实际 GET / => /index.html

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

GET client :实际 GET /@vite/client

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

  1. @vite/client 是如何映射到对应的内容呢,在调用 pluginContainer.resolveId 的过程中会遇到 aliasPlugin 插件的钩子,执行名称替换,最终替换成 vite/dist/client/client.mjs
  1. 继续将改写过的路径传给下一个插件,最终进入 resolvePlugin 插件的 tryNodeResolve 函数,最终解析获得该文件的 id 为/lib/node_modules/vite/dist/client/client.mjs
  2. 最终通过 pluginContainer.load 获取加载本地文件,然后通过 pluginContainer.transform 进行代码转换,将转换后的代码通过 send 方法发送给浏览器。

有无 middlewares 的影响:例子—— vue-dev-server-analysis

(尤雨溪开发vue dev server理解vite原理 - 编程宝库)

# 克隆官方仓库
git clone https://github.com/vuejs/vue-dev-server.git
cd vue-dev-server
# npm i -g yarn
# 安装依赖
yarn

 一般来说,我们看源码先从package.json文件开始:

// vue-dev-server/package.json
{
  "name": "@vue/dev-server",
  "version": "0.1.1",
  "description": "Instant dev server for Vue single file components",
  "main": "middleware.js",
  // 指定可执行的命令
  "bin": {
    "vue-dev-server": "./bin/vue-dev-server.js"
  },
  "scripts": {
    // 先跳转到 test 文件夹,再用 Node 执行 vue-dev-server 文件
    "test": "cd test && node ../bin/vue-dev-server.js"
  }
}

根据 scripts test 命令。我们来看 test 文件夹。

 test 文件夹:

vue-dev-server/test 文件夹下有三个文件,代码不长。

  • index.html
  • main.js
  • text.vue

如图下图所示。

接着我们找到 vue-dev-server/bin/vue-dev-server.js 文件,代码也不长。

主要是启动了端口 3000 的服务,重点在 vueMiddleware 中间件。接着我们来调试这个中间件。

vue-dev-server/bin/vue-dev-server.js 文件中这行 app.use(vueMiddleware()) 打上断点。

找到 vue-dev-server/package.json 的 scripts,把鼠标移动到 test 命令上,会出现运行脚本和调试脚本命令。如下图所示,选择调试脚本。

点击 进入函数(F11)按钮可以进入 vueMiddleware 函数。如果发现断点走到不是本项目的文件中,不想看,看不懂的情况,可以退出或者重新来过。可以用浏览器无痕(隐私)模式(快捷键Ctrl + Shift + N,防止插件干扰)打开 http://localhost:3000,可以继续调试 vueMiddleware 函数返回的函数。

有无 vueMiddleware 中间件对比:

先看结果,在具体分析:

不在调试情况状态下,我们可以在 vue-dev-server/bin/vue-dev-server.js 文件中注释 app.use(vueMiddleware()),执行 npm run test 打开  http://localhost:3000 .

再启用中间件后,如下图。

看图我们大概知道了有哪些区别。浏览器支持原生 type=module 模块请求加载。vue-dev-server 对其拦截处理,返回浏览器支持内容,因为无需打包构建,所以速度很快。

具体分析

接下来,我们来具体分析:

我们可以找到vue-dev-server/middleware.js,查看这个中间件函数的概览。

// vue-dev-server/middleware.js

const vueMiddleware = (options = defaultOptions) => {
  // 省略
  return async (req, res, next) => {
    // 省略
    // 对 .vue 结尾的文件进行处理
    if (req.path.endsWith('.vue')) {
    // 对 .js 结尾的文件进行处理
    } else if (req.path.endsWith('.js')) {
    // 对 /__modules/ 开头的文件进行处理
    } else if (req.path.startsWith('/__modules/')) {
    } else {
      next()
    }
  }
}
exports.vueMiddleware = vueMiddleware

vueMiddleware 最终返回一个函数。这个函数里主要做了四件事:

  • 对 .vue  结尾的文件进行处理
  • 对 .js 结尾的文件进行处理
  • 对 /__modules/  开头的文件进行处理
  • 如果不是以上三种情况,执行 next 方法,把控制权交给下一个中间件

对 .vue 结尾的文件进行处理

if (req.path.endsWith('.vue')) {
const key = parseUrl(req).pathname
let out = await tryCache(key)
if (!out) {
  // Bundle Single-File Component
  const result = await bundleSFC(req) // 具体见下文,bundleSFC 编译单文件组件
  out = result
  cacheData(key, out, result.updateTime)
}
send(res, out.code, 'application/javascript')
}

bundleSFC 编译单文件组件

这个函数,根据 @vue/component-compiler 转换单文件组件,最终返回浏览器能够识别的文件。

const vueCompiler = require('@vue/component-compiler')
async function bundleSFC (req) {
const { filepath, source, updateTime } = await readSource(req) // 看下文,readSource 读取文件资源
const descriptorResult = compiler.compileToDescriptor(filepath, source)
const assembledResult = vueCompiler.assemble(compiler, filepath, {
  ...descriptorResult,
  script: injectSourceMapToScript(descriptorResult.script),
  styles: injectSourceMapsToStyles(descriptorResult.styles)
})
return { ...assembledResult, updateTime }
}

接着来看 readSource 函数实现。

readSource 读取文件资源

这个函数主要作用:根据请求获取文件资源。返回文件路径 filepath、资源 source、和更新时间 updateTime。

const path = require('path')
const fs = require('fs')
const readFile = require('util').promisify(fs.readFile)
const stat = require('util').promisify(fs.stat)
const parseUrl = require('parseurl')
const root = process.cwd()
async function readSource(req) {
const { pathname } = parseUrl(req)
const filepath = path.resolve(root, pathname.replace(/^\//, ''))
// 根据请求获取文件资源。返回文件路径 filepath、资源 source、和更新时间 updateTime。
return {
  filepath,
  source: await readFile(filepath, 'utf-8'),
  updateTime: (await stat(filepath)).mtime.getTime()
}
}
exports.readSource = readSource

接着我们来看对 .js 文件的处理

对 .js 结尾的文件进行处理

if (req.path.endsWith('.js')) {
const key = parseUrl(req).pathname
let out = await tryCache(key)
if (!out) {
  // transform import statements
  // 转换 import 语句 
  // import Vue from 'vue'
  // => import Vue from "/__modules/vue"
  const result = await readSource(req)
  out = transformModuleImports(result.source) // 转换 import 引入
  cacheData(key, out, result.updateTime)
}
send(res, out, 'application/javascript')
}

 针对 vue-dev-server/test/main.js 转换

import Vue from 'vue'
import App from './test.vue'
new Vue({
  render: h => h(App)
}).$mount('#app')

 转换:

import Vue from "/__modules/vue"
import App from './test.vue'
new Vue({
  render: h => h(App)
}).$mount('#app')

main.js 中的 import 语句import Vue from 'vue'通过 recast 生成 ast 转换成 import Vue from "/__modules/vue" 而最终返回给浏览器的是 vue-dev-server/node_modules/vue/dist/vue.esm.browser.js

main.js 中的引入 .vue 的文件,import App from './test.vue' 则用 @vue/component-compiler 转换成浏览器支持的文件,具体见下文。

对 /__modules/ 开头的文件进行处理

import Vue from "/__modules/vue"

 这段代码最终返回的是读取路径 vue-dev-server/node_modules/vue/dist/vue.esm.browser.js 下的文件。

if (req.path.startsWith('/__modules/')) {
// 
const key = parseUrl(req).pathname
const pkg = req.path.replace(/^\/__modules\//, '')
let out = await tryCache(key, false) // Do not outdate modules
if (!out) {
  out = (await loadPkg(pkg)).toString() // loadPkg 加载包(这里只支持Vue文件)
  cacheData(key, out, false) // Do not outdate modules
}
send(res, out, 'application/javascript')
}

loadPkg 加载包(这里只支持Vue文件)

目前只支持 Vue 文件,也就是读取路径 vue-dev-server/node_modules/vue/dist/vue.esm.browser.js 下的文件返回。

// vue-dev-server/loadPkg.js
const fs = require('fs')
const path = require('path')
const readFile = require('util').promisify(fs.readFile)
async function loadPkg(pkg) {
if (pkg === 'vue') {
  // 路径
  // vue-dev-server/node_modules/vue/dist
  const dir = path.dirname(require.resolve('vue'))
  const filepath = path.join(dir, 'vue.esm.browser.js')
  return readFile(filepath)
}
else {
  // TODO
  // check if the package has a browser es module that can be used
  // otherwise bundle it with rollup on the fly?
  throw new Error('npm imports support are not ready yet.')
}
}
exports.loadPkg = loadPkg

 至此,我们就基本分析完毕了主文件和一些引入的文件。对主流程有个了解。

Vite 提供了哪些常用的构建插件和中间件?如何使用它们?

上文讲到了对于不同的资源会有不同的插件去处理。Vite 提供了一些常用的构建插件和中间件,以帮助你在项目中进行开发和构建。

vite的内置插件:

vite在返回源码时,已经对源码做了一层处理,编译为浏览器可以运行的代码。实际上,对于不同的文件后缀,比如.ts、.vue、.tsx、.jsx,vite都会对其做不同的处理:

.vue文件

vite 默认支持 Vue 项目,并提供了一些 Vue 相关的插件,如 @vitejs/plugin-vue,用于解析和编译 Vue 单文件组件。

  • Vue 3 单文件组件支持:@vitejs/plugin-vue
  • Vue 3 JSX 支持:@vitejs/plugin-vue-jsx
  • Vue 2 支持:underfin/vite-plugin-vue2

npm install @vitejs/plugin-vue

 在 vite.config.js 中使用 Vue 插件:

import { createVuePlugin } from 'vite-plugin-vue'

export default {
  plugins: [
    createVuePlugin(/* options */)
  ]
}

.ts文件

(packages/vite/src/node/plugins/esbuild.ts)

Vite 内置支持 TypeScript,无需额外的插件,其内部使用esbuild将TypeScript 转译到 JavaScript。可以直接在项目中使用 TypeScript。

.jsx文件、.tsx文件

(packages/vite/src/node/plugins/esbuild.ts)

JSX 的转译同样是通过 esbuild,默认为 React 16 风格。

.css文件

(packages/vite/src/node/plugins/css.ts)

Vite 支持处理 CSS 相关的插件,比如 vite-plugin-css,用于处理 CSS 文件和样式。导入 .css 文件将会把内容插入到标签中。

npm install vite-plugin-css

 在 vite.config.js 中使用 CSS 插件:

import { createCssPlugin } from 'vite-plugin-css'

export default {
  plugins: [
    createCssPlugin(/* options */)
  ]
}

裸模块重写

(packages/vite/src/node/plugins/resolve.ts)(packages/vite/src/node/plugins/importAnalysis.ts)

从下面这张图中,我们可以看到, vuex.js、vue-router.js 这种依赖模块文件名后面是有后缀的,而 main.js 等这种源代码文件是没有的。为什么会有这样的不同呢?这是因为 vuex.js、vue-router.js 是裸模块,而浏览器在加载文件时,只能加载相对地址,类似于 import Vuex from 'vuex'; 这种裸模块的加载,浏览器是不支持的,所以 vite 会对其做一层裸模块重写的处理,例如将引入 vuex 的url改写为 /node_modules/.vite/deps/vuex.js?v=bc6c6eed

由于 import vue 这种模块引入方式,使用的是 Nodejs 特有的模块查找算法(到 node_modules 中取查找),浏览器无法使用,因此 Vite 会将 vue 替换成一个另一个路径,当浏览器解析到这行 import 语句时,会发送一个 /node_modules/.vite/deps/vuex.js?v=bc6c6eed, Vite Server 会到该目录下,拿到 vue 预构建之后的产物代码。

你会发现,引入的 vuex 是在.vite文件夹里头,而不是在 node_modules 的 vuex 文件夹中,这是因为 vite 做了一个优化 —— 依赖预构建

依赖预构建做了什么:

  • 扫描入口文件
  • 扫描所有用到的依赖
  • 将多个依赖进行打包
  • 修改这些模块的引入路径

简单来说,vite会对 package.json 中的 dependencies 部分先进行构建,然后把构建后的文件换存在 node_modules/.vite 文件夹中,当启动项目时,直接请求该缓存内容。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python递归求数字各个位数相加_和

python递归求数字的各项和&#xff0c;例如数字一千零二十四&#xff1a;“1024”&#xff0c;输出结果为“10247” 第一种方法&#xff1a; def sum(a): #求一个数字各项和&#xff0c;第一种递归方法if 0<a<9: #从前到最后一个&#xff0c;出循环…

swiper垂直方向全屏实现鼠标滚轮滚动一下切换一屏

效果 20231116092014 添加mousewheelControl: true,这个属性即可 <div class"swiper-container"><div class"swiper-wrapper"><div class"swiper-slide" > <div class"" style"height: 100%; background-…

一个前端非侵入式骨架屏自动生成方案

目录 背景 现有方案调研 侵入业务式手写代码 非侵入业务式手写代码 非侵入式骨架屏代码自动生成 技术方案 设计原则 架构图 骨架屏生成 骨架屏注入 优化点 部分技术细节解析 puppeteer 文本块处理 图片块处理 a 标签处理 自定义属性处理 首屏HTML处理 首屏样…

合肥数字孪生赋能工业制造,加速推进制造业数字化转型

聚焦国家战略需求和先进制造业发展方向&#xff0c;加快数字化发展战略部署&#xff0c;数字孪生、工业互联网、工业物联网已被广泛认为是工业革命的新引擎。合肥数字孪生正在推动工业制造从制造转向智造。通过数字化建模和仿真的方式&#xff0c;优化设计、生产、质量管理、供…

使用 Gradle 命令了解项目构建信息

引言 首先&#xff0c;Gradle 作为使用 Android Studio 开发 Android 项目的默认构建工具&#xff0c;它里面的任何东西都基于两个概念&#xff1a; projects ( 项目 )tasks ( 任务 ) 每一个构建由一个或多个 projects 构成&#xff0c;每一个 project 由一个或多个 tasks 构…

SOLIDWORKS功能布局实用技巧之保存实体技术

在SOLIDWORKS软件中&#xff0c;有一些命令可以将一个或多个实体保存为独立的零件文件。然而&#xff0c;每个命令都具有不同的特性&#xff0c;有些命令的选项可以让您在保存多个零件时直接生成装配体文件。让我们来深入了解这些功能布局技巧&#xff0c;特别是实体保存技术。…

XXX系统测试报告测试用例模板

XXX系统测试报告 编制&#xff1a; 2023-5-16 审核&#xff1a; 日期&#xff1a; 批准&#xff1a; 日期&#xff1a; 版本 修订时间 修订人 修订类型 修订章节 修订内容 *修订类型分为 A …

如何解决3d max渲染效果图全白这类异常问题?

通过3d max渲染效果图时&#xff0c;经常会出现3Dmax渲染效果图全黑或是3Dmax渲染效果图全白这类异常问题。可能遇到这类问题较多的都是新手朋友。不知如何解决。 3dmax渲染出现异常的问题&#xff0c;该如何高效解决呢&#xff1f;今天小编这里整理几项知识点&#xff0c;大家…

力扣刷题篇之数与位1

系列文章目录 目录 系列文章目录 前言 一、进制转换 总结 前言 本系列是个人力扣刷题汇总&#xff0c;本文是数与位。刷题顺序按照[力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 - 力扣&#xff08;LeetCode&#xff09; 一、进制转换 67. 二进制求和 - 力扣&…

二十、泛型(8)

本章概要 潜在的类型机制 pyhton 中的潜在类型C 中的潜在类型Go 中的潜在类型java 中的直接潜在类型 潜在类型机制 在本章的开头介绍过这样的思想&#xff0c;即要编写能够尽可能广泛地应用的代码。为了实现这一点&#xff0c;我们需要各种途径来放松对我们的代码将要作用的…

Leetcode周赛371补题(3 / 3)

目录 1、找出强数对的最大异或值 - 暴力 2、高访问员工 - 哈希表 模拟 3、最大化数组末位元素的最少操作次数 - 思维 贪心 1、找出强数对的最大异或值 - 暴力 找出强数对的最大异或值 I class Solution {public int maximumStrongPairXor(int[] a) {int na.length,max0;…

VS Code如何使用服务器的Python开发环境

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

写作脑科学——屠龙的高效写作指南

ISBN: 978-7-115-59231-6 作者&#xff1a;杨滢&#xff08;屠龙的胭脂井&#xff09; 页数&#xff1a;201页 阅读时间&#xff1a;2023-09-09 推荐指数&#xff1a;★★★★★ 十分推荐这本书&#xff0c;写的非常简单易懂&#xff0c;里面有很多方法论和实用技巧&#xff0c…

X86 bios 中断大全

1、显示服务(Video Service——INT 10H) 00H —设置显示器模式 0CH —写图形象素 01H —设置光标形状 0DH —读图形象素 02H —设置光标位置 0EH —在Teletype模式下显示字符 03H —读取光标信息 0FH —读取显示器模式 04H —读取光笔位置 10H —颜色…

【LeetCode刷题-滑动窗口】--1658.将x减到0的最小操作数

1658.将x减到0的最小操作数 思路与算法&#xff1a; 根据题目描述&#xff0c;在每一次操作中&#xff0c;可以移除数组nums最左边和最右边的元素&#xff0c;因此&#xff0c;在所有的操作完成后&#xff0c;数组nums的一个前缀以及一个后缀被移除&#xff0c;并且它们的和恰…

CopyOnWriteArrayList 源码详解

目录 一. 前言 二. 源码详解 2.1. 类结构 2.2. 属性 2.3. 构造方法 2.4. add(E e) 2.5. add(int index, E element) 2.6. addIfAbsent() 方法 2.7. 获取元素() 方法 2.8. remove(int index) 2.9. size() 三. FAQ 3.1. 为什么CopyOnWriteArrayList没有size属性&…

架构师的成名之路

相信大家都对未来的职业发展有着憧憬和规划&#xff0c;要做架构师、要做技术总监、要做CTO。对于如何实现自己的职业规划也都信心满满&#xff0c;努力工作、好好学习、不断提升自己。 相信成为一名优秀的架构师是很多程序员的目标&#xff0c;架构师的工作包罗万象&#xff…

3.3 Windows驱动开发:内核MDL读写进程内存

MDL内存读写是一种通过创建MDL结构体来实现跨进程内存读写的方式。在Windows操作系统中&#xff0c;每个进程都有自己独立的虚拟地址空间&#xff0c;不同进程之间的内存空间是隔离的。因此&#xff0c;要在一个进程中读取或写入另一个进程的内存数据&#xff0c;需要先将目标进…

基于SSM的校园服务平台管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

C++初阶,详解类和对象(2)

详解类和对象&#xff08;2&#xff09; 一&#xff0c;前言二&#xff0c;构造函数2.1构造函数概念2.2构造函数特性 三&#xff0c;析构函数3.1析构函数概念3.2析构函数特性 一&#xff0c;前言 上一篇我们讲了类的大体框架&#xff0c;这篇内容我们要重点来说一说类的几个默…