一文带你了解vite对浏览器的请求做了什么

目录
  • 工作原理:
  • 浏览器做的什么事啊
    • 宿主文件index.html
    • main.js
    • 其他裸模块
  • 了解一下预打包
  • 服务器做的什么事啊
    • 请求首页index.html
    • 请求以.js结尾的文件
      • 基础js文件
      • 对main中的依赖进行处理
      • 处理.vue文件
    • 处理图片路径
  • 总结

工作原理:

  • type="module" 浏览器中ES Module原生native支持。 如果浏览器支持type="module" ,我i们可以使用es6模块化的方式编写。浏览器会把我们需要导入的文件再发一次http请求,再发到服务器上。 开发阶段不需要打包
  • 第三方依赖预打包
  • 启动一个开发服务器处理资源请求

一图详解vite原理:

浏览器做的什么事啊

宿主文件index.html

<script type="module" src="/src/main.js"></script>

浏览器获取到宿主文件中的资源后,发现还要再去请求main.js文件。会再向服务端发送一次main.js的资源请求。

main.js

在main中,可以发现,浏览器又再次发起对vue.js?v=d253a66c、App.vue?t=1637479953836两个文件的资源请求。

服务器会将App.vue中的内容进行编译然后返回给浏览器,下图可以看出logo图片和文字都被编译成_hoisted_ 的静态节点。

从请求头中,也可以看出sfc文件已经变成浏览器可以识别的js文件(app.vue文件中要存在script内容才会编译成js)。对于浏览器来说,执行的就是一段js代码。

其他裸模块

如果vue依赖中还存在其他依赖的话,浏览器依旧会再次发起资源请求,获取相应资源。

了解一下预打包

对于第三方依赖(裸模块)的加载,vite对其提前做好打包工作,将其放到node_modules/.vite下。当启动项目的时候,直接从该路径下下载文件。

通过上图,可以看到再裸模块的引入时,路径发生了改变。

服务器做的什么事啊

总结一句话:服务器把特殊后缀名的文件进行处理返回给前端展示。

我们可以模拟vite的devServe,使用koa中间件启动一个本地服务。

// 引入依赖
const Koa = require('koa')
const app = new Koa()
const fs = require('fs')
const path = require('path')
const compilerSfc = require('@vue/compiler-sfc')
const compilerDom = require('@vue/compiler-dom')

app.use(async (ctx) => {
 const { url, query } = ctx.request
 // 处理请求资源代码都写这
})
app.listen(3001, () => {
  console.log('dyVite start!!')
})

请求首页index.html

 if (url === '/') {
    const p = path.join(__dirname, './index.html') // 绝对路径
    //  首页
    ctx.type = 'text/html'
    ctx.body = fs.readFileSync(p, 'utf8')
  }

看到上面这张图,就知道我们的宿主文件已经请求成功了。只是浏览器又给服务端发送的一个main.js文件的请求。这时,我们还需要判断处理一下main.js文件。

请求以.js结尾的文件

我们处理上述情况后,emmmm。。。发现main中还是存在好多其他资源请求。

基础js文件

main文件:

console.log(1)

处理main:

else if (url.endsWith('.js')) {
    // 响应js请求
    const p = path.join(__dirname, url)
    ctx.type = 'text/javascript'
    ctx.body = rewriteImport(fs.readFileSync(p, 'utf8')) // 处理依赖函数
  }

对main中的依赖进行处理

你以为main里面就一个输出吗?太天真了。这样的还能处理吗?

main文件:

import { createApp, h } from 'vue'
createApp({ render: () => h('div', 'helllo dyVite!') }).mount('#app')

emmm。。。应该可以!

我们可以将main中导入的地址变成相对地址。

在裸模块路径添加上/@modules/。再去识别/@modules/的文件即(裸模块文件)。

// 把能读出来的文件地址变成相对地址
// 正则替换 重写导入 变成相对地址
// import { createApp } from 'vue'  => import { createApp } from '/@modules/vue'
function rewriteImport(content) {
  return content.replace(/ from ['|"](.*)['|"]/g, function (s0, s1) {
    //  s0匹配字符串,s1分组内容
    // 是否是相对路径
    if (s1.startsWith('./') || s1.startsWith('/') || s1.startsWith('../')) {
      // 直接返回
      return s0
    } else {
      return ` from '/@modules/${s1}'`
    }
  })
}

