<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圖解指南(模塊化部分)

          共 7090字,需瀏覽 15分鐘

           ·

          2021-02-20 13:25

          在前面一篇文章中《模塊化系列》徹底理清 AMD,CommonJS,CDM,UMD,ES6?我們可以學(xué)到了各種模塊化的機(jī)制。那么接下里我們就來分析一下 webpack 的模塊化機(jī)制。(主要講 JS 部分)

          提到 webpack,可以說是與我們的開發(fā)工程非常密切的工具,不管是日常開發(fā)、進(jìn)行面試還是對(duì)于自我的提高,都離不開它,因?yàn)樗o我們的開發(fā)帶了極大的便利以及學(xué)習(xí)的價(jià)值。但是由于webpack是一個(gè)非常龐大的工程體系,使得我們望之卻步。本文想以這種圖解的形式能夠?qū)⑺貏冮_一層一層復(fù)雜的面紗,最終露出它的真面目。以下是我列出的關(guān)于 webpack 相關(guān)的體系。

          webpack-2

          本文講的是 ?打包 - CommonJS ?模塊,主要分為兩個(gè)部分

          • webpack 的作用
          • webpack 的模塊化機(jī)制與實(shí)現(xiàn)

          webpack 的作用

          在我們前端多樣化的今天,很多工具為了滿足我們?nèi)找嬖鲩L的開發(fā)需求,都變得非常的龐大,例如 webpack 。在我們的印象中,它似乎集成了所有關(guān)于開發(fā)的功能,模塊打包,代碼降級(jí),文件優(yōu)化,代碼校驗(yàn)等等。正是因?yàn)槊鎸?duì)如此龐大的一個(gè)工具,所以才讓我們望而卻步,當(dāng)然了還有一點(diǎn)就是,webpack 的頻繁升級(jí),周邊的生態(tài)插件配套版本混亂,也加劇我們對(duì)它的恐懼。

          那么我們是不是應(yīng)該思考一下,webpack 的出現(xiàn)究竟給我們帶來了什么?我們?yōu)樯缎枰盟慷厦嫠械囊恍┐a降級(jí)(babel轉(zhuǎn)化)、編譯SCSS 、代碼規(guī)范檢測都是得益于它的插件系統(tǒng)和loader機(jī)制,并不是完完全全屬于它。

          所以在我看來,它的功能核心是「打包」,而打包則是能夠讓模塊化的規(guī)范得以在瀏覽器直接執(zhí)行。因此我們來看看打包后所帶來的功能:

          • 模塊隔離
          • 模塊依賴加載

          模塊隔離

          如果我們不用打包的方式,我們所有的模塊都是直接暴露在全局,也就是掛載在 window/global ?這個(gè)對(duì)象。也許代碼量少的時(shí)候還可以接受,不會(huì)有那么多的問題。特別是在代碼增多,多人協(xié)作的情況下,給全局空間帶來的影響是不可預(yù)估的,如果你的每一次開發(fā)都得去一遍一遍查找是否有他們使用當(dāng)前的變量名。

          舉個(gè)例子(僅僅為例子說明,實(shí)際工程會(huì)比以下復(fù)雜許多),一開始我們的 user1 寫了一下幾個(gè)模塊,跑起來非常的順暢。

          image-20200626231748187
          ├──?bar.js????function?bar(){}
          ├──?baz.js????function?baz(){}
          └──?foo.js?function?foo(){}

          但是呢,隨著業(yè)務(wù)迭代,工程的復(fù)雜性增加,來了一個(gè) user2,這個(gè)時(shí)候 user2,需要開發(fā)一個(gè) foo 業(yè)務(wù),里面也有一個(gè) baz 模塊,代碼也很快寫好了,變成了下面這個(gè)樣子。

          ├──?bar.js????function?bar(){}
          ├──?baz.js????function?baz(){}
          ├──?foo
          │???└──?baz.js?function?baz(){}
          └──?foo.js?function?foo(){}

          但是呢這個(gè)時(shí)候,老板來找 user2 了,為什么增加了新業(yè)務(wù)后,原來的業(yè)務(wù)出錯(cuò)了呢?這個(gè)時(shí)候發(fā)現(xiàn)原來是 user2 寫的新模塊覆蓋了 user1 的模塊,從而導(dǎo)致了這場事故。

          image-20200626220806881

          因此,當(dāng)我們開發(fā)的時(shí)候?qū)⑺械哪K都暴露在全局的時(shí)候,想要避免錯(cuò)誤,一切都得非常的小心翼翼,我們很容易在不知情的偷偷覆蓋我們以前定義的函數(shù),從而釀成錯(cuò)誤。

          因此 webpack 帶來的第一個(gè)核心作用就是隔離,將每個(gè)模塊通過閉包的形式包裹成一個(gè)個(gè)新的模塊,將其放于局部作用域,所有的函數(shù)聲明都不會(huì)直接暴露在全局。

          image-20200626220851909

          原來我們調(diào)用的 是 foo 函數(shù),但是 webpack 會(huì)幫我們生成獨(dú)一無二的模塊ID,完全不需要擔(dān)心模塊的沖突,現(xiàn)在可以愉快地書寫代碼啦。

          baz.js
          module.exports?=?function?baz?(){}

          foo/baz.js
          module.exports?=?function?baz?(){}

          main.js
          var?baz?=?require('./baz.js');
          var?fooBaz?=?require('./foo/baz.js');

          baz();
          fooBaz();

          可能你說會(huì)之前的方式也可以通過改變函數(shù)命名的方式,但是原來的作用范圍是整個(gè)工程,你得保證,當(dāng)前命名在整個(gè)工程中不沖突,現(xiàn)在,你只需要保證的是單個(gè)文件中命名不沖突。(對(duì)于頂層依賴也是非常容易發(fā)現(xiàn)沖突)

          image-20200627140818771

          模塊依賴加載

          還有一種重要的功能就是模塊依賴加載。這種方式帶來的好處是什么?我們同樣先來看例子,看原來的方式會(huì)產(chǎn)生什么問題?

          User1 現(xiàn)在寫了3個(gè)模塊,其中 baz 是依賴于 bar 的。

          image-20200627000240836

          寫完后 user1 進(jìn)行了上線,利用了順序來指出了依賴關(guān)系。




          可是過了不久 user2 又接手了這個(gè)業(yè)務(wù)。user 2 發(fā)現(xiàn),他開發(fā)的 abc 模塊,通過依賴 bar 模塊,可以進(jìn)行快速地開發(fā)。可是 粗心的 user2 ?不太明白依賴關(guān)系。竟然將 abc 的位置隨意寫了一下,這就導(dǎo)致 運(yùn)行 abc 的時(shí)候,無法找到 bar 模塊。

          image-20200627000713100




          因此這里 webpack 利用 CommonJS/ ES Modules 規(guī)范進(jìn)行了處理。使得各個(gè)模塊之間相互引用無需考慮最終實(shí)際呈現(xiàn)的順序。最終會(huì)被打包為一個(gè) bunlde 模塊,無需按照順序手動(dòng)引入。

          baz.js
          const?bar?=?require('./bar.js');
          module.exports?=?function?baz?(){
          ?...
          ?bar();
          ?...
          }

          abc.js
          const?bar?=?require('./bar.js');
          module.exports?=?function?baz?(){
          ?...
          ?bar();
          ?...
          }
          <script?src="./bundle.js">script>
          image-20200627003815071

          webpack 的模塊化機(jī)制與實(shí)現(xiàn)

          基于以上兩項(xiàng)特性,模塊的隔離以及模塊的依賴聚合。我們現(xiàn)在可以非常清晰的知道了webpack所起的核心作用。

          • 為了盡可能降低編寫的難度和理解成本,我沒有使用 AST 的解析,(當(dāng)然 AST 也不是什么很難的東西,以后的文章中我會(huì)講解 AST是什么以及 AST 解析器的實(shí)現(xiàn)過程。
          • 僅實(shí)現(xiàn)了 CommonJS 的支持

          bundle工作原理

          為了能夠?qū)崿F(xiàn) webpack, 我們可以通過反推的方法,先看webpack 打包后 bundle 是如何工作的。

          「源文件」

          //?index.js
          const?b?=?require('./b');
          b();
          //?b.js
          module.exports?=?function?()?{
          ????console.log(11);
          }

          「build 后」(去除了一些干擾代碼)

          (function(modules)?{
          ??var?installedModules?=?{};
          ??function?__webpack_require__(moduleId)?{
          ????if?(installedModules[moduleId])?{
          ??????return?installedModules[moduleId].exports;
          ????}
          ????var?module?=?(installedModules[moduleId]?=?{
          ??????i:?moduleId,
          ??????l:?false,
          ??????exports:?{},
          ????});
          ????modules[moduleId].call(
          ??????module.exports,
          ??????module,
          ??????module.exports,
          ??????__webpack_require__
          ????);
          ????module.l?=?true;
          ????return?module.exports;
          ??}
          ??return?__webpack_require__((__webpack_require__.s?=?0));
          })([
          ??/*?0?*/
          ??function(module,?exports,?__webpack_require__)?{
          ????var?b?=?__webpack_require__(1);
          ????b();
          ??},
          ??/*?1?*/
          ??function(module,?exports)?{
          ????module.exports?=?function()?{
          ??????console.log(11);
          ????};
          ??},
          ]);

          image-20200627135324956

          以上就是 bundle 的運(yùn)作原理。通過上述的流程圖我們可以看到,有四個(gè)關(guān)鍵點(diǎn)

          • 已注冊(cè)模塊(存放已經(jīng)注冊(cè)的模塊)
          • 模塊列表(用來存放所有的包裝模塊)
          • 模塊查找(從原來的樹形的模塊依賴,變成了扁平查找)
          • 模塊的包裝(原有的模塊都進(jìn)行了一次包裝)

          webpack實(shí)現(xiàn)

          通過 bundle 的分析,我們只需要做的就是 4 件事

          • 遍歷出所有的模塊
          • 模塊包裝
          • 提供注冊(cè)模塊、模塊列表變量和導(dǎo)入函數(shù)
          • 持久化導(dǎo)出

          模塊的遍歷

          首先來介紹一下模塊的結(jié)構(gòu),能使我們快速有所了解, 結(jié)構(gòu)比較簡單,由內(nèi)容和模塊id組成。

          interface?GraphStruct?{
          ????context:?string;
          ????moduleId:?string;
          }
          {
          ?"context":?`function(module,?exports,?require)?{
          ????const?bar?=?require('./bar.js');
          ??const?foo?=?require('./foo.js');
          ??console.log(bar());
          ??foo();
          ??}`,
          ??"moduleId":?"./example/index.js"
          }

          接下來我們以拿到一個(gè)入口文件來進(jìn)行講解,當(dāng)拿到一個(gè)入口文件時(shí),我們需要對(duì)其依賴進(jìn)行分析。說簡單點(diǎn)就是拿到 require 中的值,以便我們?nèi)ふ蚁乱粋€(gè)模塊。由于在這一部分不想引入額外的知識(shí),開頭也說了,一般采用的是 AST 解析的方式,來獲取 require 的模塊,在這里我們使用正則。

          用來匹配全局的?require?
          const?REQUIRE_REG_GLOBAL?=?/require\(("|')(.+)("|')\)/g;
          用來匹配?require?中的內(nèi)容
          const?REQUIRE_REG_SINGLE?=?/require\(("|')(.+)("|')\)/;
          const?context?=?`
          const?bar?=?require('./bar.js');
          const?foo?=?require('./foo.js');
          console.log(bar());
          foo();
          `
          ;
          console.log(context.match(REQUIRE_REG_GLOBAL));
          //?["require('./bar.js')",?"require('./foo.js')"]
          image-20200627202427794

          由于模塊的遍歷并不是只有單純的一層結(jié)構(gòu),一般為樹形結(jié)構(gòu),因此在這里我采用了深度遍歷。主要通過正則去匹配出require 中的依賴項(xiàng),然后不斷遞歸去獲取模塊,最后將通過深度遍歷到的模塊以數(shù)組形式存儲(chǔ)。(不理解深度遍歷,可以理解為遞歸獲取模塊)

          image-20200627142130902

          以下是代碼實(shí)現(xiàn)

          ...
          private?entryPath:?string
          private?graph:?GraphStruct[]
          ...
          createGraph(rootPath:?string,?relativePath:?string)?{
          ????//?通過獲取文件內(nèi)容
          ????const?context?=?fs.readFileSync(rootPath,?'utf-8');
          ????//?匹配出依賴關(guān)系
          ????const?childrens?=?context.match(REQUIRE_REG_GLOBAL);
          ???//?將當(dāng)前的模塊存儲(chǔ)下來
          ????this.graph.push({
          ????????context,
          ????????moduleId:?relativePath,
          ????})
          ????const?dirname?=?path.dirname(rootPath);
          ????if?(childrens)?{
          ???????//?如有有依賴,就進(jìn)行遞歸
          ????????childrens.forEach(child?=>?{
          ????????????const?childPath?=?child.match(REQUIRE_REG_SINGLE)[2];
          ????????????this.createGraph(path.join(dirname,?childPath),?childPath);
          ????????});
          ????}
          }

          模塊包裝

          為了能夠使得模塊隔離,我們?cè)谕獠糠庋b一層函數(shù), 然后傳入對(duì)應(yīng)的模擬 requiremodule使得模塊能進(jìn)行正常的注冊(cè)以及導(dǎo)入 。

          function?(module,?exports,?require){
          ????...
          },

          提供注冊(cè)模塊、模塊列表變量和導(dǎo)入函數(shù)

          這一步比較簡單,只要按照我們分析的流程圖提供已注冊(cè)模塊變量、模塊列表變量、導(dǎo)入函數(shù)。

          /*?modules?=?{
          ??"./example/index.js":?function?(module,?exports,?require)?{
          ????const?a?=?require("./a.js");
          ????const?b?=?require("./b.js");

          ????console.log(a());
          ????b();
          ??},
          ??...
          };*/

          bundle(graph:?GraphStruct[])?{
          ????let?modules?=?'';
          ????graph.forEach(module?=>?{
          ????????modules?+=?`"${module.moduleId}":function?(module,?exports,?require){
          ????????${module.context}
          ????????},`
          ;
          ????});
          ????const?bundleOutput?=?`
          ????(function(modules)?{
          ????????var?installedModules?=?{};
          ????????//?導(dǎo)入函數(shù)
          ????????function?require(moduleId)?{
          ????????????//?檢查是否已經(jīng)注冊(cè)該模塊
          ????????????if?(installedModules[moduleId])?{
          ????????????????return?installedModules[moduleId].exports;
          ????????????}
          ???????????//?沒有注冊(cè)則從模塊列表獲取模塊進(jìn)行注冊(cè)
          ????????????var?module?=?(installedModules[moduleId]?=?{
          ????????????????i:?moduleId,
          ????????????????l:?false,
          ????????????????exports:?{},
          ????????????});
          ???????????//?執(zhí)行包裝函數(shù),執(zhí)行后更新模塊的內(nèi)容
          ????????????modules[moduleId].call(
          ????????????????module.exports,
          ????????????????module,
          ????????????????module.exports,
          ????????????????require
          ????????????);
          ????????????//?設(shè)置標(biāo)記已經(jīng)注冊(cè)
          ????????????module.l?=?true;
          ????????????//?返回實(shí)際模塊
          ????????????return?module.exports;
          ????????}
          ????????require("${graph[0].moduleId}");
          ????})({${modules}})
          ????`
          ;
          ????return?bundleOutput;
          }

          持久化導(dǎo)出

          最后將生成的 bundle 持久寫入到磁盤就大功告成。

          fs.writeFileSync('bundle.js',?this.bundle(this.graph))

          完整代碼100行 代碼不到,詳情可以查看以下完整示例。

          github地址: ?https://github.com/hua1995116/tiny-webpack


          瀏覽 48
          點(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>
                  91电影无码| 欧美一级在线视频 | 成人理伦A级A片在线论坛 | 成人蘑菇视频 | 久插视频 |