<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 核心知識(shí)有哪些?

          共 7614字,需瀏覽 16分鐘

           ·

          2020-12-24 08:21

          作者: 百應(yīng)前端團(tuán)隊(duì) @雙魚

          https://juejin.im/user/307518985745895


          1. 核心概念

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

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

          不同mode的默認(rèn)配置如下:

          2. 打包流程

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

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

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

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

          看大圖點(diǎn)這里?

          流程圖出處:淘系前端團(tuán)隊(duì)-細(xì)說 webpack 之流程篇

          3. Loader

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

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

          4. Plugin

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

          實(shí)現(xiàn)要點(diǎn):
          • 一個(gè)命名JS函數(shù)或者JS類
          • 在prototype上定義一個(gè)apply方法(供webpack調(diào)用,并且在調(diào)用時(shí)注入 compiler 對(duì)象)
          • 在 apply 函數(shù)中需要有通過 compiler 對(duì)象掛載的 webpack 事件鉤子(鉤子函數(shù)中能拿到當(dāng)前編譯的 compilation 對(duì)象)
          • 處理 webpack 內(nèi)部實(shí)例的特定數(shù)據(jù)
          • 功能完成后調(diào)用 webpack 提供的回調(diào)
          基本模型:
          //?1、Plugin名稱??
          const?MY_PLUGIN_NAME?=?"MyBasicPlugin";??
          ??
          class?MyBasicPlugin?{??
          ??//?2、在構(gòu)造函數(shù)中獲取插件配置項(xiàng)??
          ??constructor(option)?{??
          ????this.option?=?option;??
          ??}??
          ??
          ??//?3、在原型對(duì)象上定義一個(gè)apply函數(shù)供webpack調(diào)用??
          ??apply(compiler)?{??
          ????//?4、注冊(cè)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;??
          實(shí)現(xiàn)一個(gè)plugin,功能是在dist目錄自動(dòng)生成README文件:
          const?MY_PLUGIN_NAME?=?"MyReadMePlugin";??
          ??
          //?插件功能:自動(dòng)生成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?||?'默認(rèn)標(biāo)題'}`;??
          ??????????},??
          ??????????//?文件大小??
          ??????????size:?()?=>?30,??
          ????????};??
          ????????asyncCallback();??
          ??????}??
          ????);??
          ??}??
          }??
          ??
          //?7、模塊導(dǎo)出??
          module.exports?=?MyReadMePlugin;??
          ??

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

          Tapable:

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

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

          Complier對(duì)象:

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

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

          5. 性能優(yōu)化

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

          • 使用 webpack-bundle-analyzer 進(jìn)行體積分析

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

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

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

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

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

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

          4. 多進(jìn)程系列

          多進(jìn)程陣營(yíng)里有幾位知名選手:

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

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

          • 將其放在需要單獨(dú)加載的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 配置項(xiàng)
          5. 合理利用緩存 縮短非首次構(gòu)建時(shí)間

          目前項(xiàng)目在用的插件是?hard-source-webpack-plugin,效果較為顯著,不過缺點(diǎn)有3

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

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

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

          官方文檔描述的code splitting的3種姿勢(shì):

          1. 多entry配置(多entry是天然的code splitting,但是基本沒人會(huì)因?yàn)樾阅軆?yōu)化的點(diǎn)去把一個(gè)單頁應(yīng)用改成多entry)

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

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

          6. 想的更遠(yuǎn):那些值得深究的問題?

          • HMR的原理?
          • Tree shaking原理,為什么需要es module的寫法?
          • webpack5的Module Federation有哪些優(yōu)勢(shì),在與http2.0的結(jié)合上有哪些有趣的事情,在微前端上的應(yīng)用?
          • 為什么說rollup比webpack更適合打包組件庫(kù)?
          瀏覽 49
          點(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>
                  亚洲成人电影AV | 国产视频1区 | 欧美色址 | 日本熟女性爱视频 | 怡红院成人在线 |