对于第三方依赖,vite内部是使用预打包请求自己服务器/node_modules/.vite/下的内部资源。

我们可以简单化一点,将拿到的依赖名去客户端下的node_modules下拿相应的资源。

  else if (url.startsWith('/@modules/')) {
    // 裸模块的加载
    const moduleName = url.replace('/@modules/', '')
    const pre![1637477009328](imgs/1637477009328.png)![1637477009368](imgs/1637477009368.png)的地址
    const module = require(prefix + '/package.json').module
    const filePath = path.join(prefix, module) // 拿到文件加载的地址
    // 读取相关依赖
    const ret = fs.readFileSync(filePath, 'utf8')
    ctx.type = 'text/javascript'
    ctx.body = rewriteImport(ret) //依赖内部可能还存在依赖,需要递归
  }

在main中进行render时,会报下图错误:

我们加载的文件都是服务端执行的库,内部可能会产生node环境的代码,需要判断一下环境变量。如果开发时,会输出一些警告信息,但是在前端是没有的。所以我们需要mock一下,告诉浏览器我们当前的环境。

给html加上process环境变量。

  <script>
    window.process = { env: { NODE_ENV: 'dev' } }
  </script>

此时main文件算是加载出来了。

但是这远远打不到我们的目的啊!

我们需要的是可以编译vue文件的服务器啊!

处理.vue文件

main.js文件:

import { createApp, h } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

在vue文件中,它是模块化加载的。

我们需要在处理vue文件的时候,对.vue后面携带的参数做处理。

在此,我们简化只考虑template和sfc情况。

else if (url.indexOf('.vue') > -1) {
    // 处理vue文件  App.vue?vue&type=style&index=0&lang.css
    // 读取vue内容
    const p = path.join(__dirname, url.split('?')[0])
    // compilerSfc解析sfc  获得ast
    const ret = compilerSfc.parse(fs.readFileSync(p, 'utf8'))
    // App.vue?type=template
    // 如果请求没有query.type 说明是sfc
    if (!query.type) {
      // 处理内部的script
      const scriptContent = ret.descriptor.script.content
      // 将默认导出配置对象转为常量
      const script = scriptContent.replace(
        'export default ',
        'const __script = ',
      )
      ctx.type = 'text/javascript'
      ctx.body = `
  ${rewriteImport(script)}
  // template解析转换为单独请求一个资源
  import {render as __render} from '${url}?type=template'
  __script.render = __render
  export default __script
`
    } else if (query.type === 'template') {
      const tpl = ret.descriptor.template.content
      // 编译包含render模块
      const render = compilerDom.compile(tpl, { mode: 'module' }).code
      ctx.type = 'text/javascript'
      ctx.body = rewriteImport(render)
    }
  }

处理图片路径

直接从客户端读取返回。

 else if (url.endsWith('.png')) {
    ctx.body = fs.readFileSync('src' + url)
  }

总结

