<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端框架源碼解讀之Vite

          共 8726字,需瀏覽 18分鐘

           ·

          2022-07-04 22:21

          前端工具鏈?zhǔn)瓯P點(diǎn):https://mp.weixin.qq.com/s/FBxVpcdVobgJ9rGxRC2zfg


          Webpack、Rollup 、Esbuild、Vite ?

          • webpack: 基于 JavaScript 開發(fā)的前端打包構(gòu)建框架,通過依賴收集,模塊解析,生成 chunk,最終輸出生成的打包產(chǎn)物,是一個(gè)BundleBased的框架,優(yōu)點(diǎn)是大而全,缺點(diǎn)是配置繁瑣
          • Rollup: Rollup 是專門針對(duì)類庫(kù)進(jìn)行打包,它的優(yōu)點(diǎn)是小巧而專注,現(xiàn)在很多我們熟知的庫(kù)都都使用它進(jìn)行打包,比如:Vue、React 和 three.js 等。

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

          • Vite: Vite 基于_ESMBased_devServer 在開發(fā)環(huán)境實(shí)現(xiàn)了快速啟動(dòng)、按需編譯、即時(shí)模塊熱更新等能力,同時(shí)針對(duì)同一份代碼,在生產(chǎn)環(huán)境通過 Rollup 進(jìn)行打包,生成線上產(chǎn)物。

          大廠技術(shù)  高級(jí)前端  Node進(jìn)階

          點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)

          回復(fù)1,加入高級(jí)Node交流群

          Vite 簡(jiǎn)介

          背景驅(qū)動(dòng)

          目前比較成熟的前端開發(fā)構(gòu)建工具,如 webpack 等,基本是通過“打包”的方式來進(jìn)行源碼構(gòu)建,即通過對(duì)源碼進(jìn)行依賴收集、構(gòu)建處理,最終生成可在瀏覽器運(yùn)行的 JS 文件,然而隨著項(xiàng)目增長(zhǎng),他們存在以下問題

          1. 打包構(gòu)建時(shí)間也會(huì)隨著增長(zhǎng),項(xiàng)目本地啟動(dòng)緩慢
          1. 更新緩慢,即使使用 HMR 開發(fā),也需要幾秒的時(shí)間代碼變更才能反映到頁面上,嚴(yán)重影響開發(fā)體驗(yàn)

          得益于現(xiàn)在前端生態(tài)系統(tǒng)的快速發(fā)展,Vite 基于下面兩個(gè)新特性去解決上述存在的問題

          1. 瀏覽器開始支持 原生 ES 模塊
          1. 越來越多 JavaScript 工具使用編譯型語言如 Go 等進(jìn)行編寫,加快了構(gòu)建速度

          核心功能





          • 本地開發(fā)環(huán)境,利用瀏覽器支持原生 ESM 文件的特性,不對(duì)源代碼進(jìn)行打包操作,瀏覽器直接動(dòng)態(tài)引入資源,并在 devServer 對(duì)請(qǐng)求的資源進(jìn)行處理,最終返回瀏覽器可運(yùn)行的內(nèi)容
          • 依賴預(yù)構(gòu)建,首次啟動(dòng)的時(shí)候,通過 Esbuild 對(duì)項(xiàng)目的依賴進(jìn)行預(yù)構(gòu)建,并緩存在本地,后續(xù)瀏覽器請(qǐng)求的時(shí)候可以直接返回

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

          • 生產(chǎn)產(chǎn)物構(gòu)建時(shí),基于 Rollup 進(jìn)行打包,并提供了一套  構(gòu)建優(yōu)化  的  構(gòu)建命令,開箱即用。

          Vite 核心模塊原理

          本次分享主要介紹最核心的兩個(gè)功能的實(shí)現(xiàn)原理

          • 依賴預(yù)構(gòu)建
          • 瀏覽器模塊加載流程

          源碼初識(shí)

          源碼版本:v2.8.2

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

          ./src
          ├── client # 客戶端運(yùn)行時(shí)WEB SOCKET以及HMR相關(guān)的代碼
          │ ├── client.ts
          │ ├── env.ts
          │ ├── overlay.ts
          │ └── tsconfig.json
          └── node # 本地服務(wù)器相關(guān)代碼
          ├── __tests__
          ├── build.ts # 生產(chǎn)環(huán)境rollup build代碼
          ├── certificate.ts
          ├── cli.ts # cli,入口
          ├── config.ts
          ├── constants.ts
          ├── http.ts
          ├── importGlob.ts
          ├── index.ts # 導(dǎo)出出口
          ├── logger.ts
          ├── optimizer # 依賴預(yù)構(gòu)建
          ├── packages.ts
          ├── plugin.ts
          ├── plugins # 插件
          ├── preview.ts # build構(gòu)建后,在預(yù)覽模式下啟動(dòng)Vite Server,以模擬生產(chǎn)部署
          ├── server # server文件夾,dev環(huán)境主要代碼
          ├── ssr
          ├── tsconfig.json
          └── utils.ts

          7 directories, 18 files

          我們主要關(guān)注server目錄下的代碼,框架通過在本地啟動(dòng)一個(gè) http+connect 的服務(wù)器,然后在啟動(dòng)之前做一些優(yōu)化操作主入口在src/server/index.tscreateServer函數(shù)中,這個(gè)函數(shù)里做了以下幾件事情


          流程初始化


          1)調(diào)用resolveConfig函數(shù),解析合并各種配置

          2)初始化一個(gè)http+connect服務(wù)器

          3)創(chuàng)建插件容器 ,createPluginContainer方法,把插件的各個(gè)鉤子函數(shù)串聯(lián)起來,后續(xù)在請(qǐng)求處理的過程中直接執(zhí)行掛載好的鉤子函數(shù)

          4)生成一個(gè)server對(duì)象,包含配置信息、服務(wù)器信息、一些輔助函數(shù)等

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

          調(diào)用 server 的 listen 方法

          1)運(yùn)行插件containerbuildStart鉤子,進(jìn)而運(yùn)行所有插件的buildStart鉤子

          2)進(jìn)行依賴預(yù)構(gòu)建,運(yùn)行runOptimize函數(shù)。

          3)開啟服務(wù),監(jiān)聽端口

          請(qǐng)求處理流程

          1)主要處理流程在tansformMiddleware中間件處理,這部分后面的內(nèi)容會(huì)詳細(xì)介紹

          依賴預(yù)構(gòu)建

          進(jìn)行依賴預(yù)構(gòu)建有兩個(gè)目的:

          1. CommonJS 和 UMD 兼容性: 開發(fā)階段中,Vite 的開發(fā)服務(wù)器將所有代碼視為原生 ES 模塊。因此,Vite 必須先將作為 CommonJS 或 UMD 發(fā)布的依賴項(xiàng)轉(zhuǎn)換為 ESM。
          1. 性能:Vite 將有許多內(nèi)部模塊的 ESM 依賴關(guān)系轉(zhuǎn)換為單個(gè)模塊,以提高后續(xù)頁面加載性能。例如將 lodash 中的小模塊打包成一個(gè)大的文件

          參數(shù)配置

          首先看一下,vite 配置中關(guān)于 optimizeDeps 的入?yún)?/p>

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

          預(yù)構(gòu)建結(jié)果

          預(yù)構(gòu)建的結(jié)果默認(rèn)保存在node_modules/.vite中,具體預(yù)構(gòu)建的依賴列表在_metadata.json 文件中,其中_metadata.json 的內(nèi)容為一個(gè) json 結(jié)構(gòu)

          {
          // 配置的hash值
          hash : afcda65e ,
          /**
          * 主要用于瀏覽器獲取預(yù)構(gòu)建的 npm 依賴時(shí),添加的查詢字符串
          * 在依賴變化時(shí),瀏覽器能更新緩存
          */
          browserHash : c369dd06 ,
          optimized : { // 預(yù)構(gòu)建的優(yōu)化列表
          react : {
          // 構(gòu)建后的文件地址
          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 ,
          // 記錄那些在依賴預(yù)構(gòu)建時(shí),使用了commonjs語法的依賴
          // 如果使用了commonjs語法,那么 needsInterop 為 true
          needsInterop : 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
          }
          }
          }

          預(yù)構(gòu)建過程

          入口文件:src/node/optimizer/index.ts,入口函數(shù):optimizeDeps,構(gòu)建過程如下


          1. 調(diào)用getDepHash()函數(shù)去計(jì)算當(dāng)前依賴相關(guān)的的 hash 值,影響依賴預(yù)構(gòu)建 hash 值的內(nèi)容有
            1. 包管理器的 lockfile,例如 package-lock.json,yarn.lock,或者 pnpm-lock.yaml
            2. vite.config.js 中的部分相關(guān)配置,如 plugins、optimizeDeps 的 include 和 exclude 等
          1. 讀取本地_metadata.json 中的 hash 值,判斷和計(jì)算出來的是否一致,一致且未設(shè)置強(qiáng)制構(gòu)建的話,則直接結(jié)束預(yù)構(gòu)建過程,否則需要進(jìn)入預(yù)構(gòu)建過程

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

          其中,scanImports方法會(huì)掃描根目錄下的所有 .html 文件或者用戶配置對(duì) optimizeDeps.entries 文件,然后找到文件中所有的 script 標(biāo)簽,這樣就找到了入口 js 文件,之后調(diào)用 esbuild,通過配置的插件,就可以一層層的找到對(duì)應(yīng)的依賴項(xiàng)了


          1. 使用es-module-lexerparse處理所有的 deps,獲得其中exportsData內(nèi)容 ,并得到依賴 id 到exportsData的映射,用于之后esbuild構(gòu)建時(shí)進(jìn)行依賴圖分析并打包到一個(gè)文件里面,parse 解析后的結(jié)構(gòu)如下圖所示


          1. 調(diào)用esbuild進(jìn)行依賴的預(yù)構(gòu)建,并將構(gòu)建之后的文件寫入緩存目錄node_modules/.vite,得益于 esbuild 比傳統(tǒng)構(gòu)建工具快 10-100 倍的速度,所以依賴預(yù)構(gòu)建也是非常快的
          1. 將 metadata 信息寫進(jìn)本地緩存目錄下,后續(xù)可以直接使用緩存的依賴

          依賴訪問過程

          在進(jìn)行了依賴預(yù)構(gòu)建之后,如何訪問這些已經(jīng)構(gòu)建的依賴呢

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

          2)在瀏覽器去請(qǐng)求資源的時(shí)候,通過resolvePlugin插件去解析,獲得真正的本地文件,匹配到對(duì)應(yīng)的本地緩存資源

          模塊加載

          對(duì)于瀏覽器請(qǐng)求,針對(duì)一個(gè)文件的訪問,vite 會(huì)如何進(jìn)行處理呢?

          主要由以下兩個(gè)中間件來統(tǒng)一處理請(qǐng)求的內(nèi)容,并在中間件處理的流程中調(diào)用 vite 插件容器的相關(guān)鉤子函數(shù)

          • transformMiddleware:核心中間件處理代碼
          • indexHtmlMiddleware:html 相關(guān)請(qǐng)求處理中間件

          vite 插件體系

          在這里,我們先了解一下 vite 的插件體系,Vite 插件擴(kuò)展了設(shè)計(jì)出色的 Rollup 接口,帶有一些 Vite 獨(dú)有的配置項(xiàng)。

          因此,你只需要編寫一個(gè) Vite 插件,就可以同時(shí)為開發(fā)環(huán)境和生產(chǎn)環(huán)境工作。vite 的插件其實(shí)就是定義一個(gè)對(duì)象,該對(duì)象包含了一系列的 hook 函數(shù)配置

          export default function myPlugin() {
          const virtualModuleId = '@my-virtual-module'
          const resolvedVirtualModuleId = '\0' + virtualModuleId

          return {
          name: 'my-plugin', // 必須的,將會(huì)在 warning 和 error 中顯示
          resolveId(id) {
          if (id === virtualModuleId) {
          return resolvedVirtualModuleId
          }
          },
          load(id) {
          if (id === resolvedVirtualModuleId) {
          return `export const msg = from virtual module `
          }
          }
          }
          }

          Rollup 插件兼容性

          相當(dāng)數(shù)量的 Rollup 插件將直接作為 Vite 插件工作,但并不是所有的,因?yàn)橛行┎寮^子在非構(gòu)建式的開發(fā)服務(wù)器上下文中沒有意義。

          一般來說,只要 Rollup 插件符合以下標(biāo)準(zhǔn),它就應(yīng)該像 Vite 插件一樣工作:

          • 沒有使用moduleParsed鉤子。
          • 它在打包鉤子和輸出鉤子之間沒有很強(qiáng)的耦合。

          和rollup保持一致的通用鉤子以下鉤子在服務(wù)器啟動(dòng)時(shí)被調(diào)用:

          • options
          • buildStart

          以下鉤子會(huì)在每個(gè)傳入模塊請(qǐng)求時(shí)被調(diào)用:

          • resolveId
          • load

          • transform

          以下鉤子在服務(wù)器關(guān)閉時(shí)被調(diào)用:

          • buildEnd
          • closeBundle

          Vite 獨(dú)有鉤子

          Vite 插件也可以提供鉤子來服務(wù)于特定的 Vite 目標(biāo)。這些鉤子會(huì)被 Rollup 忽略。

          • config
          • configResolved

          • transformIndexHtml

          • handleHotUpdate

          具體插件執(zhí)行過程

          1)在 dev 環(huán)境模擬了一套和 rollup 保持一致的插件運(yùn)行環(huán)境,確保在開發(fā)環(huán)境和生產(chǎn)環(huán)節(jié)的核心環(huán)節(jié)執(zhí)行同樣的流程

          2)vite 通過createPluginContainer創(chuàng)建了一個(gè)插件容器,將每個(gè)插件中對(duì)應(yīng)的 hook 收集起來

          3)最終在各個(gè)生命周期階段,執(zhí)行對(duì)應(yīng)的已經(jīng)收集好的鉤子

          模塊請(qǐng)求加載過程


          GET /

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

          GET /@vite/client

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

          這個(gè)請(qǐng)求會(huì)直接進(jìn)入 transformMiddleware 中間件中,進(jìn)入中間件的處理過程:中間件會(huì)調(diào)用transformRequest(url, server, options = {})函數(shù)

          1. @vite/client 是如何映射到對(duì)應(yīng)的內(nèi)容呢,在調(diào)用pluginContainer.resolveId的過程中會(huì)遇到 aliasPlugin 插件的鉤子,執(zhí)行名稱替換,最終替換成vite/dist/client/client.mjs
          1. 繼續(xù)將改寫過的路徑傳給下一個(gè)插件,最終進(jìn)入resolvePlugin插件的tryNodeResolve函數(shù),最終解析獲得該文件的 id 為/Users/zhachunliu/.nvm/versions/node/v14.17.0/lib/node_modules/vite/dist/client/client.mjs

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

          GET /src/main.tsx

          針對(duì)普通的 tsx 文件的請(qǐng)求,流程基本上和上面介紹的GET /@vite/client一致,不同點(diǎn)在于使用的插件鉤子內(nèi)容不一樣,因?yàn)樾枰獙?duì) tsx 文件進(jìn)行處理成 js

          1. 通過 resolveId 鉤子函數(shù),將/src/main.tsx 映射到本地文件系統(tǒng)
          1. 調(diào)用 load 鉤子函數(shù),加載本地文件到內(nèi)存中

          2. 通過 vite:react-babel 插件,將 jsx 語法進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換成 js 代碼

          3. 通過 vite:esbuild 插件,進(jìn)行代碼格式化

          4. 通過 vite:import-analysis 插件,將代碼中所有的 import 內(nèi)容,轉(zhuǎn)換成對(duì)應(yīng)的本地文件,方便后續(xù)直接請(qǐng)求

          5. 返回結(jié)果

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

          vite 調(diào)試工具

          vite-plugin-inspect(插件調(diào)試工具,強(qiáng)推)

          在學(xué)習(xí)、調(diào)試或創(chuàng)作插件時(shí),建議在你的項(xiàng)目中引入vite-plugin-inspect。它可以幫助你檢查 Vite 插件的中間狀態(tài)。安裝后,你可以訪問localhost:3000/__inspect/來檢查你項(xiàng)目的模塊和棧信息。請(qǐng)查閱vite-plugin-inspect 文檔中的安裝說明。


          Vite debug 模式

          通過vite --force --debug命令,可以明確的了解到,啟動(dòng)過程和請(qǐng)求過程,經(jīng)歷了什么插件,具體的執(zhí)行流程等,方便調(diào)試


          參考資料

          Node 社群



          我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。



          如果你覺得這篇內(nèi)容對(duì)你有幫助,我想請(qǐng)你幫我2個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多人也能看到這篇文章
          2. 訂閱官方博客 www.inode.club 讓我們一起成長(zhǎng)

          點(diǎn)贊和在看就是最大的支持

            瀏覽 133
            點(diǎn)贊
            評(píng)論
            收藏
            分享

            手機(jī)掃一掃分享

            分享
            舉報(bào)
            評(píng)論
            圖片
            表情
            推薦
            點(diǎn)贊
            評(píng)論
            收藏
            分享

            手機(jī)掃一掃分享

            分享
            舉報(bào)
            <kbd id="afajh"><form id="afajh"></form></kbd>
            <strong id="afajh"><dl id="afajh"></dl></strong>
              <del id="afajh"><form id="afajh"></form></del>
                  1. <th id="afajh"><progress id="afajh"></progress></th>
                    <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    欧美日韩视频在线播放 | 考逼视频免费看 | jiujiu加勒比 | 一级a一级a爱片免费网站 | 91 国产 爽 黄 喷水 |