1. Webpack 性能系列: 使用 Cache 提升構建性能

        共 4901字,需瀏覽 10分鐘

         ·

        2021-10-15 00:56

        不知不覺,Webpack 原理系列已經陸續(xù)出了十篇文章,以構建主流程為綱逐步遞進到插件、Loader、模塊、運行時、Chunk、依賴對象、模塊依賴圖等關鍵概念的含義與運行原理,再到 HMR、Tree-Shaking 等特性的功能介紹和原理解析,滿滿當當十篇文章,合計超過 5W 字,基本上已經貫徹 Webpack 整個核心流程。

        所有文章均已收錄在我的公眾號【Tecvan】,歡迎關注取閱。

        接下來我會繼續(xù)沿著 Webpack 這個少人問津的方向,推出兩個實用性更強的系列:基礎應用、性能優(yōu)化。性能優(yōu)化系列主要介紹在 Webpack 場景下如何通過配置、插件等手段,優(yōu)化構建與運行性能,以及這些性能優(yōu)化背后的核心原理,例如本文即將介紹的 Webpack5 全新的 cache 功能。

        使用持久化緩存

        經過這么多年發(fā)展,Webpack 生態(tài)在前端工程化能力方面已經發(fā)展的非常全面且強大,但大而全的背后其運行性能卻逐漸為行業(yè)詬病,后進如 Vite、SnowPack 等以性能著稱的同類框架更是在業(yè)內掀起不小波瀾。為此,Webpack 終于在第 5 個大版本引入持久化緩存,提升運行性能。

        持久化緩存算得上是 Webpack 5 最令人振奮的特性之一,它能夠將首次構建結果持久化到本地文件系統(tǒng),在下次執(zhí)行構建時跳過一系列解析、鏈接、編譯等非常消耗性能的操作,直接復用 module、chunk 的構建結果。

        使用持久化緩存后,構建性能有巨大提升!以 Three.js 為例,該項目包含 362 份 JS 文件,合計約 3w 行代碼,算得上中大型項目:

        配置 babel-loadereslint-loader 后,在我機器上測試,未使用 cache 特性時構建耗時大約在 11000ms 到 18000ms 之間;啟動 cache 功能后第二次構建耗時降低到 500ms 到 800ms 之間,兩者相差接近 「50」 倍!

        而這接近 50 倍的性能提升,僅僅需要在 Webpack5 場景下設置 cache.type = 'filesystem' 即可開啟:

        module.exports?=?{
        ????//...
        ????cache:?{
        ????????type:?'filesystem'
        ????},
        ????//...
        };

        原理

        那么,為什么開啟持久化緩存之后構建性能會有如此巨大的提升呢?一言蔽之,Webpack5 會將首次構建出的 Module、Chunk、ModuleGraph 等對象序列化后保存到硬盤中,后面再運行的時候就可以跳過一些耗時的編譯動作,直接復用緩存信息。

        構建流程

        在《Webpack 原理系列》中,我們已經深入聊了很多關于 Webpack 構建功能的運行流程與實現細節(jié)的內容,為了加深對緩存的理解,這里有必要從構建性能角度簡單回顧一下。

        Webpack 的構建過程大致上可劃分為三個階段:

        • 初始化,主要是根據配置信息設置內置的各類插件
        • Make - 構建階段,從 entry 模塊開始,執(zhí)行:
          • 讀入文件內容
          • 調用 Loader 轉譯文件內容
          • 調用 acorn 生成 AST 結構
          • 分析 AST,確定模塊依賴列表
          • 遍歷模塊依賴列表,對每一個依賴模塊重新執(zhí)行上述流程,直到生成完整的模塊依賴圖 —— ModuleGraph 對象
        • Seal - 生成階段,過程:
          • 代碼轉譯,如 import 轉換為 require 調用
          • 分析運行時依賴
          • 遍歷模塊依賴圖,對每一個模塊執(zhí)行:
          • 合并模塊代碼與運行時代碼,生成 chunk
          • 執(zhí)行產物優(yōu)化操作,如 Tree-shaking
          • 將最終結果寫出到產物文件

        過程中存在許多 CPU 密集型操作,例如調用 Loader 鏈加載文件時,遇到 babel-loader、eslint-loader、ts-loader 等工具時可能需要重復生成 AST;分析模塊依賴信息時則需要遍歷 AST,執(zhí)行大量運算;Seal 階段也同樣存在大量 AST 遍歷,以及代碼轉換、優(yōu)化操作,等等。

        實現緩存

        在引入持久化緩存之前,Webpack 在每次運行時都需要對所有模塊完整執(zhí)行上述構建流程,假設業(yè)務項目中有 1000 個文件,則每次執(zhí)行 npx webpack 命令時都需要從 0 開始執(zhí)行 1000 次構建、生成邏輯。

        而 Webpack5 的持久化緩存功能則嘗試將構建結果保存到文件系統(tǒng)中,在下次編譯時對比每一個文件的內容哈?;驎r間戳,未發(fā)生變化的文件跳過編譯操作,直接使用緩存副本,減少重復計算;發(fā)生變更的模塊則重新執(zhí)行編譯流程。緩存執(zhí)行時機如下圖:

        如圖,Webpack 在首次構建完畢后將 Module、Chunk、ModuleGraph 三類對象的狀態(tài)序列化并記錄到緩存文件中;在下次構建開始時,嘗試讀入并恢復這些對象的狀態(tài),從而跳過執(zhí)行 Loader 鏈、解析 AST、解析依賴等耗時操作,提升編譯性能。

        用法詳解

        理解緩存的核心原理后,我們再回過頭來看看 cache 提供的配置項列表,下面摘錄幾個比較常用的配置項:

        官方文檔:https://webpack.js.org/configuration/cache
        • cache.type:緩存類型,支持 'memory' | 'filesystem',需要設置 filesystem 才能開啟持久緩存
        • cache.cacheDirectory:緩存文件存放的路徑,默認為 node_modules/.cache/webpack
        • cache.buildDependencies:額外的依賴文件,當這些文件內容發(fā)生變化時,緩存會完全失效而執(zhí)行完整的編譯構建,通??稍O置為項目配置文件,如:
        module.exports?=?{
        ??cache:?{
        ????buildDependencies:?{
        ??????config:?[path.join(__dirname,?'webpack.dll_config.js')],
        ????},
        ??},
        };
        • cache.managedPaths:受控目錄,Webpack 構建時會跳過新舊代碼哈希值與時間戳的對比,直接使用緩存副本,默認值為 ['./node_modules']
        • cache.profile:是否輸出緩存處理過程的詳細日志,默認為 false
        • cache.maxAge:緩存失效時間,默認值為 5184000000

        使用時通常關注上述配置項即可,其它如 idleTimeout、idleTimeoutAfterLargeChanges 等項均與 Webpack 內部實現算法有關,與緩存效果關系不大,無需關注。

        Webpack 4 中的緩存

        實際上,Webpack 4 已經內置使用內存實現的臨時緩存功能,但必須在 watch 模式下使用,進程退出后立即失效,實用性不高。不過,在 Webpack 4 及之前版本中可以使用一些 loader 自帶的緩存功能提升構建性能,例如 babel-loader、eslint-loadercache-loader 。

        開啟 babel-loader 緩存

        只需設置 cacheDirectory = true 即可開啟 babel-loader 持久化緩存功能,例如:

        module.exports?=?{
        ????//?...
        ????module:?{
        ????????rules:?[{
        ????????????test:?/\.m?js$/,
        ????????????loader:?'babel-loader',
        ????????????options:?{
        ????????????????cacheDirectory:?true,
        ????????????},
        ????????}]
        ????},
        ????//?...
        };
        配置項說明:https://github.com/babel/babel-loader#options

        以 Three.js 為例,開啟緩存后生產環(huán)境構建耗時從 3500ms 降低到 1600ms;開發(fā)環(huán)境構建從 6400ms 降低到 4500ms,性能提升約 30% ~ 50% 。

        默認情況下,babel-loader 會將緩存內容保存到 node_modules/.cache/babel-loader 目錄,用戶也可以通過 cacheDirectory = 'dir' 方式設置緩存路徑。

        開啟 eslint-loader 緩存

        eslint-loader 同樣支持緩存功能,只需設置 cache = true 即可開啟,如:

        module.exports?=?{
        ????//?...
        ????module:?{
        ????????rules:?[{
        ????????????test:?/\.js$/,
        ????????????exclude:?/node_modules/,
        ????????????loader:?'eslint-loader',
        ????????????options:?{
        ????????????????cache:?true,
        ????????????},
        ????????},?]
        ????},
        ????//?...
        };
        配置項說明:https://github.com/webpack-contrib/eslint-loader#cache

        依然以 Three.js 為例,開啟緩存后生產環(huán)境構建耗時從 6400ms 降低到 1400ms;開發(fā)環(huán)境構建從 7000ms 降低到 2100ms,性能提升達到 70% ~ 80%。

        默認情況下,babel-loader 會將緩存內容保存到 ./node_modules/.cache/eslint-loader 目錄,用戶也可以通過 cache = 'dir' 方式設置緩存路徑。

        使用 cache-loader

        babel-loadereslint-loader 這類特化 loader 自身攜帶的緩存功能外,Webpack 4 中還可以使用 cache-loader 實現與 Webpack 5 相似的通用持久化緩存功能,使用上只需將 cache-loader 配置在 loader 數組首位,例如:

        const?path?=?require("path");
        const?webpack?=?require("webpack");

        module.exports?=?{
        ????//?...
        ????module:?{
        ????????rules:?[{
        ????????????test:?/\.js$/,
        ????????????use:?['cache-loader',?'babel-loader',?'eslint-loader']
        ????????}]
        ????},
        ????//?...
        };
        cache-loader 文檔:https://www.npmjs.com/package/cache-loader

        使用 cache-loader 后,生產環(huán)境構建耗時從 10602ms 降低到 1540ms;開發(fā)環(huán)境構建從 11130ms 降低到 4247ms,性能提升約 「60% ~ 80%」。

        與 Webpack 5 自帶的持久化緩存不同,cache-loader 僅 Loader 執(zhí)行結果有效,緩存范圍與深度不如內置的緩存功能,所以性能收益相對較低,但在 Webpack 4 版本下已經不失為一種簡單而有效的性能優(yōu)化手段。

        總結

        網絡上關于 Webpack 持久化緩存的討論特別少,但這確實是 Webpack 5 引入的一個特別讓人振奮的功能,甚至在某些情況下能夠讓構建性能達到 Unbundle 方案的量級,相信隨著 Webpack 5 的推廣覆蓋,持久化緩存會成為 Webpack 性能優(yōu)化的一大利器。



        瀏覽 44
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 激情五月中文字幕 | 强乱中文字幕av一区乱码 | 乱辈亂倫大雞巴亂交 | 人人爽人人操 | 久久夜色精品国产欧美乱 |