最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊

二十張圖片徹底講明白 Webpack 設(shè)計(jì)理念

2023-02-15 16:57 作者:要寵你上天  | 我要投稿

一、前言

Webpack?一直都是有些人的心魔,不清楚原理是什么,不知道怎么去配置,只會(huì)基本的 API 使用。它就像一個(gè)黑盒,讓部分開發(fā)者對它望而生畏。

而本節(jié)最大的作用,就是幫大家一點(diǎn)一點(diǎn)的消滅心魔。

大家之所以認(rèn)為?Webpack?復(fù)雜,很大程度上是因?yàn)樗栏街惶嫶蟮纳鷳B(tài)系統(tǒng)。其實(shí) Webpack 的核心流程遠(yuǎn)沒有我們想象中那么復(fù)雜,甚至只需百來行代碼就能完整復(fù)刻出來。

因此在學(xué)習(xí)過程中,我們應(yīng)注重學(xué)習(xí)它本身的設(shè)計(jì)思想,不管是它的?Plugin 系統(tǒng)還是?Loader 系統(tǒng),都是建立于這套核心思想之上。所謂萬變不離其宗,一通百通。

在本文中,我將會(huì)從 Webpack 的整體流程出發(fā),通篇采用結(jié)論先行、自頂向下的方式進(jìn)行講解。在涉及到原理性的知識(shí)時(shí),盡量采用圖文的方式輔以理解,注重實(shí)現(xiàn)思路,注重設(shè)計(jì)思想

二、基本使用

初始化項(xiàng)目:

安裝完依賴后,根據(jù)以下目錄結(jié)構(gòu)來添加對應(yīng)的目錄和文件:


webpack.config.js


