vite介紹


什么是 Vite

借用作者的原話:

Vite,一個基於瀏覽器原生 ES imports 的開發服務器。利用瀏覽器去解析 imports,在服務器端按需編譯返回,完全跳過了打包這個概念,服務器隨起隨用。同時不僅有 Vue 文件支持,還搞定了熱更新,而且熱更新的速度不會隨着模塊增多而變慢。針對生產環境則可以把同一份代碼用 rollup 打包。雖然現在還比較粗糙,但這個方向我覺得是有潛力的,做得好可以徹底解決改一行代碼等半天熱更新的問題。

注意到兩個點:

  • 一個是 Vite 主要對應的場景是開發模式,原理是攔截瀏覽器發出的 ES imports 請求並做相應處理。(生產模式是用 rollup 打包)
  • 一個是 Vite 在開發模式下不需要打包,只需要編譯瀏覽器發出的 HTTP 請求對應的文件即可,所以熱更新速度很快。

因此,要實現上述目標,需要要求項目里只使用原生 ES imports,如果使用了 require 將失效,所以要用它完全替代掉 Webpack 就目前來說還是不太現實的。上面也說了,生產模式下的打包不是 Vite 自身提供的,因此生產模式下如果你想要用 Webpack 打包也依然是可以的。從這個角度來說,Vite 可能更像是替代了 webpack-dev-server 的一個東西。

modules 模塊

Vite 的實現離不開現代瀏覽器原生支持的模塊功能。如下:

<script type="module">
import { a } from './a.js'
</script>

當聲明一個 script 標簽類型為 module 時,瀏覽器將對其內部的 import 引用發起 HTTP 請求獲取模塊內容。比如上述,瀏覽器將發起一個對 HOST/a.js 的 HTTP 請求,獲取到內容之后再執行。

Vite 劫持了這些請求,並在后端進行相應的處理(比如將 Vue 文件拆分成 templatestylescript 三個部分),然后再返回給瀏覽器。

由於瀏覽器只會對用到的模塊發起 HTTP 請求,所以 Vite 沒必要對項目里所有的文件先打包后返回,而是只編譯瀏覽器發起 HTTP 請求的模塊即可。這里是不是有點按需加載的味道?

編譯和打包的區別

看到這里,可能有些朋友不免有些疑問,編譯和打包有什么區別?為什么 Vite 號稱「熱更新的速度不會隨着模塊增多而變慢」?

簡單舉個例子,有三個文件 a.jsb.jsc.js

// a.js
const a = () => { ... }
export { a }

// b.js
const b = () => { ... }
export { b }
// c.js
import { a } from './a'
import { b } from './b'

const c = () => {
  return a() + b()
}

export { c }

如果以 c 文件為入口,那么打包就會變成如下(結果進行了簡化處理):(假定打包文件名為 bundle.js)

// bundle.js
const a = () => { ... }
const b = () => { ... }
const c = () => {
  return a() + b()
}

export { c }

值得注意的是,打包也需要有編譯的步驟。

Webpack 的熱更新原理簡單來說就是,一旦發生某個依賴(比如上面的 a.js )改變,就將這個依賴所處的 module 的更新,並將新的 module 發送給瀏覽器重新執行。由於我們只打了一個 bundle.js,所以熱更新的話也會重新打這個 bundle.js。試想如果依賴越來越多,就算只修改一個文件,理論上熱更新的速度也會越來越慢。

而如果是像 Vite 這種只編譯不打包會是什么情況呢?

只是編譯的話,最終產出的依然是 a.jsb.jsc.js 三個文件,只有編譯耗時。由於入口是 c.js,瀏覽器解析到 import { a } from './a' 時,會發起 HTTP 請求 a.js (b 同理),就算不用打包,也可以加載到所需要的代碼,因此省去了合並代碼的時間。

在熱更新的時候,如果 a 發生了改變,只需要更新 a 以及用到 ac。由於 b 沒有發生改變,所以 Vite 無需重新編譯 b,可以從緩存中直接拿編譯的結果。這樣一來,修改一個文件 a,只會重新編譯這個文件 a 以及瀏覽器當前用到這個文件 a 的文件,而其余文件都無需重新編譯。所以理論上熱更新的速度不會隨着文件增加而變慢。

當然這樣做有沒有不好的地方?有,初始化的時候如果瀏覽器請求的模塊過多,也會帶來初始化的性能問題。不過如果你能遇到初始化過慢的這個問題,相信熱更新的速度會彌補很多。當然我相信以后尤大也會解決這個問題。

