一文帶你瞭解vite對瀏覽器的請求做瞭什麼

工作原理:

  • 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瀏覽器的請求內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: