久久综合九色综合97婷婷-美女视频黄频a免费-精品日本一区二区三区在线观看-日韩中文无码有码免费视频-亚洲中文字幕无码专区-扒开双腿疯狂进出爽爽爽动态照片-国产乱理伦片在线观看夜-高清极品美女毛茸茸-欧美寡妇性猛交XXX-国产亚洲精品99在线播放-日韩美女毛片又爽又大毛片,99久久久无码国产精品9,国产成a人片在线观看视频下载,欧美疯狂xxxx吞精视频

有趣生活

當前位置:首頁>職場>webpack5優缺點(從構建進程間緩存設計談)

webpack5優缺點(從構建進程間緩存設計談)

發布時間:2024-01-24閱讀(13)

導讀讓我們把目光先聚焦到即將破土而出的Webpack5上,盡管國內外已經有搶鮮試水的嘗試,其中也不乏@張立理的文章:Webpack5升級實驗,講述升級路徑和體會....

webpack5優缺點(從構建進程間緩存設計談)(1)

讓我們把目光先聚焦到即將破土而出的 Webpack 5 上,盡管國內外已經有搶鮮試水的嘗試,其中也不乏@張立理的文章:Webpack 5 升級實驗,講述升級路徑和體會,但是尚沒發現從技術原理角度的設計解析。

這篇文章,我就以 Spec: A module disk cache between build processes 為方向,介紹一下 Webpack 5 最令人期待的「長效緩存」功能的前世今生,技術背景以及落地方案。同時希望“管中窺豹”,介紹一下整體 Webpack 的構建流程。整篇文章將會設計大量 Webpack 實現原理和體系設計,閱讀需要一定的前置知識和理解成本。總之:“你認為的緩存不僅僅是簡單的「空間換時間」,同時,你認為的「Webpack 工程師」恰恰是前端體系中最具功力的拼圖板塊”。

關于現狀和深度場景 關于問題和解決方向

這一部分我們將從兩個方面,介紹現有 webpack 構建的痛點和已有解決方案,我們逐一分析這些解決方案的不足,并以 Webpack 官方視角,來探討是否存在更可行、更優雅的官方解決方案。

不間斷進程(continuous processes)和緩存

對于大型復雜項目應用,在開發階段,開發者一般習慣使用 Webpack --watch 選項或者 webpack-dev-server 啟動一個不間斷的進程(continuous processes)以達到最佳的構建速度和效率。Webpack --watch 選項和 webpack-dev-server 都會監聽文件系統,進而在必要時,觸發持續編譯構建動作。此中細節值得研究,源碼涉及到文件系統的監聽、內存讀寫、不同操作系統的兼容、插件化和分層緩存設計等,也有很多著名的性能優化 issues,這里不再一一介紹,但提煉出關鍵點需要讀者優先了解,以幫助對后文的理解消化:

  • 正常啟動 Webpack 構建流程會調用 compiler.run 方法
  • --watch 模式啟動的 Webpack 構建流程會調用 compiler.watch 方法,并啟動一個構建 watch 服務
  • webpack-dev-server 是一個小型的 NodeJS 服務器,它使用 webpack-dev-middleware 這個包,webpack-dev-middleware 也是最終調用了 compiler.watch 方法
  • --watch 模式依靠各層級的緩存提高后續構建速度
  • --watch 模式下,完成第一次構建后,為了后續不再重復啟動構建進程,Webpack 會在構造函數 Watching 的原型方法 done 上(Watching.prototype.done)監聽文件的變動,實時進行構建
  • 因此,watch 服務進程會處在:「構建 -> 監聽文件變動 -> 觸發重新構建 -> 構建」的循環當中 Webpack 采用 graceful-fs 這個包來實現文件的讀寫,它對 Node.js 中的 fs 模塊進行了擴展和封裝,優化了使用方式
  • 除了 graceful-fs,業界(比如 webpack-dev-middleware)使用 memory-fs,借助 memory-fs,可以將 compiler 的 outputFileSystem 設置成 MemoryFileSystem,這樣以內存讀寫的方式,將資源編譯文件不落地輸出,大大提高構建性能
  • 截止到此,對文件的監聽邏輯源碼重點在 compiler.watchFileSystem 對象的 watch 方法,具體以 NodeEnvironmentPlugin 插件輔助 Webpack 的 watchpack 模塊進行加載
  • 上述監聽文件(夾)的底層采用的是 chokidar 包執行
  • 文件(夾)發生變化時,除了進行實例事件觸發以外,還有進行文件變更數據的更新,以及 FSACCURENCY 的校準邏輯。FSACCURENCY 校準是為了平衡「文件系統數據低精確度而導致 mtime 相同但確實發生了變化」的情況
  • 底層文件(夾)監聽觸發的功能依賴于對 EventEmitter 的繼承,并最終完成上層 Webpack 重新構建流程
  • Webpack 的 --watch 選項內置了類似 batching 的能力,我們稱之為 aggregateTimeout。意思是說:在觸發 Watchpack 實例監聽的文件(夾)的 change 事件后,會將修改的內容暫存的 aggregatedChanges 數組中,并在最后一次文件(夾)沒有變更的 200ms 后,將聚合事件 emit 給上層

