前端框架源碼解讀之Vite

前端工具鏈十年盤點:https://mp.weixin.qq.com/s/FBxVpcdVobgJ9rGxRC2zfg

Webpack、Rollup 、Esbuild、Vite ?

  • webpack: 基于 JavaScript 開發的前端打包構建框架,通過依賴收集,模塊解析,生成 chunk,最終輸出生成的打包產物,是一個BundleBased的框架,優點是大而全,缺點是配置繁瑣

  • Rollup: Rollup 是專門針對類庫進行打包,它的優點是小巧而專注,現在很多我們熟知的庫都都使用它進行打包,比如:Vue、React 和 three.js 等。

  • Esbuild: 一個基于 Go 編寫的高性能構建工具,和其他構建工具相比,速度快到 ?10-100x,其內置了一些 Loader 能解析編譯常見的 JS(X)、TS(X)等文件,同時支持通過插件的形式處理其他類型的文件。

  • Vite: Vite 基于_ESMBased_devServer 在開發環境實現了快速啟動、按需編譯、即時模塊熱更新等能力,同時針對同一份代碼,在生產環境通過 Rollup 進行打包,生成線上產物。

Vite 簡介

ae4f8e26a8ea5b0790ac7a8c1c05308e.png

背景驅動

目前比較成熟的前端開發構建工具,如 webpack 等,基本是通過“打包”的方式來進行源碼構建,即通過對源碼進行依賴收集、構建處理,最終生成可在瀏覽器運行的 JS 文件,然而隨著項目增長,他們存在以下問題

  1. 打包構建時間也會隨著增長,項目本地啟動緩慢

  1. 更新緩慢,即使使用 HMR 開發,也需要幾秒的時間代碼變更才能反映到頁面上,嚴重影響開發體驗

得益于現在前端生態系統的快速發展,Vite 基于下面兩個新特性去解決上述存在的問題

  1. 瀏覽器開始支持 原生 ES 模塊

  1. 越來越多 JavaScript 工具使用編譯型語言如 Go 等進行編寫,加快了構建速度

核心功能

020b8c2dbcca6511f78766694abb6c5e.png

804ff5b19eba15c988efcbc65f988f8c.png

  • 本地開發環境,利用瀏覽器支持原生 ESM 文件的特性,不對源代碼進行打包操作,瀏覽器直接動態引入資源,并在 devServer 對請求的資源進行處理,最終返回瀏覽器可運行的內容

  • 依賴預構建,首次啟動的時候,通過 Esbuild 對項目的依賴進行預構建,并緩存在本地,后續瀏覽器請求的時候可以直接返回

  • 更高效的HMR模塊,利用瀏覽器的緩存特性,優化資源的請求,使得無論應用大小如何,HMR 始終能保持快速更新

  • 生產產物構建時,基于 Rollup 進行打包,并提供了一套 ?構建優化? 的 ?構建命令,開箱即用。

Vite 核心模塊原理

本次分享主要介紹最核心的兩個功能的實現原理

  • 依賴預構建

  • 瀏覽器模塊加載流程

源碼初識

源碼版本:v2.8.2

之前有簡單了解過 Webpack 的源碼,看的一頭霧水,這一層層的 callback 都是些啥?然而 vite 框架的源碼看起來就很簡潔明了,非常易懂

./src
├── client # 客戶端運行時WEB SOCKET以及HMR相關的代碼
│   ├── client.ts
│   ├── env.ts
│   ├── overlay.ts
│   └── tsconfig.json
└── node # 本地服務器相關代碼├── __tests__├── build.ts # 生產環境rollup build代碼├── certificate.ts├── cli.ts # cli,入口├── config.ts├── constants.ts├── http.ts├── importGlob.ts├── index.ts # 導出出口├── logger.ts├── optimizer # 依賴預構建├── packages.ts├── plugin.ts├── plugins # 插件├── preview.ts # build構建后,在預覽模式下啟動Vite Server,以模擬生產部署├── server # server文件夾,dev環境主要代碼├── ssr├── tsconfig.json└── utils.ts7 directories, 18 files

我們主要關注server目錄下的代碼,框架通過在本地啟動一個 http+connect 的服務器,然后在啟動之前做一些優化操作主入口在src/server/index.tscreateServer函數中,這個函數里做了以下幾件事情

6cab0c515cdd144bfcd9422bea76674e.png

流程初始化

1)調用resolveConfig函數,解析合并各種配置

2)初始化一個http+connect服務器

3)創建插件容器 ,createPluginContainer方法,把插件的各個鉤子函數串聯起來,后續在請求處理的過程中直接執行掛載好的鉤子函數

4)生成一個server對象,包含配置信息、服務器信息、一些輔助函數等

5)配置一系列內置中間件,各個中間件做的事情,可以參考文章https://www.modb.pro/db/966326)返回 server 對象

調用 server 的 listen 方法

1)運行插件containerbuildStart鉤子,進而運行所有插件的buildStart鉤子

2)進行依賴預構建,運行runOptimize函數。

3)開啟服務,監聽端口

請求處理流程

1)主要處理流程在tansformMiddleware中間件處理,這部分后面的內容會詳細介紹

依賴預構建

進行依賴預構建有兩個目的:

  1. CommonJS 和 UMD 兼容性: 開發階段中,Vite 的開發服務器將所有代碼視為原生 ES 模塊。因此,Vite 必須先將作為 CommonJS 或 UMD 發布的依賴項轉換為 ESM。

  1. 性能:Vite 將有許多內部模塊的 ESM 依賴關系轉換為單個模塊,以提高后續頁面加載性能。例如將 lodash 中的小模塊打包成一個大的文件

參數配置

首先看一下,vite 配置中關于 optimizeDeps 的入參

export interface DepOptimizationOptions {/*** 入口文件,默認從html文件進行解析收集依賴,如果配置了的話,就從配置文件開始進行解析*/entries?: string | string[]/*** 需要進行預構建的文件*/include?: string[]/*** 不需要進行預構建的依賴*/exclude?: string[]/*** 預構建是通過esbuild進行的,所以可以自定義配置esbuild參數*/esbuildOptions?: Omit<EsbuildBuildOptions,| 'bundle'| 'entryPoints'| 'external'| 'write'| 'watch'| 'outdir'| 'outfile'| 'outbase'| 'outExtension'| 'metafile'>
}

預構建結果

預構建的結果默認保存在node_modules/.vite中,具體預構建的依賴列表在_metadata.json 文件中,其中_metadata.json 的內容為一個 json 結構

{// 配置的hash值hash :  afcda65e ,/*** 主要用于瀏覽器獲取預構建的 npm 依賴時,添加的查詢字符串* 在依賴變化時,瀏覽器能更新緩存*/browserHash :  c369dd06 ,optimized : { // 預構建的優化列表react : {// 構建后的文件地址file :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/react.js ,// 原始文件地址src :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/react/index.js ,// 記錄那些在依賴預構建時,使用了commonjs語法的依賴// 如果使用了commonjs語法,那么 needsInterop 為 trueneedsInterop : true},react-dom : {file :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/react-dom.js ,src :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/react-dom/index.js ,needsInterop : true},lodash : {file :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/lodash.js ,src :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/lodash/lodash.js ,needsInterop : true},react/jsx-dev-runtime : {file :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/react_jsx-dev-runtime.js ,src :  /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/react/jsx-dev-runtime.js ,needsInterop : true}}
}

預構建過程

入口文件:src/node/optimizer/index.ts,入口函數:optimizeDeps,構建過程如下

033b490e82276e429c1033a3a0062e0f.png

  1. 調用getDepHash()函數去計算當前依賴相關的的 hash 值,影響依賴預構建 hash 值的內容有

    1. 包管理器的 lockfile,例如 package-lock.json,yarn.lock,或者 pnpm-lock.yaml

    2. vite.config.js 中的部分相關配置,如 plugins、optimizeDeps 的 include 和 exclude 等

  1. 讀取本地_metadata.json 中的 hash 值,判斷和計算出來的是否一致,一致且未設置強制構建的話,則直接結束預構建過程,否則需要進入預構建過程

  2. 通過({ deps, missing } = await ``scanImports``(config));進行依賴掃描,得到需要處理的依賴,deps 是一個對象,是依賴的包名和文件系統中的路徑的映射,如下圖所示

019e7770735fcc05c1bf77d58f896d72.png

其中,scanImports方法會掃描根目錄下的所有 .html 文件或者用戶配置對 optimizeDeps.entries 文件,然后找到文件中所有的 script 標簽,這樣就找到了入口 js 文件,之后調用 esbuild,通過配置的插件,就可以一層層的找到對應的依賴項了
  1. 使用es-module-lexerparse處理所有的 deps,獲得其中exportsData內容 ,并得到依賴 id 到exportsData的映射,用于之后esbuild構建時進行依賴圖分析并打包到一個文件里面,parse 解析后的結構如下圖所示

    698c4630c68cd6062c62d05ec402fa45.png


  1. 調用esbuild進行依賴的預構建,并將構建之后的文件寫入緩存目錄node_modules/.vite,得益于 esbuild 比傳統構建工具快 10-100 倍的速度,所以依賴預構建也是非常快的

  2. 47039a4f659eb14018b0c7f070387a46.png

  1. 將 metadata 信息寫進本地緩存目錄下,后續可以直接使用緩存的依賴

