解讀webpack的bundle.js

      可能就是好奇心略重了,讀了一下webpack打包後的bundle.js的代碼,複雜的模塊可能讀不懂,但簡單的hello world模塊我仍是能看懂的。沒什麼目的,就是想經過幾個簡單的模塊,一條簡單的webpack命令,一個神奇的bundle.js代碼來了解webpack是怎麼把遵循commonJs規範的模塊應用到瀏覽器端的。webpack

幾個簡單的模塊:
web

一條簡單的webpack命令:
數組

一個神奇的bundle.js:瀏覽器

 1 /******/ (function(modules) { // webpackBootstrap
 2 /******/     // The module cache
 3 /******/     var installedModules = {};
 4 
 5 /******/     // The require function
 6 /******/     function __webpack_require__(moduleId) {
 7 
 8 /******/         // Check if module is in cache
 9 /******/         if(installedModules[moduleId])
10 /******/             return installedModules[moduleId].exports;
11 
12 /******/         // Create a new module (and put it into the cache)
13 /******/         var module = installedModules[moduleId] = {
14 /******/             exports: {},
15 /******/             id: moduleId,
16 /******/             loaded: false
17 /******/         };
18 
19 /******/         // Execute the module function
20 /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 
22 /******/         // Flag the module as loaded
23 /******/         module.loaded = true;
24 
25 /******/         // Return the exports of the module
26 /******/         return module.exports;
27 /******/     }
28 
29 
30 /******/     // expose the modules object (__webpack_modules__)
31 /******/     __webpack_require__.m = modules;
32 
33 /******/     // expose the module cache
34 /******/     __webpack_require__.c = installedModules;
35 
36 /******/     // __webpack_public_path__
37 /******/     __webpack_require__.p = "";
38 
39 /******/     // Load entry module and return exports
40 /******/     return __webpack_require__(0);
41 /******/ })
42 /************************************************************************/
43 /******/ ([
44 /* 0 */
45 /***/ function(module, exports, __webpack_require__) {
46 
47     /*
48       打印文本的index模塊
49      */
50     var text = __webpack_require__(1);
51     console.log(text);
52 
53 /***/ },
54 /* 1 */
55 /***/ function(module, exports) {
56 
57     /*
58       生成文本的Hello world模塊
59      */
60     module.exports = 'Hello world!';
61 
62 /***/ }
63 /******/ ]);
View Code

註釋太多有點懵逼,但是細看也不就是一個當即執行的函數表達式嘛(IIFE),這是JavaScript中常見的獨立做用域的方法。這裏我將註釋簡化一下:緩存

 1 (function(modules){
 2     //module緩存對象
 3     var installedModules = {};
 4     //require函數
 5     function __webpack_require__(moduleId){
 6         //檢查module是否在cache中
 7         if(installedModules[moduleId]){
 8             return installedModules[moduleId].exports;
 9         }
10         //若不在cache中則新建module並放入cache中
11         var module = installedModules[moduleId] = {
12             exports: {},
13             id: moduleId,
14             loaded: false
15         };
16         //執行module函數
17         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
18         //標記module已經加載
19         module.loaded = true;
20         //返回module的導出模塊
21         return module.exports;
22     }
23 
24     //暴露modules對象(__webpack_modules__)
25     __webpack_require__.m = modules;
26     //暴露modules緩存
27     __webpack_require__.c = installedModules;
28     //設置webpack公共路徑__webpack_public_path__
29     __webpack_require__.p = "";
30     //讀取入口模塊而且返回exports導出
31     return __webpack_require__(0);
32     
33 })([function(module, exports, __webpack_require__){ /*模塊Id爲0*/
34     var text = __webpack_require__(1);
35     console.log(text);
36 },function(module, exports){ /*模塊Id爲1*/
37     module.exports = 'Hello world';
38 }]);

這個IIFE接收一個數組做爲參數modules,數組的每一項都是一個匿名函數表明一個模塊(index.js和hello.js模塊)。但是爲何構建命令中只出現了「index.js"呢,這是由於webpack經過靜態分析index.js文件獲得語法樹,遞歸檢測index.js所依賴的模塊,以及依賴的依賴,入口模塊和全部的依賴模塊做爲數組中的項,併合併到最終的代碼中。ide

正是因爲模塊代碼被包裝成函數後,每一個模塊的運行時機變的可控,能夠決定什麼時候調用(經過 modules[moduleId].call ),並且也有了獨立的做用域,定義變量,聲明函數都不會污染全局做用域。模塊函數的參數列表中除了commonJS規範所要求的 module 與 exports 外,還有 __webpack_require__ 函數,用來替換 require ,做用是聲明對其餘模塊的依賴並得到該模塊的 exports ( return installedModules[moduleId].exports 和 return module.exports; )。並且不須要提供模塊的相對路徑或其餘形式的ID,直接傳入該模塊在modules列表中索引便可,這樣能夠省掉模塊標識符的解析過程(準確說,webpack是把require模塊的解析過程提早到了構建期),從而能夠得到更好的運行性能。

函數

程序解讀:
bundle.js經過 __webpack_require__(0); 啓動整個程序,先檢查模塊ID = 0是否在緩存對象中,若該模塊的緩存存在返回 module.exports 即模塊所暴露出來的數據,若該模塊的緩存不在則新建立module對象(該module對象做用是用來指向真實模塊)並加入到緩存對象中,此時因爲module對象和該模塊的緩存對象 installedModules[moduleId] 的exports屬性爲沒有數據,因此須要經過執行該模塊函數來返回具體require其餘模塊的數據,傳入的上下文對象是 module.exports 和 installedModules[moduleId].exports 所共同指向的一個對象。當程序執行到 var text = __webpack_require__(1); 時,又會執行 modules[1].call ,而後 module.exports = 'Hello world'; 將執行 __webpack_require__(1) 時建立的module1的exports賦值爲Hello world,並返回,此時 __webpack_require__(1) 執行完畢,text爲Hello world並打印, __webpack_require__(0) 執行完畢。這是一個遞歸的過程,若是還有更多依賴模塊的話會更明顯。

總結一下,webpack主要作了兩部分工做:
1.分析獲得全部必須模塊併合並。
2.提供讓這些模塊有序,正常的執行環境。

性能

參考:
《React全棧 Redux+Flux+webpack+Babel整合開發》ui

相關文章
相關標籤/搜索