了解這些內容,大家應該能大致明白「Webpack --watch 模式的背后發生了什么」以及「Webpack --watch 模式下第一次構建耗時較多,后續的構建速度卻大幅度提升」——這一現象背后的實現原理了。原理雖然就是大家都知道的“緩存”這么簡單,但是我們還要刨根問底:--watch 流程是如何利用事件模型,如何采用多個邏輯層設計,如何對觸發流程進行解耦,最終實現清晰而可靠的代碼的。各種細節需要結合源碼逐一分析,這里不是我們的重點,暫不展開。

盡管如此,并不是所有的 Webpack 使用都需要開啟一個不間斷的可持續進程(continuous processes,下文用可持續進程表達),比如在 CI(Continuous Integration)持續集成階段以及構建線上應用包(Production Build)的階段。這些階段的構建成本甚至更高,因為開發者需要在 CI/CD pipeline 和構建線上應用時,對 Webpack 配置加入代碼優化、代碼壓縮等插件。

為此社區上誕生了很多偉大的,勵志于縮短 Webpack 構建時間以及減小成本的解決方案,他們包括但不限于:

  • cache-loader
  • DllReferencePlugin
  • auto-dll-plugin
  • thread-loader
  • happypack
  • hard-source-webpack-plugin

這里簡要進行說明:cache-loader 可以在一些性能開銷較大的 loader 之前添加,目的是將結果緩存到磁盤里;DLLPlugin 和 DLLReferencePlugin 實現了拆分 bundles,同時節約了反復構建 bundles 的成本,大大提升了構建的速度;thread-loader 和 happypack 實現了單獨的 worker 池,用于多進程/多線程運行 loaders;不過有趣的是,vue-cli 和create-react-app 并沒有使用到 dll 技術,而是使用了更好的代替者:hard-source-webpack-plugin。

這些社區方法優化的實現是以犧牲部分文件體積和后續優化空間為代價的。所有這些方法的使用也需要一定的學習成本,更別說普通開發者參與實現和開發成本了。

再談文件監聽和緩存:unsafe cache

上面我們更多提到了模塊緩存的概念。除了模塊之外,還有個不可忽視的緩存目標,我們稱之為 resolvers unsafe cache。

什么是 resolvers unsafe cache 呢?我們先要從 Webpack 中 resolver 這個概念說起。Webpack 帶來的一大理念是:一切皆模塊。在項目中我們可以使用 ESM 的方式 import ./xxx/xxx 或者 import somepkgin_nodemodules,甚至使用 alias:import @/xxx 來實現模塊化。Webpack 在處理這些引用時,是通過 resolve 過程,找到正確的目標文件。其實不光是項目代碼中的引入聲明,在 Webpack 的整體處理流程,包括 loaders 的尋找等,只要涉及到文件路徑的,都離不開 resolve 過程。因此 resolve 可以簡單地理解為“文件路徑查找”。Webpack 對使用者也暴露了 resolve 的配置,我們可以對文件路徑查找過程進行適當的配置,比如設置文件擴展名,查找搜索的目錄等。因此,resolve 過程也會涉及到很多耗時的操作。