依賴訪問過程

在進行了依賴預構建之后,如何訪問這些已經構建的依賴呢

1)在加載資源文件的時候,會通過vite:import-analysis插件進行依賴解析,碰到已經進行預構建的依賴,直接替換,將import React from 'react'替換成import __vite__cjsImport2_react from /node_modules/.vite/react.js?v=0f16c3f0這樣的形式

2)在瀏覽器去請求資源的時候,通過resolvePlugin插件去解析,獲得真正的本地文件,匹配到對應的本地緩存資源

模塊加載

對于瀏覽器請求,針對一個文件的訪問,vite 會如何進行處理呢?

主要由以下兩個中間件來統一處理請求的內容,并在中間件處理的流程中調用 vite 插件容器的相關鉤子函數

  • transformMiddleware:核心中間件處理代碼

  • indexHtmlMiddleware:html 相關請求處理中間件

vite 插件體系

在這里,我們先了解一下 vite 的插件體系,Vite 插件擴展了設計出色的 Rollup 接口,帶有一些 Vite 獨有的配置項。

因此,你只需要編寫一個 Vite 插件,就可以同時為開發環境和生產環境工作。vite 的插件其實就是定義一個對象,該對象包含了一系列的 hook 函數配置

export default function myPlugin() {const virtualModuleId = '@my-virtual-module'const resolvedVirtualModuleId = '\0' + virtualModuleIdreturn {name: 'my-plugin', // 必須的,將會在 warning 和 error 中顯示resolveId(id) {if (id === virtualModuleId) {return resolvedVirtualModuleId}},load(id) {if (id === resolvedVirtualModuleId) {return `export const msg =  from virtual module `}}}
}

Rollup 插件兼容性

相當數量的 Rollup 插件將直接作為 Vite 插件工作,但并不是所有的,因為有些插件鉤子在非構建式的開發服務器上下文中沒有意義。

一般來說,只要 Rollup 插件符合以下標準,它就應該像 Vite 插件一樣工作:

  • 沒有使用moduleParsed鉤子。

  • 它在打包鉤子和輸出鉤子之間沒有很強的耦合。

和rollup保持一致的通用鉤子以下鉤子在服務器啟動時被調用:

  • options

  • buildStart

以下鉤子會在每個傳入模塊請求時被調用:

  • resolveId

  • load

  • transform

以下鉤子在服務器關閉時被調用:

  • buildEnd

  • closeBundle

Vite 獨有鉤子

Vite 插件也可以提供鉤子來服務于特定的 Vite 目標。這些鉤子會被 Rollup 忽略。

  • config

  • configResolved

  • transformIndexHtml

  • handleHotUpdate

具體插件執行過程

1)在 dev 環境模擬了一套和 rollup 保持一致的插件運行環境,確保在開發環境和生產環節的核心環節執行同樣的流程

2)vite 通過createPluginContainer創建了一個插件容器,將每個插件中對應的 hook 收集起來

3)最終在各個生命周期階段,執行對應的已經收集好的鉤子

模塊請求加載過程

23d116be2afd5e00817e318e7e180d54.png


GET /

當訪問頁面的時候,實際是有一個 GET / => /index.html 的重定向進入 indexHtmlMiddleware 這個過程,主要做了一件事情,注入 dev 環境需要的一些依賴,@vite/client 主要用來和服務器進行 ws 通信并處理一些 hmr 相關的工作,@react/refresh這段代碼,是 vite-plugin-react 插件注入的代碼,用來處理 dev 環境的一些能力

3360316e6f8a7892537ac7f5eda42fa0.png

GET /@vite/client

前面講到,@vite/client 里面的代碼主要用于與服務器進行 ws 通信來進行 hmr 熱更新、以及重載頁面等操作。

