日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

vite 预编译实现

發布時間:2023/12/20 编程问答 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 vite 预编译实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

直入正題,前段時間, vite 做了一個優化 – 依賴預編譯。本文就來逐步分析預編譯的邏輯和代碼實現。

那什么是依賴預編譯呢?這一過程簡而言之,就是在 DevServer 啟動前對須編譯的依賴,進行預先編譯,而后在模塊使用導入(import)時,會直接引用預編譯過的依賴。

我們先來看張圖,梳理一下整體的預編譯邏輯。在 DevServer 啟動前,在模塊使用導入(import)時,vite 會解析該依賴是否有緩存,如果存在,則判斷緩存是否失效,若失效則加入預編譯列表;如未失效則利用 node_modules/.vite 目錄下對應編譯后的依賴;如果不存在,為首次預編譯,則加入預編譯列表中,等所有預編譯依賴收集完成后進行預編譯。

接下來我們分塊梳理。

1. createServer

首先,vite 會創建一個本地開發服務器,這個過程由 createServer 函數完成。

監聽端口,執行其他服務之前,會執行 optimizeDeps 方法,即優化依賴。vite 將這部分優化叫做依賴預打包 Dependency Pre-Bundling,這么做的理由有兩個:一是將非 ES module轉化為可被瀏覽器導入的 ESM;二是將 ESM 依賴的多個內部模塊轉化為一個模塊,以減少瀏覽器請求從而提升頁面加載速度。

createServer 方法中包含初了始化配置,HMR,預打包 等功能。我們重點關注預打包代碼。

createServer 函數:

