一、ES module 減少服務啟動時間
import { foo } from './other-module'
由於大多數現代瀏覽器都支持上面的 ES module 語法,所以在開發階段,我們就不必對其進行打包,這節省了大量的服務啟動時間。另外,vite 按需加載當前頁面所需文件,一個文件一個http請求,進一步減少啟動時間。
二、緩存減少頁面更新時間
每個文件通過 http 頭緩存在瀏覽器端,當編輯完一個文件,只需讓此文件緩存失效。當基於 ES module 進行熱更新時,僅需更新失效的模塊,這使得更新時間不隨包的增大而增大。
vite 怎么做到的?
當我們執行yarn dev
時,vite-cli 會在本地啟動一個 koa 服務:
export function createServer(config) {
...
const app = new Koa()
const server = resolveServer(config, app.callback())
...
// 加載插件
...
const listen = server.listen.bind(server)
server.listen = (async (port, ...args) => {
if (optimizeDeps.auto !== false) {
await require('../optimizer').optimizeDeps(config)
}
return listen(port, ...args)
})
...
return server;
}
監聽端口,執行其他服務之前,會執行optimizeDeps
方法,即優化依賴。vite 文檔將這部分優化叫做依賴預打包Dependency Pre-Bundling,這么做的理由有兩個:一是將非 ES module轉化為可被瀏覽器導入的 ESM;二是將 ESM 依賴的多個內部模塊轉化為一個模塊,以減少瀏覽器請求從而提升頁面加載速度。
按需加載
上面我們提到,vite通過按需加載減少等待時間,這是如何做到的?所謂一生二,二生三,三生萬物。一切的根源就是服務啟動后,我們訪問的初始地址http://localhost:3000/
。首頁,瀏覽器根據這個 url 發出第一個請求,通過serverPluginServeStatic.ts
插件獲取到位於項目根目錄的index.html
文件,再通過serverPluginHtml.ts
向其插入<script type="module">import "/vite/client"</script>
,最終我們看到第一個請求返回的內容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<script type="module">import "/vite/client"</script>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
我們先看<script type="module" src="/src/main.js"></script>
,瀏覽器向我們的本地服務器請求這個文件,koa 插件serverPluginModuleRewrite.ts
會重寫main.js
源文件的 import 的解析方式:
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
上面的內容經過插件處理后,將以下內容返回給瀏覽器:
import { createApp } from '/@modules/vue.js'
import App from '/src/App.vue'
import '/src/index.css?import'
這里文檔npm-dependency-resolving也有部分提及。瀏覽器解析此文件,依次發送三個請求:
- http://localhost:3000/@modules/vue.js
- http://localhost:3000/src/App.vue
- http://localhost:3000/src/index.css?import
當瀏覽器發送第二個請求,koa 插件serverPluginVue.ts
會編譯單文件組件 App.vue,並打上 etag(用於緩存)后再返回給瀏覽器(A流程)。瀏覽器再解析此文件並更新頁面,重復這個過程(發送請求、服務端響應、瀏覽器解析)直到不再發送請求。
組件熱更新
vue 組件熱更新,由serverPluginVue.ts
、serverPluginHmr.ts
、client.ts
三個部分共同完成。其中client.ts
會被發送到瀏覽器端,位於服務端的 serverPluginHmr.ts 通過 websocket 和 client.ts 進行通信。
我們以vite-cli默認模板項目舉例,假設更改了App.vue
文件內容,此時:
-
1)serverPluginVue.ts 使用 serverPluginHmr.ts 提供的 send 方法向 client.ts 所在的瀏覽器端發送數據,即數據通過 websockt 從服務端推送到瀏覽器,數據內容如下:
{ "type": "vue-reload", "path": "/src/App.vue", "changeSrcPath": "/src/App.vue", "timestamp": 1609815863830 }
-
2) client.ts 收到數據后,執行
import('/src/App.vue?t=1609815863830')
-
3)接着瀏覽器發起請求:
http://localhost:3000/src/App.vue?t=1609815863830
,時間戳的作用是使url緩存失效 -
4)最后,執行重復上面的A流程。
經過上述步驟,頁面得以更新。當然,更新的類型還有full-reload
、js-update
以及樣式更新等,但總體流程都框定在以上步驟,這里就不展開了。
p.s.以上內容搭配vite源碼再看更易理解