Vite 運行 Web 應用的實現

上面說了這么多的鋪墊,可能還不夠直觀,我們可以先跑一個 Vite 項目來實際看看。

按照官網的說明,可以輸入如下命令(<project-name> 為自己想要的目錄名即可)

$ npx create-vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev

如果一切都正常你將在 localhost:3000(Vite 的服務器起的端口) 看到這個界面:

 

並得到如下的代碼結構:

.
├── App.vue // 頁面的主要邏輯
├── index.html // 默認打開的頁面以及 Vue 組件掛載
├── node_modules
└── package.json

攔截 HTTP 請求

接下來開始說一下 Vite 實現的核心——攔截瀏覽器對模塊的請求並返回處理后的結果。

我們知道,由於是在 localhost:3000 打開的網頁,所以瀏覽器發起的第一個請求自然是請求 localhost:3000/,這個請求發送到 Vite 后端之后經過靜態資源服務器的處理,會進而請求到 /index.html,此時 Vite 就開始對這個請求做攔截和處理了。

首先,index.html 里的源碼是這樣的:

<div id="app"></div>
<script type="module">
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
</script>

但是在瀏覽器里它是這樣的:

 

注意到什么不同了嗎?是的, import { createApp } from 'vue' 換成了 import { createApp } from '/@modules/vue

這里就不得不說瀏覽器對 import 的模塊發起請求時的一些局限了,平時我們寫代碼,如果不是引用相對路徑的模塊,而是引用 node_modules 的模塊,都是直接 import xxx from 'xxx',由 Webpack 等工具來幫我們找這個模塊的具體路徑。但是瀏覽器不知道你項目里有 node_modules,它只能通過相對路徑去尋找模塊。

因此 Vite 在攔截的請求里,對直接引用 node_modules 的模塊都做了路徑的替換,換成了 /@modules/ 並返回回去。而后瀏覽器收到后,會發起對 /@modules/xxx 的請求,然后被 Vite 再次攔截,並由 Vite 內部去訪問真正的模塊,並將得到的內容再次做同樣的處理后,返回給瀏覽器。

imports 替換

普通 JS import 替換

上面說的這步替換來自 src/node/serverPluginModuleRewrite.ts:

// 只取關鍵代碼:
// Vite 使用 Koa 作為內置的服務器
// 如果請求的路徑是 /index.html
if (ctx.path === '/index.html') {
  // ...
  const html = await readBody(ctx.body)
  ctx.body = html.replace(
    /(<script\b[^>]*>)([\s\S]*?)<\/script>/gm, // 正則匹配
    (_, openTag, script) => {
      // also inject __DEV__ flag
      const devFlag = hasInjectedDevFlag ? `` : devInjectionCode
      hasInjectedDevFlag = true
       // 替換 html 的 import 路徑
      return `${devFlag}${openTag}${rewriteImports(
        script,
        '/index.html',
        resolver
      )}</script>`
    }
  )
  // ...
}

如果並沒有在 script 標簽內部直接寫 import,而是用 src 的形式引用的話如下:

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

那么就會在瀏覽器發起對 main.js 請求的時候進行處理:

// 只取關鍵代碼:
if (
  ctx.response.is('js') &&
  // ...
) {
  // ...
  const content = await readBody(ctx.body)
  await initLexer
  // 重寫 js 文件里的 import
  ctx.body = rewriteImports(
    content,
    ctx.url.replace(/(&|\?)t=\d+/, ''),
    resolver,
    ctx.query.t
  )
  // 寫入緩存,之后可以從緩存中直接讀取
  rewriteCache.set(content, ctx.body)
}

替換邏輯 rewriteImports 就不展開了,用的是 es-module-lexer 來進行的語法分析獲取 imports 數組,然后再做的替換。

*.vue 文件的替換

如果 import 的是 .vue 文件,將會做更進一步的替換:

原本的 App.vue 文件長這樣:

<template>
  <h1>Hello Vite + Vue 3!</h1>
  <p>Edit ./App.vue to test hot module replacement (HMR).</p>
  <p>
    <span>Count is: {{ count }}</span>
    <button @click="count++">increment</button>
  </p>
</template>

<script>
export default {
  data: () => ({ count: 0 }),
}
</script>

<style scoped>
h1 {
  color: #4fc08d;
}

