前言
隨著Vue3的逐漸普及以及Vite的逐漸成熟,我們有必要來了解一下關於vite
的本地構建原理。
對於webpack
打包的核心流程是透過分析JS檔案中引用關係,透過遞迴得到整個專案的依賴關係,並且對於非JS
型別的資源,透過呼叫對應的loader
將其打包編譯生成JS
程式碼,最後再啟動開發伺服器。
瞭解到webpack
的耗時主要花費在打包上,Vite
選擇跳過打包,直接以以 原生 ESM 方式提供原始碼,這樣豈不是可以非常快!
與webpack對比
在Vite
官網有兩張對比圖能夠非常直觀的對比兩者的區別。
這張圖代表的是基於打包器的構建方式(webpack就是其中之一),它在啟動服務之前,需要從入口開始掃描整個專案的依賴關係,然後基於依賴關係構建整個應用生成bundle,最後才會啟動開發伺服器。 這就是這類構建方式為什麼慢的原因,並且整個構建時間會隨著專案的變大變的越來越長!
這張圖代表的是基於ES Module
的構建方式(比如:Vite),這張圖是不是能夠很直觀說明為什麼Vite
會非常快,因為它上來就直接啟動開發伺服器,然後在瀏覽器請求原始碼時進行轉換並按需提供原始碼。根據情景動態匯入程式碼,即只在當前頁面上實際使用時才會被處理。
也就是它不需要掃描整個專案並且打包,不打包的話那它是如何讓瀏覽器拿到分散在專案中的各個模組呢?
這一切都要得益於瀏覽器支援ESM的模組化方案,當瀏覽器識別到模組內的 ESM 方式匯入的模組時,會自動去幫我們查詢對應的內容
這就是為什麼vite
專案的模版檔案中的script
標籤需要加上type=module
,而webpack
專案不需要。
<script type="module" src="/src/main.ts"></script>
vite快的原因
其實上面已經能夠說明vite
為什麼會比webpack
快了,但還有另外一個點在上圖中並沒有表現出來。
Vite
會在一開始將專案中的所有模組分為原始碼和依賴兩類
- 原始碼指的是我們自己寫的程式碼,這類程式碼可能需要轉換(例如 JSX,CSS 或者 Vue/Svelte 元件),並且時常會被編輯。Vite 會以 原生 ESM 方式提供原始碼,同時並不是所有的原始碼都需要同時被載入(例如基於路由拆分的程式碼模組)。
- 依賴大多為在開發時不會變動的純 JavaScript。一些較大的依賴(例如有上百個模組的元件庫)處理的代價也很高。依賴也通常會存在多種模組化格式(例如 ESM 或者 CommonJS)。Vite 將會使用 esbuild預構建依賴。esbuild 使用 Go 編寫,並且比以 JavaScript 編寫的打包器預構建依賴快 10-100 倍。
總結來說就是:基於ESM模組化方案 + 預構建
使用預構建的原因
Vite
使用依賴預構建的原因主要有以下兩點:
- 相容CommonJS與UMD:在開發階段中,Vite 的開發伺服器將所有程式碼視為原生 ES 模組。因此,Vite 必須先將以 CommonJS 或 UMD 形式提供的依賴項轉換為 ES 模組。
- 效能:為了提高後續頁面的載入效能,Vite將那些具有許多內部模組的 ESM 依賴項轉換為單個模組。
可以來看個例子:
我們引入lodash-es
工具包中的debounce
方法,此時它理想狀態應該是隻發出一個請求
import { debounce } from 'lodash-es'
事實也是這樣
但這是預構建的功勞,如果我們對lodash-es
關閉預構建呢?
vite
配置檔案加上如下程式碼,再來試試:
// vite.config.js
optimizeDeps: {
exclude: ['lodash-es']
}
可以看到,此時發起了600多個請求,這是因為lodash-es
有超過 600 個內建模組!
vite透過將 lodash-es
預構建成單個模組,只需要發起一個HTTP請求!可以很大程度地提高載入效能
基本原理
跟著debug
來一步一步看vite
本地是如何工作的
首先從package.json
出發,找到專案啟動命令:
可以看到,dev
對應的命令直接就是vite
,然後我們再找到node_modules
下面的vite
下面的bin
資料夾下面的vite.js
檔案,這就是vite
執行的入口檔案。
這裡有一個start
方法,從這打上斷點開始慢慢往下走,就能夠知道整個執行的基本原理
從上面我們知道,vite
首先是會啟動一個本地服務,基於該服務對檔案的請求進行處理返回
接著往下走,我們可以看到有一個處理url的方法,此時執行棧裡面的address
變數也能夠看到是127.0.0.1:5173
,這就是我們等會要訪問的本地服務,當然現在瀏覽器還什麼也看不到,因為還沒開始處理/
路由,該路由需要返回一個html檔案,也就是我們的模版檔案(專案基於Vue3)
繼續往下走,就可以看到有一個applyHtmlTransforms
方法用來處理html
檔案並返回,可以看到當前請求的原始路徑是/
,返回的檔案是專案根目錄下的index.html
檔案
裡面有一個指令碼檔案<script type="module" src="/src/main.ts"></script>
,接下來就該請求並處理入口檔案main.ts
了
main.ts
檔案如下:
import { createApp } from 'vue'
// import './style.css'
import { debounce } from 'lodash-es'
console.log('--lodash--', debounce)
import App from './App.vue'
createApp(App).mount('#app')
經過處理之後變成了:
它其實也沒做啥處理,只是把依賴的引用路徑處理成了預構建下的路徑(.vite/deps/)
,把原始碼的引用路徑處理成了絕對路徑。
🤔這裡是不是會好奇,瀏覽器不是不能識別處理
vue
檔案嗎,這個不需要處理嗎?(接著往下看!)
來看看此時瀏覽器中的載入順序是怎樣的吧:
整個檔案的載入順序是不是都對上了,注意看這個App.vue
檔案,雖然是.vue
結尾,但檔案型別依然是一個JavaScript
檔案
App.vue
經過編譯後檔案型別已經轉成JS了!
App.vue
檔案內容如下:
<script setup lang="ts">
import { ref } from 'vue'
const userName = ref('前端南玖')
</script>
<template>
<div class="user_name">{{ userName }}</div>
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
編譯後:
再接著往下走,看下style被編譯成了什麼內容:
最後整個頁面就可以渲染出來了!
總結
vite
整體思路:啟動一個 connect 伺服器攔截由瀏覽器請求 ESM的請求。透過請求的路徑找到目錄下對應的檔案做一下編譯最終以 ESM的格式返回給瀏覽器。
對於node_modules
下面的依賴,vite
會使用esbuild
進行預構建,主要是為了相容CommonJS與UMD,以及提高效能。
這樣完整走一遍,是不是對Vite
的理解又更深一步了,它實際上就是“走一步看一步”,不像webpack
上來就掃描整個專案進行打包編譯,所以vite
的構建速度會比較快!
瞭解完vite
工作原理,我們是不是可以來實現一個簡易的vite
工具!