src/index.js(本文不討論CommonJS 和 ES Module之間的引用關(guān)系,以CommonJS為準(zhǔn)


src/name.js


src/age.js


文件依賴關(guān)系:

Webpack 本質(zhì)上是一個(gè)函數(shù),它接受一個(gè)配置信息作為參數(shù),執(zhí)行后返回一個(gè) compiler 對象,調(diào)用?compiler?對象中的 run 方法就會(huì)啟動(dòng)編譯。run?方法接受一個(gè)回調(diào),可以用來查看編譯過程中的錯(cuò)誤信息或編譯信息。

debugger.js


執(zhí)行打包命令:


得到產(chǎn)出文件?dist/main.js(先暫停三十秒讀一讀下面代碼,命名經(jīng)優(yōu)化):

運(yùn)行該文件,得到結(jié)果:

三、核心思想

我們先來分析一下源代碼和構(gòu)建產(chǎn)物之間的關(guān)系:

從圖中可以看出,入口文件(src/index.js)被包裹在最后的立即執(zhí)行函數(shù)中,而它所依賴的模塊(src/name.js、src/age.js)則被放進(jìn)了?modules?對象中(modules?用于存放入口文件的依賴模塊,key 值為依賴模塊路徑,value 值為依賴模塊源代碼)。

require?函數(shù)是?web 環(huán)境下?加載模塊的方法(?require?原本是?node環(huán)境?中內(nèi)置的方法,瀏覽器并不認(rèn)識(shí)?require,所以這里需要手動(dòng)實(shí)現(xiàn)一下),它接受模塊的路徑為參數(shù),返回模塊導(dǎo)出的內(nèi)容。

要想弄清楚 Webpack 原理,那么核心問題就變成了:如何將左邊的源代碼轉(zhuǎn)換成?dist/main.js?文件?

核心思想:

  • 第一步:首先,根據(jù)配置信息(webpack.config.js)找到入口文件(src/index.js

  • 第二步:找到入口文件所依賴的模塊,并收集關(guān)鍵信息:比如路徑、源代碼、它所依賴的模塊等:



  • 第三步:根據(jù)上一步得到的信息,生成最終輸出到硬盤中的文件(dist):包括 modules 對象、require 模版代碼、入口執(zhí)行文件等

在這過程中,由于瀏覽器并不認(rèn)識(shí)除?html、js、css?以外的文件格式,所以我們還需要對源文件進(jìn)行轉(zhuǎn)換 —— **Loader 系統(tǒng)**。

Loader 系統(tǒng)?本質(zhì)上就是接收資源文件,并對其進(jìn)行轉(zhuǎn)換,最終輸出轉(zhuǎn)換后的文件:

除此之外,打包過程中也有一些特定的時(shí)機(jī)需要處理,比如:

  • 在打包前需要校驗(yàn)用戶傳過來的參數(shù),判斷格式是否符合要求

  • 在打包過程中,需要知道哪些模塊可以忽略編譯,直接引用 cdn 鏈接

  • 在編譯完成后,需要將輸出的內(nèi)容插入到 html 文件中

  • 在輸出到硬盤前,需要先清空 dist 文件夾

  • ......

這個(gè)時(shí)候需要一個(gè)可插拔的設(shè)計(jì),方便給社區(qū)提供可擴(kuò)展的接口 —— **Plugin 系統(tǒng)**。

Plugin 系統(tǒng)?本質(zhì)上就是一種事件流的機(jī)制,到了固定的時(shí)間節(jié)點(diǎn)就廣播特定的事件,用戶可以在事件內(nèi)執(zhí)行特定的邏輯,類似于生命周期:

這些設(shè)計(jì)也都是根據(jù)使用場景來的,只有理清需求后我們才能更好的理解它的設(shè)計(jì)思想。

四、架構(gòu)設(shè)計(jì)

在理清楚核心思想后,剩下的就是對其進(jìn)行一步步拆解。

上面提到,我們需要建立一套事件流的機(jī)制來管控整個(gè)打包過程,大致可以分為三個(gè)階段:

  • 打包開始前的準(zhǔn)備工作

  • 打包過程中(也就是編譯階段)

  • 打包結(jié)束后(包含打包成功和打包失敗)

這其中又以編譯階段最為復(fù)雜,另外還考慮到一個(gè)場景:watch mode[1](當(dāng)文件變化時(shí),將重新進(jìn)行編譯),因此這里最好將編譯階段(也就是下文中的compilation)單獨(dú)解耦出來。

在?Webpack?源碼中,compiler?就像是一個(gè)大管家,它就代表上面說的三個(gè)階段,在它上面掛載著各種生命周期函數(shù),而?compilation?就像專管伙食的廚師,專門負(fù)責(zé)編譯相關(guān)的工作,也就是打包過程中這個(gè)階段。畫個(gè)圖幫助大家理解:

大致架構(gòu)定下后,那現(xiàn)在應(yīng)該如何實(shí)現(xiàn)這套事件流呢?

這時(shí)候就需要借助?Tapable?了!它是一個(gè)類似于 Node.js 中的?EventEmitter?的庫,但更專注于自定義事件的觸發(fā)和處理。通過 Tapable 我們可以注冊自定義事件,然后在適當(dāng)?shù)臅r(shí)機(jī)去執(zhí)行自定義事件。

類比到?Vue?和?React?框架中的生命周期函數(shù),它們就是到了固定的時(shí)間節(jié)點(diǎn)就執(zhí)行對應(yīng)的生命周期,tapable?做的事情就和這個(gè)差不多,我們可以通過它先注冊一系列的生命周期函數(shù),然后在合適的時(shí)間點(diǎn)執(zhí)行。

example ??:


運(yùn)行上面這段代碼,得到結(jié)果:


在 Webpack 中,就是通過?tapable?在?comiler?和?compilation?上像這樣掛載著一系列生命周期 Hook,它就像是一座橋梁,貫穿著整個(gè)構(gòu)建過程:

五、具體實(shí)現(xiàn)

整個(gè)實(shí)現(xiàn)過程大致分為以下步驟:

  • (1)搭建結(jié)構(gòu),讀取配置參數(shù)

  • (2)用配置參數(shù)對象初始化?Compiler?對象

  • (3)掛載配置文件中的插件

  • (4)執(zhí)行?Compiler?對象的?run?方法開始執(zhí)行編譯

  • (5)根據(jù)配置文件中的?entry?配置項(xiàng)找到所有的入口

  • (6)從入口文件出發(fā),調(diào)用配置的?loader?規(guī)則,對各模塊進(jìn)行編譯

  • (7)找出此模塊所依賴的模塊,再對依賴模塊進(jìn)行編譯

  • (8)等所有模塊都編譯完成后,根據(jù)模塊之間的依賴關(guān)系,組裝代碼塊?chunk

  • (9)把各個(gè)代碼塊?chunk?轉(zhuǎn)換成一個(gè)一個(gè)文件加入到輸出列表

  • (10)確定好輸出內(nèi)容之后,根據(jù)配置的輸出路徑和文件名,將文件內(nèi)容寫入到文件系統(tǒng)

5.1、搭建結(jié)構(gòu),讀取配置參數(shù)

根據(jù)?Webpack?的用法可以看出, Webpack 本質(zhì)上是一個(gè)函數(shù),它接受一個(gè)配置信息作為參數(shù),執(zhí)行后返回一個(gè)?compiler?對象,調(diào)用?compiler?對象中的?run?方法就會(huì)啟動(dòng)編譯。run?方法接受一個(gè)回調(diào),可以用來查看編譯過程中的錯(cuò)誤信息或編譯信息。

修改?debugger.js?中 webpack 的引用:


搭建結(jié)構(gòu):



運(yùn)行流程圖:

5.4、執(zhí)行Compiler對象的run方法開始執(zhí)行編譯

重點(diǎn)來了!

在正式開始編譯前,我們需要先調(diào)用?Compiler?中的?run?鉤子,表示開始啟動(dòng)編譯了;在編譯結(jié)束后,需要調(diào)用?done?鉤子,表示編譯完成。


上面架構(gòu)設(shè)計(jì)中提到過,編譯這個(gè)階段需要單獨(dú)解耦出來,通過?Compilation?來完成,定義Compilation?大致結(jié)構(gòu):

運(yùn)行流程圖(點(diǎn)擊可放大):

5.5、根據(jù)配置文件中的entry配置項(xiàng)找到所有的入口

接下來就正式開始編譯了,邏輯均在?Compilation?中。

在編譯前我們首先需要知道入口文件,而?入口的配置方式[2]?有多種,可以配置成字符串,也可以配置成一個(gè)對象,這一步驟就是為了統(tǒng)一配置信息的格式,然后找出所有的入口(考慮多入口打包的場景)。

運(yùn)行流程圖(點(diǎn)擊可放大):

5.6、從入口文件出發(fā),調(diào)用配置的loader規(guī)則,對各模塊進(jìn)行編譯

Loader 本質(zhì)上就是一個(gè)函數(shù),接收資源文件或者上一個(gè) Loader 產(chǎn)生的結(jié)果作為入?yún)ⅲ罱K輸出轉(zhuǎn)換后的結(jié)果。

