對于Vite來說,它是基于esbuild與rollup雙引擎設計的,在開發階段使用esbuild進行依賴預構建,然后基于瀏覽器原生支持的ESM完成開發預覽,而在生產環境打包時,直接使用的rollup構建。那么在這種背景下,Vite的插件機制應該如何設計?
在源碼中,我們能夠經常看到PluginContainer的身影,Vite正是通過它來模擬rollup的行為
PluginContainer 的 實現 基于借鑒于 WMR 中的 rollup-plugin-container.js ,主要功能有兩個:
在開發階段,vite會模擬rollup的行為,所以插件的執行機制也與rollup相同
圖片
?
這里需要注意的是:在 vite 中由于 AST 分析是通過 esbuild 進行的,所有沒有模擬 moduleParsed 鉤子
上下文對象通過 Context 實現 PluginContext 接口定義,PluginContext 實際上是 Rollup 內部定義的類型,可以在源碼中看到 vite 實現了 Rollup 上下文對象
class Context implements PluginContext { //... 具體實現}type PluginContext = Omit< RollupPluginContext, // Rollup 定義插件上下文接口 // not documented | 'cache' // deprecated | 'moduleIds'>
Context 上下文對象一共有 14 個核心方法,其中有 3 個方法是比較核心的方法
更多內容可以查看rollup文檔
rollup構建流程主要分為兩大類:build和output,build 階段主要負責創建模塊依賴圖,初始化各個模塊的 AST 以及模塊之間的依賴關系。output階段才是真正的打包構建過程
通過構建流程rollup的hook類型可以分為:build hook和output hook兩大類
除了上面這種分類,rollup插件還可以根據各自的執行方式來進行分類:
除了根據構建階段可以將 Rollup 插件進行分類,根據不同的 Hook 執行方式也會有不同的分類,主要包括Async、Sync、Parallel、Squential、First這五種。在后文中我們將接觸各種各樣的插件 Hook,但無論哪個 Hook 都離不開這五種執行方式。
首先是Async和Sync鉤子函數,兩者其實是相對的,分別代表異步和同步的鉤子函數,這個很好理解。
這里指并行的鉤子函數。如果有多個插件實現了這個鉤子的邏輯,一旦有鉤子函數是異步邏輯,則并發執行鉤子函數,不會等待當前鉤子完成(底層使用 Promise.all)。
比如對于Build階段的buildStart鉤子,它的執行時機其實是在構建剛開始的時候,各個插件可以在這個鉤子當中做一些狀態的初始化操作,但其實插件之間的操作并不是相互依賴的,也就是可以并發執行,從而提升構建性能。反之,對于需要依賴其他插件處理結果的情況就不適合用 Parallel 鉤子了,比如 transform。
Sequential 指串行的鉤子函數。這種 Hook 往往適用于插件間處理結果相互依賴的情況,前一個插件 Hook 的返回值作為后續插件的入參,這種情況就需要等待前一個插件執行完 Hook,獲得其執行結果,然后才能進行下一個插件相應 Hook 的調用,如transform。
如果有多個插件實現了這個 Hook,那么 Hook 將依次運行,直到返回一個非 null 或非 undefined 的值為止。比較典型的 Hook 是 resolveId,一旦有插件的 resolveId 返回了一個路徑,將停止執行后續插件的 resolveId 邏輯。
以下鉤子在服務器啟動時被調用:
以下鉤子會在每個傳入模塊請求時被調用:
它們還有一個擴展的 options 參數,包含其他特定于 Vite 的屬性。
以下鉤子在服務器關閉時被調用:
請注意 moduleParsed 鉤子在開發中是 不會 被調用的,因為 Vite 為了性能會避免完整的 AST 解析。
output階段的hook(除了 closeBundle) 在開發中是 不會 被調用的。你可以認為 Vite 的開發服務器只調用了 rollup.rollup() 而沒有調用 bundle.generate()。
Vite 插件也可以提供鉤子來服務于特定的 Vite 目標。當然這些鉤子會被 Rollup 忽略。
在解析 Vite 配置前調用。鉤子接收原始用戶配置(命令行選項指定的會與配置文件合并)和一個描述配置環境的變量,包含正在使用的 mode 和 command。它可以返回一個將被深度合并到現有配置中的部分配置對象,或者直接改變配置(如果默認的合并不能達到預期的結果)。
// 返回部分配置(推薦)const partialConfigPlugin = () => ({ name: 'nanjiu-plugin', config(config, { command }) { console.log('config', config, command) }})
圖片
需要注意的是:用戶插件在運行這個鉤子之前會被解析,因此在 config 鉤子中注入其他插件不會有任何效果。
在解析 Vite 配置后調用。使用這個鉤子讀取和存儲最終解析的配置。
const examplePlugin = () => { let config return { name: 'read-config', configResolved(resolvedConfig) { // 存儲最終解析的配置 config = resolvedConfig }, // 在其他鉤子中使用存儲的配置 transform(code, id) { if (config.command === 'serve') { // dev: 由開發服務器調用的插件 } else { // build: 由 Rollup 調用的插件 } }, }}
是用于配置開發服務器的鉤子。最常見的用例是在內部 connect 應用程序中添加自定義中間件
const myPlugin = () => ({ name: 'configure-server', configureServer(server) { server.middlewares.use((req, res, next) => { // 自定義請求處理... }) },})
與 configureServer 相同,但用于預覽服務器。configurePreviewServer 這個鉤子與 configureServer 類似,也是在其他中間件安裝前被調用。如果你想要在其他中間件 之后 安裝一個插件,你可以從 configurePreviewServer 返回一個函數,它將會在內部中間件被安裝之后再調用
const myPlugin = () => ({ name: 'configure-preview-server', configurePreviewServer(server) { // 返回一個鉤子,會在其他中間件安裝完成后調用 return () => { server.middlewares.use((req, res, next) => { // 自定義處理請求 ... }) } },})
轉換 index.html 的專用鉤子。鉤子接收當前的 HTML 字符串和轉換上下文。上下文在開發期間暴露ViteDevServer實例,在構建期間暴露 Rollup 輸出的包。
這個鉤子可以是異步的,并且可以返回以下其中之一:
默認情況下 order 是 undefined,這個鉤子會在 HTML 被轉換后應用。為了注入一個應該通過 Vite 插件管道的腳本, order: 'pre' 指將在處理 HTML 之前應用。order: 'post' 是在所有未定義的 order 的鉤子函數被應用后才應用。
const htmlPlugin = () => { return { name: 'nanjiu-plugin', transformIndexHtml(html) { return html.replace(/<title>(.*?)<//title>/, `<title> nanjiu plugin </title>`) }, }}
執行自定義 HMR 更新處理。鉤子接收一個帶有以下簽名的上下文對象
interface HmrContext { file: string timestamp: number modules: Array<ModuleNode> read: () => string | Promise<string> server: ViteDevServer}
const hotPlugin = () => { return { name: 'nanjiu-plugin', handleHotUpdate({ server, modules, timestamp}) { console.log('handleHotUpdate', modules) }, }}
當我修改App.vue文件時,modules可以獲取到如下信息:
圖片
一個 Vite 插件可以額外指定一個 enforce 屬性(類似于 webpack 加載器)來調整它的應用順序。enforce 的值可以是pre 或 post。解析后的插件將按照以下順序排列:
請注意,這與鉤子的排序是分開的,鉤子的順序仍然會受到它們的 order 屬性的影響,這一點 和 Rollup 鉤子的表現一樣
vite 在 開發環境中,會使用 createPluginContainer 方法創建插件容器,插件容器有兩個核心功能:管理插件生命周期、傳遞插件上下文
圖片
本文鏈接:http://www.tebozhan.com/showinfo-26-101721-0.html淺析Vite插件機制,你學會了嗎?
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 軟件版本號為什么那么奇怪?