export async function createServer(inlineConfig: inlineConfig = {} ): Promise<ViteDevServer> {... if (!middlewareMode && httpServer) {// 重寫 DevServer 的 listen,保證在 DevServer 啟動前進行依賴預編譯const listen = httpServer.listen.bind(httpServer)httpServer.listen = (async (port: number, ...args: any[]) => {try {...// 依賴預編譯await runOptimize()} ...}) as any...} else {await runOptimize()}... }

createServer 代碼里可以看到,在服務器啟動前,會先調用 runOptimize 函數,來處理依賴預編譯相關的邏輯。

2. runOptimize

一起看看 runOptimize 函數:

const runOptimize = async () => { // config.optimzizeCacheDir 指的是 node_modules/.vite 文件下的內容,用來存放預編譯的文件if (config.optimizeCacheDir) {...try {// 進行依賴預編譯server._optimizeDepsMetadata = await optimizeDeps(config)}...//注冊依賴預編譯server._registerMissingImport = createMissingImpoterRegisterFn(server)} }

通過代碼知道,runOptimize 函數主要做兩件事:

  • 執行依賴的預編譯方法

  • 注冊新依賴的預編譯

  • 2.1 依賴預編譯

    runOptimize 告訴我們,執行預編譯的核心函數由 optimizeDeps 方法完成。

    optimizeDeps 的實現在第三章具體分析,這里先整體表述 optimizeDeps 邏輯。 optimizeDeps 會根據配置文件 vite.config.js 的 optimizeDeps 對象內容和 package.json 的 dependencies 進行第一次預編譯;對于沒有配置的依賴,vite 會先解析 AST 語法樹里面使用到的依賴,再將該依賴進行預編譯。

    預編譯結束后,在 node_moduels/.vite 文件下生成一份 _metadata.json 對象文件,主要用來存儲預編譯依賴的詳細信息。如下圖:

    里面每個屬性的含義:

    • hash 獲取該文件此時 hash,主要利用文件簽名以及 config 屬性是否改變來判斷,是否須要從新編譯;
    • browserHash 由 hash 和在運行時發現的額定的依賴生成的,主要用于優化請求數量,避免太多的請求影響性能;
    • optimized 包含每個進行過預編譯的依賴,其對應的屬性會描述依賴源文件路徑 src 和編譯后所在路徑 file;
    • needsInterop 主要用于在 vite 進行依賴性導入分析,它會重寫需要預編譯且為 commonJS 的依賴。例如:
    import { debounce } from 'lodash';// 重寫為import $viteCjsImport1_lodash from "/@modules/lodash.js"; const Lodash = $viteCjsImport1_lodash; const debounce = $viteCjsImport1_lodash["debounce"];

    2.2 注冊依賴預編譯

    runOptimize 告訴我們,注冊依賴預編譯調用 createMissingImporterRegisterFn 函數實現,主要是注冊新的依賴預編譯。

    createMissingImporterRegisterFn 函數:

    //在觸發前等待新依賴項的請求數量 export function createMissingImporterRegisterFn(server: ViteDevServer){...async function rerun(){...try{server._isRunningOptimizer = true;server._optimizeDepsMetadata = null;const newData = (server._optimizeDepsMetadata = await optimizeDeps(server.config,true,false,newDeps ))}...}return function registerMissingImport(id:string, resolved: string){...handle = setTimeout(rerun,100);...}... }

    它會返回一個函數,函數內部調用 optimizeDeps 函數進行預編譯。與第一次預編譯不同的是,新預編譯會傳入一個 newDeps,即新的需要預編譯的依賴。

    通過對 runOptimize 里執行依賴的預編譯方法和注冊依賴預編譯代碼的梳理,看到均由 optimizeDeps 函數來實現依賴預編譯。接下來,劃重點 optimizeDeps。

    3. optimizeDeps

    optimizeDeps 是預編譯的核心內容,由于內部邏輯比較復雜,我們拆分為三大步,依賴是否失效 -> 收集依賴 -> esbuild 打包。具體的邏輯如下圖所示。

    3.1 依賴是否生效

    在代碼里依賴失效與否,主要通過文件內容對應的 hash 值來判斷,以便于判斷依賴是否失效以及依賴發生變化時,能夠重新編譯,應用最新的編譯文件。

    第一步,需要讀取緩存的文件信息。

    3.1.1 讀取 hash

    每次編譯都需要讀取該依賴的當前文件信息,調用 getDepHash 方法,拿到對應的 hash 值。
    具體代碼如下:

    // 獲取該文件此時的 hash const mainHash = getDepHash(root, config) const data: DepOptimizationMetadata = {hash: mainHash,browserHash: mainHash,optimized: {} }

    第二步,判斷 hash 值是否失效。

    3.1.2 對比 hash

    對比當前文件的 hash 和 _metadata.json文件的 hash 是否一致,如果一致,則緩存未失效,直接返回上次依賴緩存的信息,optimizeDeps 方法也至此結束;如果不一致,則緩存失效,需要重新進行預編譯。
    具體代碼如下:

    const { root, optimizeCacheDir: cacheDir } = config const dataPath = path.join(cacheDir, '_metadata.json') // 當沒有使用 --force 命令,沒有要求強制重新打包 if (!force) {let prevDatatry {// 獲取到此時緩存中編譯的文件信息prevData = JSON.parse(fs.readFileSync(dataPath, 'utf-8'))} catch (e) {}// hash 一致時無需重新編譯if (prevData && prevData.hash === data.hash) {log('Hash is consistent. Skipping. Use --force to override.')//如果新舊依賴的 Hash 值相等的時候,則返回舊的依賴內容return prevData} }

    第三步,緩存失效或不存在,需要重新預編譯。

    3.1.3 緩存失效或不存在

    如果緩存失效,則刪除緩存文件夾即 node_modules/.vite ;還有一種情況,緩存文件不存在,即第一次進行預編譯,需新建緩存文件夾。
    先來看判斷緩存是否失效代碼:

    const { root, optimizeCacheDir: cacheDir } = config // 判斷緩存是否失效 // cacheDir 即 node_modules/.vite if (fs.existsSync(cacheDir)) {// 失效則刪除緩存文件夾emptyDir(cacheDir) } else {// 首次進行依賴預編譯(緩存文件夾不存在),需創建 cacheDir 文件夾fs.mkdirSync(cacheDir, { recursive: true }) }

    當然了,更新緩存后,需要及時地更新 hash。

    //更新 browser hash data.browserHash = createHash('sha256').update(data.hash + JSON.stringify(deps)).digest('hex').substr(0, 8)

    3.2 收集依賴

    在上述判斷緩存失效后,就需要收集依賴。主要為兩大類依賴,編譯依賴和指定依賴。

    3.2.1 收集編譯依賴

    依賴收集情況分為兩種:首次預編譯和后續更新依賴。這兩者的區別在于,后續更新會傳入一個 newDep 來表示需預編譯模塊。代碼如下:

    let deps: Record<string, string>, missing: Record<string, string> if (!newDeps) {// 首次預編譯;({ missing,deps } = await scanImports(config)) } else {// 后續更新依賴// 直接將需要更新的依賴賦給 deps,此時不存在 missing 依賴deps = newDepsmissing = {} }

    通過代碼知道,如果是第一次預編譯,則會調用 scanImports 函數來找出需要預編譯的依賴 deps 和 missing。

    missing 為引入但不能成功解析的模塊,即在 node_modules 中沒找到的依賴;deps 是一個對象,主要用來存儲模塊路徑,結構如下:

    {lodash:'/Users/user/Documents/user/code/vite/vite-project/node_modules/lodash/lodash.js' }

    3.1.2 收集指定依賴

    預編譯的依賴除了 import 引入也會由 vite.config.js 的 optimizeDeps 選項指定以來。所以在處理完 import 的依賴后,需要處理 optimizeDeps 配置的依賴。

    此時,會遍歷、從 dependencies 獲取到的 deps,判斷 optimizeDeps.iclude(數組)所指定的依賴是否存在,若存在就省去此次制定編譯;若不存在,則加入強制執行編譯依賴中。

    // 拿到 vite.config.js 的 optimizeDeps const include = config.optimizeDeps?.includeif (include) {// 解析依賴const resolve = config.createResolver({ asSrc: false })for (const id of include) {// 制定依賴是否存在 deps 中if (!deps[id]) {const entry = await resolve(id)if (entry) {deps[id] = entry} else {throw new Error(`Failed to resolve force included dependency: ${chalk.cyan(id)}`)}}}}

    3.3 ESbuild 打包

    在確認需要預構建的依賴后,就到了最后一步,使用 esbuild 對依賴進行編譯打包。代碼如下:

    const esbuildService = await ensureService() await esbuildService.build({entryPoints: Object.keys(flatIdDeps),bundle: true,format: 'esm',... })

    ensureService 函數是 vite 外部封裝的 util,ensureService 實質是創立一個 esbuild 的 service,應用 service.build 函數來實現編譯過程。

    flatIdDeps 參數是一個對象,它是由上述的 deps 收集好的依賴創立,它的作用是為 esbuild 進行編譯的時候提供多路口,flatIdDeps 對象:

    {lodash-es:'/Users/user/Documents/FE/demos/vite2.0-demo/node_modules/lodash-es/lodash.js' }

    至此,我們分析了 vite 的預編譯邏輯和代碼實現。vite 通過對依賴進行預編譯和預編譯緩存,防止重復預編譯,可以減少不必要的等待項目重啟或模塊更新時間,從而縮短冷啟動,使得開發人員擁有更良好的開發體驗,加快開發進度。

    總結

    以上是生活随笔為你收集整理的vite 预编译实现的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。