h1, p {
  font-family: Arial, Helvetica, sans-serif;
}
</style>

替換后長這樣:

// localhost:3000/App.vue
import { updateStyle } from "/@hmr"

// 抽出 script 邏輯
const __script = {
  data: () => ({ count: 0 }),
}

// 將 style 拆分成 /App.vue?type=style 請求,由瀏覽器繼續發起請求獲取樣式
updateStyle("c44b8200-0", "/App.vue?type=style&index=0&t=1588490870523")
__script.__scopeId = "data-v-c44b8200" // 樣式的 scopeId

// 將 template 拆分成 /App.vue?type=template 請求,由瀏覽器繼續發起請求獲取 render function
import { render as __render } from "/App.vue?type=template&t=1588490870523&t=1588490870523"
__script.render = __render // render 方法掛載,用於 createApp 時渲染
__script.__hmrId = "/App.vue" // 記錄 HMR 的 id,用於熱更新
__script.__file = "/XXX/web/vite-test/App.vue" // 記錄文件的原始的路徑,后續熱更新能用到
export default __script

這樣就把原本一個 .vue 的文件拆成了三個請求(分別對應 scriptstyletemplate) ,瀏覽器會先收到包含 script 邏輯的 App.vue 的響應,然后解析到 templatestyle 的路徑后,會再次發起 HTTP 請求來請求對應的資源,此時 Vite 對其攔截並再次處理后返回相應的內容。

如下:

 

不得不說這個思路是非常巧妙的。

這一步的拆分來自 src/node/serverPluginVue.ts,核心邏輯是根據 URL 的 query 參數來做不同的處理(簡化分析如下):

// 如果沒有 query 的 type,比如直接請求的 /App.vue
if (!query.type) {
  ctx.type = 'js'
  ctx.body = compileSFCMain(descriptor, filePath, publicPath) // 編譯 App.vue,編譯成上面說的帶有 script 內容,以及 template 和 style 鏈接的形式。
  return etagCacheCheck(ctx) // ETAG 緩存檢測相關邏輯
}

// 如果 query 的 type 是 template,比如 /App.vue?type=template&xxx
if (query.type === 'template') {
  ctx.type = 'js'
  ctx.body = compileSFCTemplate( // 編譯 template 生成 render function
    // ...
  )
  return etagCacheCheck(ctx)
}

// 如果 query 的 type 是 style,比如 /App.vue?type=style&xxx
if (query.type === 'style') {
  const index = Number(query.index)
  const styleBlock = descriptor.styles[index]
  const result = await compileSFCStyle( // 編譯 style
    // ...
  )
  if (query.module != null) { // 如果是 css module
    ctx.type = 'js'
    ctx.body = `export default ${JSON.stringify(result.modules)}`
  } else { // 正常 css
    ctx.type = 'css'
    ctx.body = result.code
  }
}

