1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Webpack 核心知識有哪些?

        共 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ì)就是一個函數(shù),在該函數(shù)中對接收到的內(nèi)容進(jìn)行轉(zhuǎn)換,返回轉(zhuǎn)換后的結(jié)果。因?yàn)?Webpack 只認(rèn)識 JavaScript,所以 Loader 就成了翻譯官,對其他類型的資源進(jìn)行轉(zhuǎn)譯的預(yù)處理工作。
        • plugin:擴(kuò)展插件?;谑录骺蚣?Tapable,插件可以擴(kuò)展 Webpack 的功能,在 Webpack 運(yùn)行的生命周期中會廣播出許多事件,Plugin 可以監(jiān)聽這些事件,在合適的時機(jī)通過 Webpack 提供的 API 改變輸出結(jié)果。
        • module:模塊。除了js范疇內(nèi)的es module、commonJs、AMD等,css @import、url(...)、圖片、字體等在webpack中都被視為模塊。

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

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

        2. 打包流程

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

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

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

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

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

        3. Loader

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

        使用方法
        • 每個loader職責(zé)都是單一的,就像流水線上的工人
        • 順序很關(guān)鍵(從右往左)
        實(shí)現(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 包
        實(shí)現(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配置項(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這是一個異步loader,需要等待?asyncCallback?回調(diào)之后再進(jìn)行下一個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é)點(diǎn)執(zhí)行特定功能

        實(shí)現(xiàn)要點(diǎn):
        • 一個命名JS函數(shù)或者JS類
        • 在prototype上定義一個apply方法(供webpack調(diào)用,并且在調(diào)用時注入 compiler 對象)
        • 在 apply 函數(shù)中需要有通過 compiler 對象掛載的 webpack 事件鉤子(鉤子函數(shù)中能拿到當(dāng)前編譯的 compilation 對象)
        • 處理 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、在原型對象上定義一個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;??
        實(shí)現(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?||?'默認(rèn)標(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 實(shí)現(xiàn)的,Tapable 是 webpack 項(xiàng)目組的一個內(nèi)部庫,主要是抽象了一套插件機(jī)制。它類似于 NodeJS 的 EventEmitter 類,專注于自定義事件的觸發(fā)和操作。

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

        Complier對象:

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

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

        5. 性能優(yōu)化

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

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

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

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

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

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

        module?.exports?=?{???
        ??module?:?{???
        ????rules?:?[{??
        ??????//?如果項(xiàng)目源碼中只有?文件,就不要寫成/\jsx?$/,以提升正則表達(dá)式的性能??
        ??????test:?/\.js$/,???
        ??????//?babel-loader?支持緩存轉(zhuǎn)換出的結(jié)果,通過?cacheDirectory?選項(xiàng)開啟??
        ??????use:?['babel-loader?cacheDirectory']?,???
        ??????//?只對項(xiàng)目根目錄下?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將更改頻率較低的代碼(三方庫)移到單獨(dú)的編譯中,我理解大部分場景下和配置 externals 作用是差不多的(都不用打包三方庫),但是 externals 在某些場景下會存在失效問題,具體可以看 這篇文章,另外 DLLPlugin 具體使用 參考這里

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

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

        • 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)建時間

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

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

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

        6. 代碼壓縮 減少產(chǎn)物體積
        • webpack3配置optimization.minimize = true會默認(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種姿勢:

        1. 多entry配置(多entry是天然的code splitting,但是基本沒人會因?yàn)樾阅軆?yōu)化的點(diǎn)去把一個單頁應(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)勢,在與http2.0的結(jié)合上有哪些有趣的事情,在微前端上的應(yīng)用?
        • 為什么說rollup比webpack更適合打包組件庫?
        瀏覽 49
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            爱草在线 | 综合色综合色 | av下页| 扒开双腿疯狂进出爽爽爽视频 | 成年人性爱网站 | 朱竹清到爽高潮痉挛 | 豆花视频 | 18成人毛片 | 天天色五月天 | 色人阁av |