這個請求會直接進入 transformMiddleware 中間件中,進入中間件的處理過程:中間件會調用transformRequest(url, server, options = {})函數

  1. @vite/client 是如何映射到對應的內容呢,在調用pluginContainer.resolveId的過程中會遇到 aliasPlugin 插件的鉤子,執行名稱替換,最終替換成vite/dist/client/client.mjs

  1. 繼續將改寫過的路徑傳給下一個插件,最終進入resolvePlugin插件的tryNodeResolve函數,最終解析獲得該文件的 id 為/Users/zhachunliu/.nvm/versions/node/v14.17.0/lib/node_modules/vite/dist/client/client.mjs

  2. 最終通過pluginContainer.load獲取加載本地文件,然后通過pluginContainer.transform進行代碼轉換,將轉換后的代碼通過send方法發送給瀏覽器

GET /src/main.tsx

針對普通的 tsx 文件的請求,流程基本上和上面介紹的GET /@vite/client一致,不同點在于使用的插件鉤子內容不一樣,因為需要對 tsx 文件進行處理成 js

  1. 通過 resolveId 鉤子函數,將/src/main.tsx 映射到本地文件系統

  1. 調用 load 鉤子函數,加載本地文件到內存中

  2. 通過 vite:react-babel 插件,將 jsx 語法進行轉換,轉換成 js 代碼

  3. 通過 vite:esbuild 插件,進行代碼格式化

  4. 通過 vite:import-analysis 插件,將代碼中所有的 import 內容,轉換成對應的本地文件,方便后續直接請求

  5. 返回結果

其他的所有請求,都是經過類似的插件處理流程,最終返回給瀏覽器一段可執行的 JS 代碼,就不一一介紹了。

vite 調試工具

vite-plugin-inspect(插件調試工具,強推)

在學習、調試或創作插件時,建議在你的項目中引入vite-plugin-inspect。它可以幫助你檢查 Vite 插件的中間狀態。安裝后,你可以訪問localhost:3000/__inspect/來檢查你項目的模塊和棧信息。請查閱vite-plugin-inspect 文檔中的安裝說明。

aecabd3f9f2225a7b17990cad74a02bd.png

Vite debug 模式

通過vite --force --debug命令,可以明確的了解到,啟動過程和請求過程,經歷了什么插件,具體的執行流程等,方便調試

a802791388df818e1df983bb757c4345.png

參考資料

  • 前端工具鏈十年盤點:https://mp.weixin.qq.com/s/FBxVpcdVobgJ9rGxRC2zfg

  • 如何調試 vite 源碼:https://maximomussini.com/posts/debugging-javascript-libraries

  • 源碼理解:https://jishuin.proginn.com/p/763bfbd5f00e


17e17a8bdb23bad775ff6fb5d48d44e6.gif

·················?若川簡介?·················

你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助3000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。

dc43889f705e613bca06210a2c251572.png

掃碼加我微信 ruochuan02、拉你進源碼共讀

今日話題

目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 ruochuan12?進群。分享、收藏、點贊、在看我的文章就是對我最大的支持~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/274731.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/274731.shtml
英文地址,請注明出處:http://en.pswp.cn/news/274731.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

hp-ux_UX中的格式塔-或-為什么設計師如此討厭間距

hp-uxI’ve been lucky so far in my design career to have worked with engineers that seem genuinely interested in learning about design. Perhaps, as mentioned in the title, it’s more about them trying to figure out why it matters so much to us that there i…

很多人都不知道,其實博客園給我們博客開了二級域名

如題。一直都在郵件簽名里寫自己的博客地址為&#xff1a; http://www.cnblogs.com/datacool&#xff1b;直到有天突然發現使用&#xff1a;http://datacool.cnblogs.com也可以訪問。不知道的趕緊測試&#xff0c;后者明顯要酷很多啊。該不是我是最后一個知道的吧&#xff0c;知…

JavaScript 數組新增 4 個非破壞性方法!

大家好&#xff0c;我是若川。持續組織了8個月源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。另外…

自行車改裝電動車怎么樣_電動車聽起來應該是什么樣?

自行車改裝電動車怎么樣The sound of an all-electric car accelerating doesn’t have to sound like a standard combustion engine, It could sound like anything.全電動汽車加速的聲音不必聽起來像是標準的內燃機&#xff0c;它可以聽起來像任何東西。 These were the wor…

C++中的三種繼承public,protected,private(轉)

三種訪問權限 public:可以被任意實體訪問 protected:只允許子類及本類的成員函數訪問 private:只允許本類的成員函數訪問 三種繼承方式 public 繼承 protect 繼承 private 繼承 組合結果 基類中 繼承方式 子類中 public &#xff06; public繼承 > public public &#xff0…

如何碎片化時間學前端,了解前沿趨勢