@modules/* 路徑解析

上面只涉及到了替換的邏輯,解析的邏輯來自 src/node/serverPluginModuleResolve.ts。這一步就相對簡單了,核心邏輯就是去 node_modules 里找有沒有對應的模塊,有的話就返回,沒有的話就報 404:(省略了很多邏輯,比如對 web_modules 的處理、緩存的處理等)

// ...
try {
  const file = resolve(root, id) // id 是模塊的名字,比如 axios
  return serve(id, file, 'node_modules') // 從 node_modules 中找到真正的模塊內容並返回
} catch (e) {
  console.error(
    chalk.red(`[vite] Error while resolving node_modules with id "${id}":`)
  )
  console.error(e)
  ctx.status = 404 // 如果沒找到就 404
}

Vite 熱更新的實現

上面已經說完了 Vite 是如何運行一個 Web 應用的,包括如何攔截請求、替換內容、返回處理后的結果。接下來說一下 Vite 熱更新的實現,同樣實現的非常巧妙。

我們知道,如果要實現熱更新,那么就需要瀏覽器和服務器建立某種通信機制,這樣瀏覽器才能收到通知進行熱更新。Vite 的是通過 WebSocket 來實現的熱更新通信。

客戶端

客戶端的代碼在 src/client/client.ts,主要是創建 WebSocket 客戶端,監聽來自服務端的 HMR 消息推送。

Vite 的 WS 客戶端目前監聽這幾種消息:

  • connected: WebSocket 連接成功
  • vue-reload: Vue 組件重新加載(當你修改了 script 里的內容時)
  • vue-rerender: Vue 組件重新渲染(當你修改了 template 里的內容時)
  • style-update: 樣式更新
  • style-remove: 樣式移除
  • js-update: js 文件更新
  • full-reload: fallback 機制,網頁重刷新

其中針對 Vue 組件本身的一些更新,都可以直接調用 HMRRuntime 提供的方法,非常方便。其余的更新邏輯,基本上都是利用了 timestamp 刷新緩存重新執行的方法來達到更新的目的。

核心邏輯如下,我感覺非常清晰明了:

import { HMRRuntime } from 'vue' // 來自 Vue3.0 的 HMRRuntime

console.log('[vite] connecting...')

declare var __VUE_HMR_RUNTIME__: HMRRuntime

const socket = new WebSocket(`ws://${location.host}`)

// Listen for messages
socket.addEventListener('message', ({ data }) => {
  const { type, path, id, index, timestamp, customData } = JSON.parse(data)
  switch (type) {
    case 'connected':
      console.log(`[vite] connected.`)
      break
    case 'vue-reload':
      import(`${path}?t=${timestamp}`).then((m) => {
        __VUE_HMR_RUNTIME__.reload(path, m.default)
        console.log(`[vite] ${path} reloaded.`) // 調用 HMRRUNTIME 的方法更新
      })
      break
    case 'vue-rerender':
      import(`${path}?type=template&t=${timestamp}`).then((m) => {
        __VUE_HMR_RUNTIME__.rerender(path, m.render)
        console.log(`[vite] ${path} template updated.`) // 調用 HMRRUNTIME 的方法更新
      })
      break
    case 'style-update':
      updateStyle(id, `${path}?type=style&index=${index}&t=${timestamp}`) // 重新加載 style 的 URL
      console.log(
        `[vite] ${path} style${index > 0 ? `#${index}` : ``} updated.`
      )
      break
    case 'style-remove':
      const link = document.getElementById(`vite-css-${id}`)
      if (link) {
        document.head.removeChild(link) // 刪除 style
      }
      break
    case 'js-update':
      const update = jsUpdateMap.get(path)
      if (update) {
        update(timestamp) // 用新的時間戳加載並執行 js,達到更新的目的
        console.log(`[vite]: js module reloaded: `, path)
      } else {
        console.error(
          `[vite] got js update notification but no client callback was registered. Something is wrong.`
        )
      }
      break
    case 'custom':
      const cbs = customUpdateMap.get(id)
      if (cbs) {
        cbs.forEach((cb) => cb(customData))
      }
      break
    case 'full-reload':
      location.reload()
  }
})

服務端

服務端的實現位於 src/node/serverPluginHmr.ts。核心是監聽項目文件的變更,然后根據不同文件類型(目前只有 vuejs)來做不同的處理:

watcher.on('change', async (file) => {
  const timestamp = Date.now() // 更新時間戳
  if (file.endsWith('.vue')) {
    handleVueReload(file, timestamp)
  } else if (file.endsWith('.js')) {
    handleJSReload(file, timestamp)
  }
})

對於 Vue 文件的熱更新而言,主要是重新編譯 Vue 文件,檢測 templatescriptstyle 的改動,如果有改動就通過 WS 服務端發起對應的熱更新請求。

簡單的源碼分析如下:

async function handleVueReload(
    file: string,
    timestamp: number = Date.now(),
    content?: string
) {
  const publicPath = resolver.fileToRequest(file) // 獲取文件的路徑
  const cacheEntry = vueCache.get(file) // 獲取緩存里的內容

  debugHmr(`busting Vue cache for ${file}`)
  vueCache.del(file) // 發生變動了因此之前的緩存可以刪除

  const descriptor = await parseSFC(root, file, content) // 編譯 Vue 文件

  const prevDescriptor = cacheEntry && cacheEntry.descriptor // 獲取前一次的緩存

  if (!prevDescriptor) {
    // 這個文件之前從未被訪問過(本次是第一次訪問),也就沒必要熱更新
    return
  }

  // 設置兩個標志位,用於判斷是需要 reload 還是 rerender
  let needReload = false
  let needRerender = false

  // 如果 script 部分不同則需要 reload
  if (!isEqual(descriptor.script, prevDescriptor.script)) {
    needReload = true
  }

  // 如果 template 部分不同則需要 rerender
  if (!isEqual(descriptor.template, prevDescriptor.template)) {
    needRerender = true
  }

  const styleId = hash_sum(publicPath)
  // 獲取之前的 style 以及下一次(或者說熱更新)的 style
  const prevStyles = prevDescriptor.styles || []
  const nextStyles = descriptor.styles || []

  // 如果不需要 reload,則查看是否需要更新 style
  if (!needReload) {
    nextStyles.forEach((_, i) => {
      if (!prevStyles[i] || !isEqual(prevStyles[i], nextStyles[i])) {
        send({
          type: 'style-update',
          path: publicPath,
          index: i,
          id: `${styleId}-${i}`,
          timestamp
        })
      }
    })
  }

  // 如果 style 標簽及內容刪掉了,則需要發送 `style-remove` 的通知
  prevStyles.slice(nextStyles.length).forEach((_, i) => {
    send({
      type: 'style-remove',
      path: publicPath,
      id: `${styleId}-${i + nextStyles.length}`,
      timestamp
    })
  })

  // 如果需要 reload 發送 `vue-reload` 通知
  if (needReload) {
    send({
      type: 'vue-reload',
      path: publicPath,
      timestamp
    })
  } else if (needRerender) {
    // 否則發送 `vue-rerender` 通知
    send({
      type: 'vue-rerender',
      path: publicPath,
      timestamp
    })
  }
}

對於熱更新 js 文件而言,會遞歸地查找引用這個文件的 importer。比如是某個 Vue 文件所引用了這個 js,就會被查找出來。假如最終發現找不到引用者,則會返回 hasDeadEnd: true

const vueImporters = new Set<string>() // 查找並存放需要熱更新的 Vue 文件
const jsHotImporters = new Set<string>() // 查找並存放需要熱更新的 js 文件
const hasDeadEnd = walkImportChain(
  publicPath,
  importers,
  vueImporters,
  jsHotImporters
)

如果 hasDeadEndtrue,則直接發送 full-reload。如果 vueImportersjsHotImporters 里查找到需要熱更新的文件,則發起熱更新通知:

if (hasDeadEnd) {
  send({
    type: 'full-reload',
    timestamp
  })
} else {
  vueImporters.forEach((vueImporter) => {
    send({
      type: 'vue-reload',
      path: vueImporter,
      timestamp
    })
  })
  jsHotImporters.forEach((jsImporter) => {
    send({
      type: 'js-update',
      path: jsImporter,
      timestamp
    })
  })
}

客戶端邏輯的注入

寫到這里,還有一個問題是,我們在自己的代碼里並沒有引入 HRMclient 代碼,Vite 是如何把 client 代碼注入的呢?

回到上面的一張圖,Vite 重寫 App.vue 文件的內容並返回時:

 

注意這張圖里的代碼區第一句話 import { updateStyle } from '/@hmr',並且在左側請求列表中也有一個對 @hmr 文件的請求。這個請求是啥呢?

 

可以發現,這個請求就是上面說的客戶端邏輯的 client.ts 的內容。

src/node/serverPluginHmr.ts 里,有針對 @hmr 文件的解析處理:

export const hmrClientFilePath = path.resolve(__dirname, './client.js')
export const hmrClientId = '@hmr'
export const hmrClientPublicPath = `/${hmrClientId}`

app.use(async (ctx, next) => {
  if (ctx.path !== hmrClientPublicPath) { // 請求路徑如果不是 @hmr 就跳過
    return next()
  }
  debugHmr('serving hmr client')
  ctx.type = 'js'
  await cachedRead(ctx, hmrClientFilePath) // 返回 client.js 的內容
})

至此,熱更新的整體流程已經解析完畢。

vite修改項目端口方法:根目錄新建vite.config.js,配置server.port,文檔:https://vitejs.dev/config/#server-port

vite與webpack區別

webpack會先打包,然后啟動開發服務器,請求服務器時直接給予打包結果。

而vite是直接啟動開發服務器,請求哪個模塊再對該模塊進行實時編譯。

由於現代瀏覽器本身就支持ES Module,會自動向依賴的Module發出請求。vite充分利用這一點,將開發環境下的模塊文件,就作為瀏覽器要執行的文件,而不是像webpack那樣進行打包合並。

由於vite在啟動的時候不需要打包,也就意味着不需要分析模塊的依賴、不需要編譯,因此啟動速度非常快。當瀏覽器請求某個模塊時,再根據需要對模塊內容進行編譯。這種按需動態編譯的方式,極大的縮減了編譯時間,項目越復雜、模塊越多,vite的優勢越明顯。

在HMR(熱更新)方面,當改動了一個模塊后,僅需讓瀏覽器重新請求該模塊即可,不像webpack那樣需要把該模塊的相關依賴模塊全部編譯一次,效率更高。

當需要打包到生產環境時,vite使用傳統的rollup(也可以自己手動安裝webpack來)進行打包,因此,vite的主要優勢在開發階段。另外,由於vite利用的是ES Module,因此在代碼中(除了vite.config.js里面,這里是node的執行環境)不可以使用CommonJS。

介紹ES Module

顧名思義這是ES提供的模塊化規則,主要有importexportsexport default,這三個方法的具體功能和用法可以查看es6官方文檔【module的語法】,我這里主要說明一些注意的知識點。

1. import 異步導入模塊,在js解析階段進行

2. import() 按需導入模塊,可在函數中進行,緩解加載緩慢的問題

3. export {} 導出數據,此處的 {} 不是對象,而是一種約定的符號,用於接收數據,所以不能用es的語法糖

4. 在js引擎中,這里有一個模塊環境記錄的處理,bind導出的字段,類似於: const name = name(name為導出的字段); 如果在導出文件中,后續異步修改了name,在導入文件中拿到的name是最新的name值,就是因為export的內部結構;

既然已經有了 Webpack,尤雨溪為啥再整一個 Vite呢?

webpack 無法避免的問題:

  • 本地開發環境webpack也是需要先打包,然后服務器運行的是打包后的文件,所以代碼量很大的項目就會有啟服務很慢的現象,
  • 熱更新:Webpack 的熱更新會以當前修改的文件為入口重新 build 打包,所有涉及到的依賴也都會被重新加載一次。雖然webpack 也采用的是局部熱更新並且是有緩存機制的,但是還是需要重新打包所以很大的代碼項目是真的有卡頓的現象(親身經歷,例如集成很多子平台的大型項目)

具有了快速冷啟動、按需編譯、模塊熱更新的 Vite

Vite 通過在一開始將應用中的模塊區分為 依賴 和 源碼 兩類,改進了開發服務器啟動時間。

  • 依賴預構建:依賴 大多為在開發時不會變動的純 JavaScript。一些較大的依賴(例如有上百個模塊的組件庫)處理的代價也很高。依賴也通常會存在多種模塊化格式(例如 ESM 或者 CommonJS)。Vite 將會使用 esbuild 預構建依賴。Esbuild 使用 Go 編寫,並且比以 JavaScript 編寫的打包器預構建依賴快 10-100 倍。參考文章:zhuanlan.zhihu.com/p/379164359

這個過程有兩個目的:

  • CommonJS 和 UMD 兼容性: 開發階段中,Vite 的開發服務器將所有代碼視為原生 ES 模塊

  • Vite 將有許多內部模塊的 ESM 依賴關系轉換為單個模塊,以提高后續頁面加載性能。

  • 快速冷啟動:只啟動一台靜態頁面的服務器,對文件代碼不打包,服務器會根據客戶端的請求加載不同的模塊處理(利用的是瀏覽器對esMoudle的原生支持),所以節省了webpack 那一套打包轉化封裝的邏輯。所以大型項目不會再出現熱更新卡頓,起服務慢的情況(理論上,尚未找到合適項目實踐)

與其它非打包解決方案比較

  • 按需編譯、模塊熱更新:采用立即編譯當前修改文件的辦法。同時 vite 還會使用緩存機制( http 緩存 => vite 內置緩存 )是基於緩存的熱更新

    文件緩存:Vite 會將預構建的依賴緩存到node_modules/.vite。它根據幾個源來決定是否需要重新運行預構建步驟:package.json 中的 dependencies 列表, package-lock等

    瀏覽器緩存:解析后的依賴請求會以 HTTP 頭 max-age=31536000,immutable 強緩存,以提高在開發時的頁面重載性能。一旦被緩存,這些請求將永遠不會再到達開發服務器

 

后續有興趣,繼續深入,可以了解一下js 模塊化方案,commonJs等。

ES6之前,JS一直沒有自己的模塊體系,這一點對於大型項目的開發很不友好,所以社區出現了CommonJSAMD(本人不熟悉),CommonJS主要是用於服務器(Node),AMD主要是用於瀏覽器

但是ES6引入了ESM,到此,JS終於有了自己的模塊體系,基本上可以完全取代CJS和AMD。

 以上。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM