<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>

          Webpack漫談

          共 7686字,需瀏覽 16分鐘

           ·

          2020-09-10 22:31

          本文使用「署名 4.0 國際 (CC BY 4.0)」 許可協(xié)議,歡迎轉(zhuǎn)載、或重新修改使用,但需要注明來源。

          作者: 百應(yīng)前端團隊 @雙魚

          https://juejin.im/user/307518985745895

          1. 核心概念

          • entry:入口。webpack是基于模塊的,使用webpack首先需要指定模塊解析入口(entry),webpack從入口開始根據(jù)模塊間依賴關(guān)系遞歸解析和處理所有資源文件。
          • output:輸出。源代碼經(jīng)過webpack處理之后的最終產(chǎn)物。
          • loader:模塊轉(zhuǎn)換器。本質(zhì)就是一個函數(shù),在該函數(shù)中對接收到的內(nèi)容進行轉(zhuǎn)換,返回轉(zhuǎn)換后的結(jié)果。因為 Webpack 只認識 JavaScript,所以 Loader 就成了翻譯官,對其他類型的資源進行轉(zhuǎn)譯的預(yù)處理工作。
          • plugin:擴展插件。基于事件流框架 Tapable,插件可以擴展 Webpack 的功能,在 Webpack 運行的生命周期中會廣播出許多事件,Plugin 可以監(jiān)聽這些事件,在合適的時機通過 Webpack 提供的 API 改變輸出結(jié)果。
          • module:模塊。除了js范疇內(nèi)的es module、commonJs、AMD等,css @import、url(...)、圖片、字體等在webpack中都被視為模塊。

          另外webpack4開始 mode 變成一個重要概念,webpack為不同 mode提供了一些默認值,附上阮一峰老師的吐槽

          不同mode的默認配置如下:

          2. 打包流程

          1. 初始化參數(shù):從配置文件和 Shell 語句中讀取與合并參數(shù),得出最終的參數(shù);
          2. 初始化編譯:用上一步得到的參數(shù)初始化 Compiler 對象,注冊插件并傳入 Compiler 實例(掛載了眾多webpack事件api供插件調(diào)用);
          3. AST & 依賴圖:從入口文件(entry)出發(fā),調(diào)用AST引擎(acorn)生成抽象語法樹AST,根據(jù)AST構(gòu)建模塊的所有依賴;
          4. 遞歸編譯模塊:調(diào)用所有配置的 Loader 對模塊進行編譯;
          5. 輸出資源:根據(jù)入口和模塊之間的依賴關(guān)系,組裝成一個個包含多個模塊的 Chunk,再把每個 Chunk 轉(zhuǎn)換成一個單獨的文件加入到輸出列表,這步是可以修改輸出內(nèi)容的最后機會;
          6. 輸出完成:在確定好輸出內(nèi)容后,根據(jù)配置確定輸出的路徑和文件名,把文件內(nèi)容寫入到文件系統(tǒng);

          在以上過程中,Webpack 會在特定的時間點廣播出特定的事件,插件在監(jiān)聽到相關(guān)事件后會執(zhí)行特定的邏輯,并且插件可以調(diào)用 Webpack 提供的 API 改變 Webpack 的運行結(jié)果

          構(gòu)建流程核心概念:
          • Tapable:一個基于發(fā)布訂閱的事件流工具類,Compiler 和 Compilation 對象都繼承于 Tapable
          • Compiler:webpack編譯貫穿始終的核心對象,在編譯初始化階段被創(chuàng)建的全局單例,包含完整配置信息、loaders、plugins以及各種工具方法
          • Compilation:代表一次 webpack 構(gòu)建和生成編譯資源的的過程,在watch模式下每一次文件變更觸發(fā)的重新編譯都會生成新的 Compilation 對象,包含了當(dāng)前編譯的模塊 module, 編譯生成的資源,變化的文件, 依賴的狀態(tài)等

          更加細化的構(gòu)建流程圖:

          看大圖點這里?

          流程圖出處:淘系前端團隊-細說 webpack 之流程篇

          3. Loader

          loader就像一個翻譯官,將源文件經(jīng)過轉(zhuǎn)換后生成目標(biāo)文件并交由下一流程處理

          使用方法
          • 每個loader職責(zé)都是單一的,就像流水線上的工人
          • 順序很關(guān)鍵(從右往左)
          實現(xiàn)準(zhǔn)則
          • 簡單【Simple】loader只做單一任務(wù),多個loader > 一個多功能loader
          • 鏈?zhǔn)健綜haining】遵循鏈?zhǔn)秸{(diào)用原則
          • 無狀態(tài)【Stateless】即函數(shù)式里的Pure Function,無副作用
          • 使用工具庫【Loader Utilities】充分利用 loader-utils 包
          實現(xiàn)一個簡單的loader,功能是替換console.log、去除換行符、在文件結(jié)尾處增加一行自定義內(nèi)容
          /**?webpack.config.js?*/??
          ??
          const?path?=?require("path");??
          ??
          module.exports?=?{??
          ??entry:?{??
          ????index:?path.resolve(__dirname,?"src/index.js"),??
          ??},??
          ??output:?{??
          ????path:?path.resolve(__dirname,?"dist"),??
          ??},??
          ??module:?{??
          ????rules:?[??
          ??????{??
          ????????test:?/\.js$/,??
          ????????use:?[??
          ??????????{??
          ????????????loader:?path.resolve("lib/loader/loader1.js"),??
          ????????????options:?{??
          ??????????????message:?"this?is?a?message",??
          ????????????}??
          ??????????}??
          ????????],??
          ??????},??
          ????],??
          ??},??
          };??
          /**?lib/loader/loader1.js?*/??
          ??
          const?loaderUtils?=?require('loader-utils');??
          ??
          /**?過濾console.log和換行符?*/??
          module.exports?=?function?(source)?{??
          ??
          ??//?獲取loader配置項??
          ??const?options?=?loaderUtils.getOptions(this);??
          ??
          ??console.log('loader配置項:',?options);??
          ??
          ??const?result?=?source??
          ????.replace(/console.log\(.*\);?/g,?"")??
          ????.replace(/\n/g,?"")??
          ????.concat(`console.log("${options.message?||?'沒有配置項'}");`);??
          ??
          ??return?result;??
          };??
          ??
          異步loader如何編寫
          /**?lib/loader/loader1.js?*/??
          ??
          /**?異步loader?*/??
          module.exports?=?function?(source)?{??
          ??
          ??let?count?=?1;??
          ??
          ??//?1.調(diào)用this.async()?告訴webpack這是一個異步loader,需要等待?asyncCallback?回調(diào)之后再進行下一個loader處理??
          ??//?2.this.async?返回異步回調(diào),調(diào)用表示異步loader處理結(jié)束??
          ??const?asyncCallback?=?this.async();??
          ??
          ??const?timer?=?setInterval(()?=>?{??
          ????console.log(`時間已經(jīng)過去${count++}秒`);??
          ??},?1000);??
          ??
          ??//?異步操作??
          ??setTimeout(()?=>?{??
          ????clearInterval(timer);??
          ????asyncCallback(null,?source);??
          ??},?3200);??
          ??
          };??
          ??
          ??

          4. Plugin

          在webpack編譯整個生命周期的特定節(jié)點執(zhí)行特定功能

          實現(xiàn)要點:
          • 一個命名JS函數(shù)或者JS類
          • 在prototype上定義一個apply方法(供webpack調(diào)用,并且在調(diào)用時注入 compiler 對象)
          • 在 apply 函數(shù)中需要有通過 compiler 對象掛載的 webpack 事件鉤子(鉤子函數(shù)中能拿到當(dāng)前編譯的 compilation 對象)
          • 處理 webpack 內(nèi)部實例的特定數(shù)據(jù)
          • 功能完成后調(diào)用 webpack 提供的回調(diào)
          基本模型:
          //?1、Plugin名稱??
          const?MY_PLUGIN_NAME?=?"MyBasicPlugin";??
          ??
          class?MyBasicPlugin?{??
          ??//?2、在構(gòu)造函數(shù)中獲取插件配置項??
          ??constructor(option)?{??
          ????this.option?=?option;??
          ??}??
          ??
          ??//?3、在原型對象上定義一個apply函數(shù)供webpack調(diào)用??
          ??apply(compiler)?{??
          ????//?4、注冊webpack事件監(jiān)聽函數(shù)??
          ????compiler.hooks.emit.tapAsync(??
          ??????MY_PLUGIN_NAME,??
          ??????(compilation,?asyncCallback)?=>?{??
          ??
          ????????//?5、操作Or改變compilation內(nèi)部數(shù)據(jù)??
          ????????console.log(compilation);????????
          ??
          ????????console.log("當(dāng)前階段?======>?編譯完成,即將輸出到output目錄");??
          ??
          ????????//?6、如果是異步鉤子,結(jié)束后需要執(zhí)行異步回調(diào)??
          ????????asyncCallback();??
          ??????}??
          ????);??
          ??}??
          }??
          ??
          //?7、模塊導(dǎo)出??
          module.exports?=?MyBasicPlugin;??
          實現(xiàn)一個plugin,功能是在dist目錄自動生成README文件:
          const?MY_PLUGIN_NAME?=?"MyReadMePlugin";??
          ??
          //?插件功能:自動生成README文件,標(biāo)題取自插件option ?
          class?MyReadMePlugin?{??
          ??
          ??constructor(option)?{??
          ????this.option?=?option?||?{};??
          ??}??
          ??
          ??apply(compiler)?{??
          ????compiler.hooks.emit.tapAsync(??
          ??????MY_PLUGIN_NAME,??
          ??????(compilation,?asyncCallback)?=>?{??
          ????????compilation.assets["README.md"]?=?{??
          ??????????//?文件內(nèi)容??
          ??????????source:?()?=>?{??
          ????????????return?`#?${this.option.title?||?'默認標(biāo)題'}`;??
          ??????????},??
          ??????????//?文件大小??
          ??????????size:?()?=>?30,??
          ????????};??
          ????????asyncCallback();??
          ??????}??
          ????);??
          ??}??
          }??
          ??
          //?7、模塊導(dǎo)出??
          module.exports?=?MyReadMePlugin;??
          ??

          compiler.hooks 上掛載了不同時期觸發(fā)的webpack事件函數(shù)(類似于React生命周期),可以在編譯的各個階段執(zhí)行其他邏輯或者改變輸出結(jié)果,具體支持的事件列表可以看這里:compiler-hooks

          Tapable:

          webpack 的插件架構(gòu)主要基于 Tapable 實現(xiàn)的,Tapable 是 webpack 項目組的一個內(nèi)部庫,主要是抽象了一套插件機制。它類似于 NodeJS 的 EventEmitter 類,專注于自定義事件的觸發(fā)和操作。

          Tapable事件類型分為同步和異步,內(nèi)部又以不同的規(guī)則分為不同類型,上述事件的具體區(qū)別可以看 這篇文章,理解這些事件的區(qū)別和應(yīng)用場景有助于我們理解webpack源碼和編寫Plugin

          Complier對象:

          在webpack啟動時被初始化一次,全局唯一,可以理解為webpack編譯實例,它包含了webpack原始配置、Loader、Plugin引用、各種鉤子

          部分源碼:https://github.com/webpack/webpack/blob/10282ea20648b465caec6448849f24fc34e1ba3e/lib/webpack.js

          5. 性能優(yōu)化

          1. 從何開始?
          • 使用 speed-measure-webpack-plugin 測量打包速度

          • 使用 webpack-bundle-analyzer 進行體積分析

            從某項目的分析圖可以看出一個很明顯的優(yōu)化空間就是 BizCharts 沒有按需引入,這時候我們可以import路徑再執(zhí)行一次打包分析看效果。

            另外圖中每個模塊都有三種Size,分別是 Stat Size、Parsed Size、Gzipped Size,這三者的分別代表什么含義可以看下插件的github issue

          2. 優(yōu)化Loader配置

          思路主要是優(yōu)化搜索時間、縮小文件搜索范圍、減少不必要的編譯工作,具體做法可以看以下配置文件

          module?.exports?=?{???
          ??module?:?{???
          ????rules?:?[{??
          ??????//?如果項目源碼中只有?文件,就不要寫成/\jsx?$/,以提升正則表達式的性能??
          ??????test:?/\.js$/,???
          ??????//?babel-loader?支持緩存轉(zhuǎn)換出的結(jié)果,通過?cacheDirectory?選項開啟??
          ??????use:?['babel-loader?cacheDirectory']?,???
          ??????//?只對項目根目錄下?src?目錄中的文件采用?babel-loader??
          ??????include:?path.resolve(__dirname,'src'),??
          ??????//?使用resolve.alias把原導(dǎo)入路徑映射成一個新的導(dǎo)入路徑,減少耗時的遞歸解析操作??
          ??????alias:?{??
          ????????'react':?path.resolve(?__dirname?,'./node_modules/react/dist/react.min.js'),??
          ??????},??
          ??????//?讓?Webpack?忽略對部分沒采用模塊化的文件的遞歸解析處理??
          ??????noParse:?'/jquery|lodash/',??
          ????}],??
          ??}??
          }??
          3. DLL Plugin Or Externals

          合理使用DLLPlugin將更改頻率較低的代碼(三方庫)移到單獨的編譯中,我理解大部分場景下和配置 externals 作用是差不多的(都不用打包三方庫),但是 externals 在某些場景下會存在失效問題,具體可以看 這篇文章,另外 DLLPlugin 具體使用 參考這里

          4. 多進程系列

          多進程陣營里有幾位知名選手:

          • thread-loader(v4以后的官方推薦)
          • happypack(不怎么維護了)
          • parallel-webpack(不怎么維護了)

          這里只介紹一下 thread-loader ,使用 thread-loader 將開銷較大的 loader(例如babel-loader)放到獨立進程中(官方描述 worker pool)處理,使用上有以下注意事項

          • 將其放在需要單獨加載的loader的前面,順序很關(guān)鍵
          module.exports?=?{??
          ??module:?{??
          ????rules:?[??
          ??????{??
          ????????test:?/\.js$/,??
          ????????include:?path.resolve("src"),??
          ????????use:?[??
          ??????????"thread-loader",??
          ??????????//?your?expensive?loader?(e.g?babel-loader)??
          ????????]??
          ??????}??
          ????]??
          ??}??
          }??
          • worker pool中的loader使用上是有限制的,例如無法使用自定義 loader api,無法獲取webpack 配置項
          5. 合理利用緩存 縮短非首次構(gòu)建時間

          目前項目在用的插件是 hard-source-webpack-plugin,效果較為顯著,不過缺點有3

          1. 生成的緩存文件較大,比較占用磁盤空間(之前還出現(xiàn)過發(fā)布的時候誤把緩存文件上傳到服務(wù)器導(dǎo)致發(fā)布特別慢的情況 =。=,所以最好還是指定緩存文件路徑為 node_modules 內(nèi)部)
          2. 這個倉庫也很久沒更新了
          3. 現(xiàn)有項目偶爾會出現(xiàn)更改代碼不觸發(fā)重新編譯的情況,猜測可能與此插件有關(guān)

          另外 webpack5 是否有自帶的緩存策略或者官方維護的緩存插件還需要去了解一下

          6. 代碼壓縮 減少產(chǎn)物體積
          • webpack3配置optimization.minimize = true會默認啟用 UglifyJsPlugin,其多進程版本為 ParallelUglifyPlugin
          • webpack4 中 webpack.optimize.UglifyJsPlugin 已被廢棄,默認內(nèi)置使用 terser-webpack-plugin 插件壓縮優(yōu)化代碼,原生支持多進程(這里想起官方文檔 Build Performance 章節(jié)中列舉的優(yōu)化措施第一點:Stay Up to Date,最香的還是最新的webpack版本)
          7. Code Splitting

          官方文檔描述的code splitting的3種姿勢:

          1. 多entry配置(多entry是天然的code splitting,但是基本沒人會因為性能優(yōu)化的點去把一個單頁應(yīng)用改成多entry)

          2. 使用 SplitChunksPlugin 進行重復(fù)數(shù)據(jù)刪除和提取

          3. 使用 Dynamic Import 指定模塊拆分,并且可以結(jié)合 preload、prefetch做更多用戶體驗上的優(yōu)化

          6. 想的更遠:那些值得深究的問題?

          • HMR的原理?
          • Tree shaking原理,為什么需要es module的寫法?
          • webpack5的Module Federation有哪些優(yōu)勢,在與http2.0的結(jié)合上有哪些有趣的事情,在微前端上的應(yīng)用?
          • 為什么說rollup比webpack更適合打包組件庫?

          》》面試官都在用的題庫,快來看看《《

          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  色老板免费精品视频 | 黑人把逼操出水的视频了 | 亚洲无码 国产精品 豆花 | 中国男女一级黄色操逼在线视频 | 思思热视频|