到此这篇关于vite对浏览器的请求做了什么的文章就介绍到这了,更多相关vite浏览器的请求内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 一文带你了解vite对浏览器的请求做了什么

    目录 工作原理: 浏览器做的什么事啊 宿主文件index.html main.js 其他裸模块 了解一下预打包 服务器做的什么事啊 请求首页index.html 请求以.js结尾的文件 基础js文件 对main中的依赖进行处理 处理.vue文件 处理图片路径 总结 工作原理: type="module" 浏览器中ES Module原生native支持. 如果浏览器支持type="module" ,我i们可以使用es6模块化的方式编写.浏览器会把我们需要导入的文件再发

  • 一文带你了解什么是浏览器缓存,DNS,CDN及域名解析类型

    浏览器的缓存机制 当我们使用Ctrl+F5组合键刷新一个页面时,在HTTP的请求头中会增加一些请求头,它告诉服务端我们要获取最新的数据而不是缓存. Cache-Control 这个HTTP Head字段用于指定所有缓存机制在整个请求/响应链中必须服从的指令. 可选值 说明 Public 所有内容都将被缓存,在响应头中设置 Private 内容只缓存到私有缓存中,在响应头中设置 no-cache 所有内容都不会被缓存,在请求头和响应头中设置 no-store 所有内容都不会被缓存到缓存或Inter

  • 做java这么久了居然还不知道JSON的使用(一文带你了解)

    JSON(JavaScript Object Notation, NS对象标记)是一种轻量级的数据交换格式,目前使用特别广泛. 采用完全独立于编程语言的 文本格式 来存储和表示数据. 简洁和清晰的层次结构使得JSON成为理想的数据交换语言. 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率. 在JavaScript语言中,一切都是对象.因此,任何JavaScript 支持的类型都可以通过JSON来表示,例如字符串.数字.对象.数组等.看看他的要求和语法格式: 对象表示为键值对

  • 一文带你搞懂Vue3的基本语法

    目录 1.通过 CDN 使用 Vue3 2.Vue3 模板语法 文本 Html 属性 表达式 指令 参数 3.模板用户输入双向绑定 1.通过 CDN 使用 Vue3 你可以借助 script 标签直接通过 CDN 来使用 Vue: <script src="https://unpkg.com/vue@next"></script> 通过 CDN 使用 Vue 时,不涉及“构建步骤”.这使得设置更加简单,并且可以用于增强静态的 HTML 或与后端框架集成 接下来我

  • 一文带你搞懂JavaScript中的进制与进制转换

    目录 进制介绍 进制转换 parseInt(str, radix) Number() +(一元运算符) Number.prototype.toString(radix) 自定义转换 十进制与十六进制转换 十进制和二进制转换 进制介绍 JavaScript 中提供的进制表示方法有四种:十进制.二进制.十六进制.八进制. 对于数值字面量,主要使用不同的前缀来区分: 十进制(Decimal):取值数字 0-9:不用前缀. 二进制(Binary):取值数字 0 和 1 :前缀 0b 或 0B. 十六进制

  • 一文带你了解 C# DLR 的世界(DLR 探秘)

    在很久之前,我写了一片文章详解C# 匿名对象(匿名类型).var.动态类型 dynamic,可以借鉴.因为那时候是心中想当然的认为只有反射能够在运行时解析对象的成员信息并调用成员方法.后来也是因为其他的事一直都没有回过头来把这一节知识给补上,正所谓亡羊补牢,让我们现在来大致了解一下DLR吧. DLR 全称是 Dynamic Language Runtime(动态语言运行时).这很容易让我们想到同在C#中还有一个叫 CLR 的东西,它叫 Common Language Runtime.那这两者有什

  • 一文带你入门JDK8新特性——Lambda表达式

    Lambda简介 Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构. JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便.高效. 对接口的要求 虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现.Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法 jd

  • 一文带你彻底理解Java序列化和反序列化

    Java序列化是什么? Java序列化是指把Java对象转换为字节序列的过程,Java反序列化是指把字节序列恢复为Java对象的过程. 反序列化: 客户端重文件,或者网络中获取到文件以后,在内存中重构对象. 序列化: 对象序列化的最重要的作用是传递和保存对象的时候,保证对象的完整性和可传递性.方便字节可以在网络上传输以及保存在本地文件. 为什么需要序列化和反序列化 实现分布式 核心在于RMI,可以利用对象序列化运行远程主机上的服务,实现运行的时候,就像在本地上运行Java对象一样. 实现递归保存

  • 一文带你彻底搞懂Lambda表达式

    1. 为什么使用Lambda表达式 Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递).可以写出更简洁.更灵活的代码.作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升. 我们来看一下使用lambda之前创建匿名内部类: new Thread(new Runnable() { @Override public void run() { System.out.println("执行Runnable方法"); } });

  • 一文带你了解Python 四种常见基础爬虫方法介绍

    一.Urllib方法 Urllib是python内置的HTTP请求库 import urllib.request #1.定位抓取的url url='http://www.baidu.com/' #2.向目标url发送请求 response=urllib.request.urlopen(url) #3.读取数据 data=response.read() # print(data) #打印出来的数据有ASCII码 print(data.decode('utf-8')) #decode将相应编码格式的

随机推荐