寫兩個(gè)自定義 Loader 配置到?webpack.config.js?中:

webpack.config.js

這一步驟將從入口文件出發(fā),然后查找出對應(yīng)的 Loader 對源代碼進(jìn)行翻譯和替換。

主要有三個(gè)要點(diǎn):

  • (6.1)把入口文件的絕對路徑添加到依賴數(shù)組(this.fileDependencies)中,記錄此次編譯依賴的模塊

  • (6.2)得到入口模塊的的?module?對象 (里面放著該模塊的路徑、依賴模塊、源代碼等)

    • (6.2.1)讀取模塊內(nèi)容,獲取源代碼

    • (6.2.2)創(chuàng)建模塊對象

    • (6.2.3)找到對應(yīng)的?Loader?對源代碼進(jìn)行翻譯和替換

  • (6.3)將生成的入口文件?module?對象 push 進(jìn)?this.modules?中

6.1:把入口文件的絕對路徑添加到依賴數(shù)組中,記錄此次編譯依賴的模塊

這里因?yàn)橐@取入口文件的絕對路徑,考慮到操作系統(tǒng)的兼容性問題,需要將路徑的?\?都替換成?/

6.2.1:讀取模塊內(nèi)容,獲取源代碼


6.2.2:創(chuàng)建模塊對象


6.2.3:找到對應(yīng)的?Loader?對源代碼進(jìn)行翻譯和替換


6.3:將生成的入口文件?module?對象 push 進(jìn)?this.modules?中


運(yùn)行流程圖(點(diǎn)擊可放大):

5.7、找出此模塊所依賴的模塊,再對依賴模塊進(jìn)行編譯

該步驟是整體流程中最為復(fù)雜的,一遍看不懂沒關(guān)系,可以先理解思路。

