可能就是好奇心略重了,讀了一下webpack打包后的bundle.js的代碼,復雜的模塊可能讀不懂,但簡單的hello world模塊我還是能看懂的。沒什么目的,就是想通過幾個簡單的模塊,一條簡單的webpack命令,一個神奇的bundle.js代碼來了解webpack是怎么把遵循commonJs規范的模塊應用到瀏覽器端的。
幾個簡單的模塊:
一條簡單的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 /******/ ]);
注釋太多有點懵逼,可是細看也不就是一個立即執行的函數表達式嘛(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所依賴的模塊,以及依賴的依賴,入口模塊和所有的依賴模塊作為數組中的項,並合並到最終的代碼中。
正是由於模塊代碼被包裝成函數后,每個模塊的運行時機變的可控,可以決定何時調用(通過 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整合開發》