Webpack 源碼中對于 resolver 的實現主要依賴 enhanced-resolve 的 ResolverFactory,它一共創建了三種類型的 resolver:

  • normalResolver:提供文件路徑解析功能,用于普通文件導入
  • contextResolver:提供目錄路徑解析功能,用于動態文件導入
  • loaderResolver:提供文件路徑解析功能,用于 loader 文件導入

在 Webpack 構建運行時,對于每一種類型模塊,都會使用 Resolver 預先判斷路徑是否存在,并獲取路徑的完整地址供后續加載文件使用。當然對于這三種類型 resolver,也設置了緩存:Webpack 本身通過 UnsafecachePlugin 對 resolve 結果進行緩存,對于相同引用,返回緩存路徑結果。

UnsafeCachePlugin 插件原理很簡單:它通過 UnsafeCachePlugin.prototype.apply 方法,覆蓋原有 Resolver 實例的 resolve 方法,新的方法上會包裝一層路徑結果 cache,以及包裝了在完成原有方法后進行 cache 更新的邏輯。

聽上去也很簡單,但是這個設計和實現過程關聯到「是否需要重新構建」的決斷,這就值得深究一下了。我們來具體分析:

在通過 UnsafeCachePlugin 插件完成了必備文件路徑查找之后,如果編輯過程沒有出錯,且當前 loader 調用了 this.cacheable(),且存在上一次構建的結果集合,那么即將進入「是否需要重新構建」的決斷(needRebuild),決斷策略根據當前模塊的 this.fileDependencies 和 this.contextDependencies 這兩個關鍵因素來確定。this.fileDependencies 表示當前模塊所關聯的文件依賴;this.contextDependencies 表示模塊關聯的文件夾依賴。我們先獲取這兩類依賴的最后變更時間(contextTimestamps、fileTimestamps)的最大值 timestamp,再和上一次構建時間 buildTimestamp 進行比對,如果 timestamp >= buildTimestamp,則表示需要重新編譯。如果不需要重新編譯,直接讀取 compilation 對象中的 cache 屬性相關值。

請思考,為什么 UnsafeCachePlugin 這個插件的名字需要加上一個 unsafe 前綴呢?事實上,這類緩存(unsafe cache)默認在 Webpack core 中打開,但是它犧牲了一定的 resolving 準確度,同時它意味著持續性構建過程需要反復重新啟動決斷策略,這就要收集文件的尋找策略(resolutions)的變化,要識別判斷文件 resolutions 是否變化,這一系列過程也是有成本的,只不過對于大多數應用,應用緩存 resolutions 性價比更高,是能夠顯著提升應用構建性能的。

Webpack 5 新的設計提案

了解了上述知識,我們繼續探討已有方案的缺陷以及 Webpack 5 持久化緩存設計的“臺前幕后”。Webpack 5 令人期待的持久緩存優化了整個構建流程,原理依然還是那一套:當檢測到某個文件變化時,根據依賴關系,只對依賴樹上相關的文件進行編譯,從而大幅提高了構建速度。官方經過測試,16000 個模塊組成的單頁應用,速度竟然可以提高 98%!其中值得注意的是持久緩存會將緩存存儲到磁盤。

對于一個持續化構建過程來說,第一次構建是一次全量構建,它會利用磁盤模塊緩存,使得后續的構建從中獲利。后續構建具體流程是:讀取磁盤緩存 -> 校驗模塊 -> 解封模塊內容。因為模塊之間的關系并不會被顯式緩存,因此模塊之間的關系仍然需要在每次構建過程中被校驗,這個校驗過程和正常的 webpack 進行分析依賴關系時的邏輯是完全一致的。對于 resolver 的緩存同樣可以持久化緩存起來,一旦 resolver 緩存經過校驗后發現準確匹配,就可以用于快速尋找依賴關系。對于 resolver 緩存校驗失敗的情況,將會直接執行 resolver 的常規構建邏輯。正常來講,resolver 的變化也將會引起持續構建過程中文件路徑變化的鉤子觸發。