我很開心在前端行業認識了一批優秀且樂于分享的朋友&#xff0c;他們的技術分享與職業觀點讓我獲益良多&#xff0c;推薦給大家一起關注。程序員成長指北Node.js 前端工程化 低代碼考拉小姐姐&#xff0c;一個有趣且樂于分享的人&#xff01;目前就職于某知名外企&#xff0c;負…

谷歌pay破解_Google Pay缺少Google聞名的一件事-UX案例研究

谷歌pay破解Disclaimer: The views expressed in the blog post is purely based on personal experience. It was not influenced by any external factor.When Google launched Tez (now Google Pay) in India during 2017, their primary goal was to design a simple payme…

進階高級前端,這位大前端架構師一定不能錯過

今天給大家介紹一位好朋友&#xff1a;這波能反殺&#xff1a;一位擁有十年工作經驗&#xff0c;對學習方法有獨到理解的資深大前端架構師。一、博客早在 2017 年初&#xff0c;波神在簡書平臺以《前端基礎進階》為名&#xff0c;更新了一系列優質文章&#xff0c;獲得大量認可…

memcached應用策略(轉)

memcached應用策略&#xff08;轉&#xff09;(2012-04-05 11:10:02) 轉載▼標簽&#xff1a; memcached 應用策略 it分類&#xff1a; linux_c memcached應用策略memcached 主要的作用是為減輕大訪問量對數據庫的沖擊&#xff0c;所以一般的邏輯是首先從memcached中讀取數據&a…

突然討厭做前端,討厭代碼_為什么用戶討厭重新設計

突然討厭做前端,討厭代碼重點 (Top highlight)The core of design thinking is to only design something that will bring value and fill the gap in consumer needs. Right? Why else would one design something that no one asked for? While that may be true to some …

那些年我面過的「六年經驗」的初級工程師

大家好&#xff0c;我是若川。持續組織了8個月源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。另外…

sql反模式分析2

第八章 多列屬性目標&#xff1a;存儲多值屬性 為一個bug設置多個標簽反模式&#xff1a;創建多個列&#xff0c;為bugs創建tag1&#xff0c;tag2&#xff0c;tag3幾個列保存標簽。標簽必須放于其中一個。1.查詢數據&#xff0c;比如搜索這三列&#xff0c;可以使用in語句2.添…

更多信息請關注微信公眾號_為什么我們更多地關注表面異常?

更多信息請關注微信公眾號Don’t you feel lucky to find a single seasoned curly fry in your bunch of plain old boring french fries? Do you remember highlighting important texts of your study materials before the exams? Both situations might seem irrelevant…

eclipse中的漢字極小的解決方案(轉載)

eclipse中的漢字極小的解決方案(轉載) 可能新裝了eclipse后&#xff0c;寫java代碼的時候發現&#xff0c;寫注釋的時候發現&#xff0c;漢字小的可憐&#xff0c;網上搜一下&#xff0c;又是改字體又是設置字體大小&#xff0c;試用后發現都不是針對這個的方法。 無奈在自己摸…

面試官經常問的觀察者模式如何實現~

大家好&#xff0c;我是若川。持續組織了8個月源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。另外…

旅行者 問題_門檻項目:沒有旅行者回到他的原籍城市。

旅行者 問題Sohini Mukherjee| MFA| Spring 2020Sohini Mukherjee | 外交部| 2020年Spring Artivive app to see the full Artivive應用程序可查看完整的#AR experience.#AR體驗。 Prompt:提示&#xff1a; As second semester, first year graduate students, you are at a …

產品經理懂技術=流氓會武術(zz)

最近七年&#xff0c;我都在做互聯網產品&#xff0c;其中前五年分別在創業公司和上市公司里&#xff0c;做別人的產品&#xff1b;近兩年在創業&#xff0c;做自己的產品。 我的體會是&#xff1a;產品經理需要懂技術&#xff0c;創業者尤其需要。但前提是你總覺得有股憋不住的…

技術人的七大必備特質

大家好&#xff0c;我是若川。持續組織了8個月源碼共讀活動&#xff0c;感興趣的可以 點此加我微信ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。另外…

figma下載_在Figma中進行原型制作的技巧和竅門

figma下載自定義過渡和微交互 (Custom transitions and micro-interactions) Yep, I know that there are a lot of useful built-in transition effects in Figma already, but here I want to talk about custom micro-interactions, complicated transitions and show you h…

布局收藏用

http://www.aa25.cn/layout/index.shtml轉載于:https://www.cnblogs.com/OceanChen/archive/2012/07/25/2608882.html