該步驟經(jīng)過細(xì)化可以將其拆分成十個(gè)小步驟:

  • (7.1):先把源代碼編譯成?AST[3]

  • (7.2):在?AST?中查找?require?語句,找出依賴的模塊名稱和絕對路徑

  • (7.3):將依賴模塊的絕對路徑 push 到?this.fileDependencies?中

  • (7.4):生成依賴模塊的模塊 id

  • (7.5):修改語法結(jié)構(gòu),把依賴的模塊改為依賴模塊 id

  • (7.6):將依賴模塊的信息 push 到該模塊的?dependencies?屬性中

  • (7.7):生成新代碼,并把轉(zhuǎn)譯后的源代碼放到?module._source?屬性上

  • (7.8):對依賴模塊進(jìn)行編譯(對?module 對象中的?dependencies?進(jìn)行遞歸執(zhí)行?buildModule?)

  • (7.9):對依賴模塊編譯完成后得到依賴模塊的?module 對象,push 到?this.modules?中

  • (7.10):等依賴模塊全部編譯完成后,返回入口模塊的?module?對象

運(yùn)行流程圖(點(diǎn)擊可放大):

5.8、等所有模塊都編譯完成后,根據(jù)模塊之間的依賴關(guān)系,組裝代碼塊?chunk

現(xiàn)在,我們已經(jīng)知道了入口模塊和它所依賴模塊的所有信息,可以去生成對應(yīng)的代碼塊了。

一般來說,每個(gè)入口文件會(huì)對應(yīng)一個(gè)代碼塊chunk,每個(gè)代碼塊chunk里面會(huì)放著本入口模塊和它依賴的模塊,這里暫時(shí)不考慮代碼分割。

運(yùn)行流程圖(點(diǎn)擊可放大):

5.9、把各個(gè)代碼塊?chunk?轉(zhuǎn)換成一個(gè)一個(gè)文件加入到輸出列表

這一步需要結(jié)合配置文件中的output.filename去生成輸出文件的文件名稱,同時(shí)還需要生成運(yùn)行時(shí)代碼:

到了這里,Compilation?的邏輯就走完了。

運(yùn)行流程圖(點(diǎn)擊可放大):

5.10、確定好輸出內(nèi)容之后,根據(jù)配置的輸出路徑和文件名,將文件內(nèi)容寫入到文件系統(tǒng)

該步驟就很簡單了,直接按照 Compilation 中的 this.status 對象將文件內(nèi)容寫入到文件系統(tǒng)(這里就是硬盤)。

運(yùn)行流程圖(點(diǎn)擊可放大):

完整流程圖

以上就是整個(gè) Webpack 的運(yùn)行流程圖,還是描述的比較清晰的,跟著一步步走看懂肯定沒問題!

執(zhí)行?node ./debugger.js,通過我們手寫的 Webpack 進(jìn)行打包,得到輸出文件?dist/main.js

carbon.png

六、實(shí)現(xiàn) watch 模式

看完上面的實(shí)現(xiàn),有些小伙伴可能有疑問了:Compilation?中的?this.fileDependencies(本次打包涉及到的文件)是用來做什么的?為什么沒有地方用到該屬性?

這里其實(shí)是為了實(shí)現(xiàn)?Webpack 的 watch 模式[4]:當(dāng)文件發(fā)生變更時(shí)將重新編譯。

思路:對?this.fileDependencies?里面的文件進(jìn)行監(jiān)聽,當(dāng)文件發(fā)生變化時(shí),重新執(zhí)行?compile?函數(shù)。

相信看到這里,你一定也理解了 compile 和 Compilation 的設(shè)計(jì),都是為了解耦和復(fù)用呀。

七、總結(jié)

本文從 Webpack 的基本使用和構(gòu)建產(chǎn)物出發(fā),從思想和架構(gòu)兩方面深度剖析了 Webpack 的設(shè)計(jì)理念。最后在代碼實(shí)現(xiàn)階段,通過百來行代碼手寫了 Webpack 的整體流程,盡管它只能對文件進(jìn)行打包,還缺少很多功能,但麻雀雖小,卻也五臟俱全。


二十張圖片徹底講明白 Webpack 設(shè)計(jì)理念的評論 (共 條)

分享到微博請遵守國家法律
明溪县| 平潭县| 策勒县| 邓州市| 普兰县| 津南区| 玛纳斯县| 通化市| 宜昌市| 安图县| 和顺县| 南宁市| 鹿泉市| 银川市| 夹江县| 柳河县| 通山县| 兴文县| 阿克| 永顺县| 舞阳县| 康定县| 白水县| 湛江市| 会昌县| 梧州市| 永吉县| 沙湾县| 山东| 桃园市| 昭觉县| 抚宁县| 扶沟县| 昂仁县| 荣成市| 会泽县| 青海省| 博湖县| 莱阳市| 桃源县| 甘洛县|