緩存設計和安全性校驗

那么如何設計這樣一個持久化緩存呢?從數據類型和結構上來說,JSON 無疑是一個最好的選擇。配合 JSON 數據,我們實現讀寫磁盤上的模塊緩存數據以及每個模塊的狀態字段,這個模塊狀態字段將會在校驗緩存可用性的階段派上用場。

對于這樣的緩存設計,非常重要的一點是緩存的安全性和可用性,也就是說我們需要保證持久化緩存是一個 safe cache。任何被緩存的數據都要有一個對應校驗可用性的邏輯。如何來保障校驗的準確,是非常核心重要的課題。具體來說:對于每個模塊,我們根據時間戳 timestamps 和內容 hashes 來做可用性校驗。但是這里的 timestamps 并不能完全保證準確性, 因為實際情況中,會存在文件內容改變,但是 timestamps 并沒有變化或者甚至 timestamps 變小的情況(比如該文件在依賴關系上下文中被刪除,或有被重命名的情況)。因此使用文件內容的 hashes(或其他內容比較算法)就靠譜很多。這里需要注意的是根據文件系統的 metadata 的比較算法(Filesystem metadata comparisons)也是不安全的,因為這個 metadata 往往都是通過文件大小和最近修改時間混淆得到的。

緩存校驗的安全性將是 Webpack 5 不同于以往版本的一大關鍵,我們總結一下:

  • 模塊內容基于 timestamps 或 filesystem metadata 的校驗需要被基于 hash 算法或其他基于內容的比對算法取代
  • 文件依賴關系(File dependency)的 timestamps 的校驗需要被文件內容 hashes 算法取代
  • 上下文依賴(Context dependency)的 timestamps 的校驗需要被文件路徑的 hashes 算法取代

這里提到的 File dependency 和 Context dependency 是 Webpack 內的重要概念,這里不再展開,只需要讀者了解緩存校驗設計的思路即可。

除了模塊緩存,前面提到過的 resolver 緩存同樣需要有類似的緩存校驗過程。那么這兩種校驗過程也同樣需要被優化,以達到更好的性能和構建速度。

兄弟緩存(cache sibling)和緩存集合概念

文件依賴關系的變動,文件內容的變動都會觸發構建,進而對每個模塊的緩存進行校驗和應用。同樣值得思考的是:不同的 Webpack 配置(比如對 Webpack 配置的修改),不應該直接導致模塊緩存失效,而是應該對應不同的緩存集合。

Webpack 配置可能會在開發期間頻繁地被改動,對于不同配置的緩存集合,我們可以使用配置內容的 hash 來做標記,標記出該配置下的緩存集合。對于持續性構建來說,每個新增構建會根據當前的配置 hash 找到匹配的緩存集合,再繼續進行構建過程。

如下圖:

webpack5優缺點(從構建進程間緩存設計談)(2)

另外一個注意點是對于第三方依賴即 nodemodules 文件內內容的緩存和更新。對于此,Yarn 和 Npm 5 以上版本,這些包管理機制自身可以通過鎖文件的 hash 校驗來保證依賴內容的一致性和更新監聽。對于 Npm 5 以下版本或其他情況,我們仍然需要一種緩存和更新機制,來保證依賴內容的一致性和變化監聽。一種常用做法是:將 nodemodules 文件夾內第一層所有的 package.json 文件集合進行 hash 化。

緩存淘汰策略設計

我們在設計任何一種緩存體系時,除了要考慮緩存校驗,還要考慮到緩存容量限制。Webpack 5 持久化緩存當然不能無限制的擴展,對于磁盤的合理利用和緩存清理設置是必不可少的關鍵環節。

初期 Webpack 5 核心開發者 mzgoddard 在討論設計時認為:對于一個緩存集合,最大限度應該不超過 5 個緩存內容,最大累積資源占用不超過 500 MB,當逼近或超過 500MB 的閾值時,優先刪除最老的緩存內容。同時,也設計了緩存的有效時長為 2 個星期。

這實際上類似一個經典的 LRU cache(Least Recently Used 最近最少使用)設計。該算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是“如果數據最近被訪問過,那么將來被訪問的幾率也更高”。一般我們通過基于 HashMap 和 雙向鏈表實現 LRU cache。具體內容只做提示,不再展開。

其他考慮點

一個強健有效的緩存系統,尤其是對于 Webpack 這種復雜的構建工具來說,需要考慮的關鍵點仍然有很多。這里我們簡單進行羅列,不求面面俱到,更重要的是讓讀者能夠有一個更加立體的認知:

面向 Webpack plugin 和 loader 開發者的緩存需求

對于 Webpack plugin 和 loader 開發者來說,緩存體系需要實現開箱即用的工具或策略以便完成對緩存的調試和檢驗。同時也需要暴露給 Webpack plugin 和 loader 開發者開關緩存的能力以及時全量緩存失效的能力。

面向普通開發者的緩存需求

對于普通開發者,最核心的需求當然是可以依靠緩存系統完成構建的絕對量級優化。同時需要對未開啟緩存的性能不優化型構建進行提示,且該提示應該是可關閉的。對于緩存的操作,不需要普通開發者手動進行,所有緩存體系的運轉都應該是一個自動流程。

同樣對于開發者,也能夠使用不支持持久化緩存的 Webpack plugin 和 loader。緩存體系的設計和建設,當然不能破壞整個 Webpack 生態體系。

面向 Webpack 開發者

對于 Webpack 官方核心開發者,緩存體系同樣需要提供測試和調試能力。

面向 CI 過程和跨系統

CI/CD(持續集成 Continuous Integration / 持續部署 Continuous Deployment)中涉及到的前端構建始終是一個有趣的話題。對于 Webpack 5 持久化緩存來說,對于 CI/CD 過程以及跨系統場景,也應該有合理的控制和設計。

總體來講,在這個階段的持久化緩存應該“易于攜帶”,我們用 portable 這次詞語來形容這種特性。對不同的 CI 實例,或不同項目在不同系統設備上的 clones 來說,緩存都應該可以被重復利用,跨環境利用,這也是緩存在面向 CI 過程和跨系統當中所表現出來的可移植性。舉個例子,緩存內容應該在不同的 pipeline 階段中,在不同的 CI 實例上都可用;或者從一個公共的中心化的存儲中拉取最新的緩存內容,而不需要在通過第一次全量構建獲取緩存內容。

持久化緩存信息寫入 Webpack stats

熟悉 Webpack 核心體系的讀者應該對于 Webpack stats 并不陌生。Webpack stats 是 Webpack 對于一次構建的統計分析信息,它對于分析 Webpack 構建過程,優化構建方案非常重要。在此信息中,我們也應該加入持久化緩存所關聯的磁盤信息(disk cache information )。比如:編譯緩存 ID,緩存所占用磁盤空間等。

總結

本篇文章沒有貼源碼來具體分析 Webpack 5 持久化緩存實現,而是從設計體系出發,講解 Webpack 現有構建流程和緩存環節。其中涉及到較多 Webpack 核心原理和基本概念,在閱讀過程中讀者可以隨時查漏補缺。緩存體系說起來簡單,但是如何實現的優雅,完成體系化、安全化、工程化等多方面考慮,仍然需要每一個開發者深思。

文章開篇提到了此時嚴峻的疫情形勢,在這個時間節點,我相信未來一切就像阿爾貝?加繆在《鼠疫》中所說:“春天的腳步正從所有偏遠的區域向疫區走來。成千上萬朵玫瑰依舊枯萎在市場和街道兩旁花商的籃子里,但空氣中充溢著它們的香氣”。同時,書中另外一句話也讓我印象深刻:“對未來真正的慷慨,是把一切獻給現在”, 抗擊疫情如此,學習進階道路同樣如此。

關于本文作者:@Lucas HC

原文:https://zhuanlan.zhihu.com/p/110995118

TAGS標簽:  webpack5  優缺點  構建  進程  緩存  webpack5優缺

歡迎分享轉載→http://www.avcorse.com/read-221834.html

Copyright ? 2024 有趣生活 All Rights Reserve吉ICP備19000289號-5 TXT地圖HTML地圖XML地圖