本文主要說明Webpack模塊構建和加載的原理,對構建後的源碼進行分析。html
本文以一個簡單的示例,經過對構建好的bundle.js源碼進行分析,說明Webpack的基礎構建原理。webpack
本文使用的Webpack版本是4.32.2版本。web
注意:以前也分析過Webpack3.10.0版本構建出來的bundle.js,經過和此次的Webpack 4.32.2版本對比,核心的構建原理基本一致,只是將模塊索引id改成文件路徑和名字、模塊代碼改成了eval(moduleString)執行的方式等一些優化改造。npm
1)Webpack.config.js文件內容:json
1 const path = require('path'); 2 3 module.exports = { 4 entry: './src/index.js', 5 output: { 6 filename: 'bundle.js', 7 path: path.resolve(__dirname, 'dist') 8 }, 9 mode: 'development' // 'production' 用於配置開發仍是發佈模式 10 };
2)建立src文件夾,添加入口文件index.js:緩存
1 import moduleLog from './module.js'; 2 3 document.write('index.js loaded.'); 4 5 moduleLog();
3)在src目錄下建立module.js文件:函數
1 export default function () { 2 document.write('module.js loaded.'); 3 }
4)package.json文件內容:工具
1 { 2 "name": "webpack-demo", 3 "version": "1.0.0", 4 "description": "", 5 "main": "index.js", 6 "scripts": { 7 "test": "echo \"Error: no test specified\" && exit 1", 8 "webpack": "webpack" 9 }, 10 "keywords": [], 11 "author": "", 12 "license": "ISC", 13 "devDependencies": { 14 "webpack": "^4.32.2", 15 "webpack-cli": "^3.3.2" 16 }, 17 "dependencies": { 18 "lodash": "^4.17.4" 19 } 20 }
執行構建命令:npm run webpack優化
在dist目錄下生成的bundle.js源碼以下(下邊代碼是將註釋去掉、壓縮的代碼還原後的代碼):ui
1 (function (modules) { 2 // The module cache 3 var installedModules = {}; 4 // The require function 5 function __webpack_require__(moduleId) { 6 // Check if module is in cache 7 if (installedModules[moduleId]) { 8 return installedModules[moduleId].exports; 9 } 10 // Create a new module (and put it into the cache) 11 var module = installedModules[moduleId] = { 12 i: moduleId, 13 l: false, 14 exports: {} 15 }; 16 17 // Execute the module function 18 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 19 20 // Flag the module as loaded 21 module.l = true; 22 23 // Return the exports of the module 24 return module.exports; 25 } 26 27 28 // expose the modules object (__webpack_modules__) 29 __webpack_require__.m = modules; 30 31 // expose the module cache 32 __webpack_require__.c = installedModules; 33 34 // define getter function for harmony exports 35 __webpack_require__.d = function (exports, name, getter) { 36 if (!__webpack_require__.o(exports, name)) { 37 Object.defineProperty(exports, name, {enumerable: true, get: getter}); 38 } 39 }; 40 41 // define __esModule on exports 42 __webpack_require__.r = function (exports) { 43 if (typeof Symbol !== 'undefined' && Symbol.toStringTag) { 44 Object.defineProperty(exports, Symbol.toStringTag, {value: 'Module'}); 45 } 46 Object.defineProperty(exports, '__esModule', {value: true}); 47 }; 48 49 // create a fake namespace object 50 // mode & 1: value is a module id, require it 51 // mode & 2: merge all properties of value into the ns 52 // mode & 4: return value when already ns object 53 // mode & 8|1: behave like require 54 __webpack_require__.t = function (value, mode) { 55 if (mode & 1) value = __webpack_require__(value); 56 if (mode & 8) return value; 57 if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 58 var ns = Object.create(null); 59 __webpack_require__.r(ns); 60 Object.defineProperty(ns, 'default', {enumerable: true, value: value}); 61 if (mode & 2 && typeof value != 'string') for (var key in value) __webpack_require__.d(ns, key, function (key) { 62 return value[key]; 63 }.bind(null, key)); 64 return ns; 65 }; 66 67 // getDefaultExport function for compatibility with non-harmony modules 68 __webpack_require__.n = function (module) { 69 var getter = module && module.__esModule ? 70 function getDefault() { 71 return module['default']; 72 } : 73 function getModuleExports() { 74 return module; 75 }; 76 __webpack_require__.d(getter, 'a', getter); 77 return getter; 78 }; 79 80 // Object.prototype.hasOwnProperty.call 81 __webpack_require__.o = function (object, property) { 82 return Object.prototype.hasOwnProperty.call(object, property); 83 }; 84 85 // __webpack_public_path__ 86 __webpack_require__.p = ""; 87 88 89 // Load entry module and return exports 90 return __webpack_require__(__webpack_require__.s = "./src/index.js"); 91 }) 92 /************************************************************************/ 93 ({ 94 "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) { 95 "use strict"; 96 97 __webpack_require__.r(__webpack_exports__); 98 var _module_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/module.js"); 99 document.write('index.js loaded.'); 100 Object(_module_js__WEBPACK_IMPORTED_MODULE_0__["default"])(); 101 }), 102 103 "./src/module.js": (function (module, __webpack_exports__, __webpack_require__) { 104 "use strict"; 105 106 __webpack_require__.r(__webpack_exports__); 107 __webpack_exports__["default"] = (function () { 108 document.write('module.js loaded.'); 109 }); 110 }) 111 });
bundle.js整個代碼實際就是一個自執行函數,當在html中加載該文件時,就是執行該自執行函數。
大概結構以下:
1 (function (modules) { 2 // The module cache 3 var installedModules = {}; 4 // The require function 5 function __webpack_require__(moduleId) {...} 6 7 // expose the modules object (__webpack_modules__) 8 __webpack_require__.m = modules; 9 10 // expose the module cache 11 __webpack_require__.c = installedModules; 12 13 // define getter function for harmony exports 14 __webpack_require__.d = function (exports, name, getter) {...}; 15 16 // define __esModule on exports 17 __webpack_require__.r = function (exports) {...}; 18 19 // create a fake namespace object 20 // mode & 1: value is a module id, require it 21 // mode & 2: merge all properties of value into the ns 22 // mode & 4: return value when already ns object 23 // mode & 8|1: behave like require 24 __webpack_require__.t = function (value, mode) {...}; 25 26 // getDefaultExport function for compatibility with non-harmony modules 27 __webpack_require__.n = function (module) {...}; 28 29 // Object.prototype.hasOwnProperty.call 30 __webpack_require__.o = function (object, property) {...}; 31 32 // __webpack_public_path__ 33 __webpack_require__.p = ""; 34 35 // Load entry module and return exports 36 return __webpack_require__(__webpack_require__.s = "./src/index.js"); 37 }) 38 ({ 39 "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {...}), 40 "./src/module.js": (function (module, __webpack_exports__, __webpack_require__) {...}) 41 });
該參數是一個對象,對象屬性的key就是入口模塊和它引用的全部模塊文件的路徑和名字組合。整個代碼有多少文件被引入,就會有多少個屬性對應。屬性key對應的值是一個函數。該函數的內容具體是什麼,後邊會單獨分析。
自執行函數主要作了下邊幾件事:
1)定義了installedModules緩存模塊的對象變量
該變量用於存儲被加載過的模塊相關信息。該對象的屬性結構以下:
1 installedModules = { 2 "./src/index.js": { 3 i: moduleId, 4 l:false, 5 exports: {...} 6 } 7 }
installedModules對象的屬性key就是模塊的id,跟參數對象的key同樣。
屬性對象中有三個屬性:
i:模塊id,目前看和key是同樣的。
l:標識該模塊是否已經加載過。目前感受這個變量沒啥用,只有加載過的模塊纔會存到該變量中吧?可能還有其它用途,有待發現。
exports:加載完模塊後的,模塊導出的值都放在這個變量中。
2)定義了__webpack_require__函數,以及該函數上的各類屬性
該函數是Webpack的最核心的函數,相似於RequireJS的require方法。用於文件模塊的加載和執行。
詳細內容會在下邊專門講到。
3)經過__webpack_require__函數加載入口模塊
傳入的參數是模塊id,"./src/index.js"是入口模塊的id標識。在這裏正是啓動了入口模塊的加載。
該函數的源碼以下:
1 function __webpack_require__(moduleId) { 2 // Check if module is in cache 3 if (installedModules[moduleId]) { 4 return installedModules[moduleId].exports; 5 } 6 // Create a new module (and put it into the cache) 7 var module = installedModules[moduleId] = { 8 i: moduleId, 9 l: false, 10 exports: {} 11 }; 12 13 // Execute the module function 14 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 15 16 // Flag the module as loaded 17 module.l = true; 18 19 // Return the exports of the module 20 return module.exports; 21 }
該函數主要作了以下幾件事:
1)判斷該模塊是否已經加載過了:若是已經加載過了,從installedModules緩存中找到該模塊信息,並將以前加載該模塊時保存的exports信息返回。
2)若是該模塊沒有被加載過:建立一個模塊對象,用於保存該模塊的信息,並將該模塊存儲到installedModules緩存變量中,該模塊對象的屬性詳細見前邊說明。
3)加載該模塊的代碼。modules是整個bundle.js文件中自執行函數的參數,詳細見上邊說明。注意:執行該模塊的代碼函數時,傳入三個參數:模塊信息對象、模塊導出內容存儲對象、__webpack_require__函數。將該函數傳入的緣由是:加載的當前模塊,可能會依賴其它模塊,須要__webpack_require__繼續加載其它模塊。
4)將該模塊標識爲已加載過的。
5)返回模塊的導出值。
該函數上定義了不少屬性,各個屬性的做用以下(英文是源碼的原始註解,這裏沒有刪除):
1 // expose the modules object (__webpack_modules__) 2 // 保存整個全部模塊的原始信息,modules是整個bundle.js文件中自執行函數的參數,詳細見上邊說明。 3 __webpack_require__.m = modules; 4 5 // expose the module cache 6 // 保存全部已加載模塊的信息,具體見上邊說明 7 __webpack_require__.c = installedModules; 8 9 // define getter function for harmony exports 10 // 工具函數:給對應的exports對象上建立name屬性 11 __webpack_require__.d = function (exports, name, getter) { 12 if (!__webpack_require__.o(exports, name)) { 13 Object.defineProperty(exports, name, {enumerable: true, get: getter}); 14 } 15 }; 16 17 // define __esModule on exports 18 // 給緩存中加載過的模塊導出對象中,添加__esModule屬性。 19 // TODO:具體這個屬性的其它用途,待研究 21 __webpack_require__.r = function (exports) { 22 if (typeof Symbol !== 'undefined' && Symbol.toStringTag) { 23 Object.defineProperty(exports, Symbol.toStringTag, {value: 'Module'}); 24 } 25 Object.defineProperty(exports, '__esModule', {value: true}); 26 }; 27 28 // create a fake namespace object 29 // mode & 1: value is a module id, require it 30 // mode & 2: merge all properties of value into the ns 31 // mode & 4: return value when already ns object 32 // mode & 8|1: behave like require 33 // TODO: 待研究該函數做用,後續研究完補充 34 __webpack_require__.t = function (value, mode) { 35 if (mode & 1) value = __webpack_require__(value); 36 if (mode & 8) return value; 37 if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 38 var ns = Object.create(null); 39 __webpack_require__.r(ns); 40 Object.defineProperty(ns, 'default', {enumerable: true, value: value}); 41 if (mode & 2 && typeof value != 'string') 42 for (var key in value) __webpack_require__.d(ns, key, function (key) { 43 return value[key]; 44 }.bind(null, key)); 45 return ns; 46 }; 47 48 // getDefaultExport function for compatibility with non-harmony modules 49 // 工具函數:建立一個獲取模塊返回值的函數 50 __webpack_require__.n = function (module) { 51 var getter = module && module.__esModule ? 52 function getDefault() { 53 return module['default']; 54 } : 55 function getModuleExports() { 56 return module; 57 }; 58 __webpack_require__.d(getter, 'a', getter); 59 return getter; 60 }; 61 62 // Object.prototype.hasOwnProperty.call 63 // 工具函數:判斷一個對象是否存在一個屬性 64 __webpack_require__.o = function (object, property) { 65 return Object.prototype.hasOwnProperty.call(object, property); 66 }; 67 68 // __webpack_public_path__ 69 // 基礎路徑,這個在有些時候很是有用(例如:懶加載時),具體後續補充 70 __webpack_require__.p = "";
經過上邊的屬性能夠看出,經過__webpack_require__函數,能夠獲取到全部信息。
上邊分析自執行函數中,最主要的一行代碼就是經過__webpack_require__函數加載了入口文件。
__webpack_require__(__webpack_require__.s = "./src/index.js")
./src/index.js源碼以下:
1 "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) { 2 "use strict"; 3 4 __webpack_require__.r(__webpack_exports__); 5 var _module_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/module.js"); 6 document.write('index.js loaded.'); 7 Object(_module_js__WEBPACK_IMPORTED_MODULE_0__["default"])(); 8 })
主要作了以下幾件事:
1)調用__webpack_require__.r方法,具體參考上邊說明。
2)經過__webpack_require__調用依賴的模塊"./src/module.js",並將該模塊的exports存入_module_js__WEBPACK_IMPORTED_MODULE_0__ 變量。
3)執行index.js自身代碼。
能夠看出,相比index.js源碼,添加和修改了一些代碼,源碼中經過ES6的import導入模塊方式改成了__webpack_require__方法。
在上邊入口模塊index.js中,經過__webpack_require__加載了module.js模塊。
該模塊源碼以下:
1 "./src/module.js": (function (module, __webpack_exports__, __webpack_require__) { 2 "use strict"; 3 4 __webpack_require__.r(__webpack_exports__); 5 __webpack_exports__["default"] = (function () { 6 document.write('module.js loaded.'); 7 }); 8 })
主要作了以下幾件事:
1)調用__webpack_require__.r方法,具體參考上邊說明。
2)將導出值存入緩存該模塊信息對象的exports屬性對象中。
到此,bundle.js的全部源碼已解讀完畢。
從上邊源碼解讀中,能夠看出,整個構建過程以下:
1.將全部文件和內容存入自執行函數的參數對象;
2.經過__webpack_require__方法加載入口文件;
3.將加載了的文件信息緩存;
4.若是當前加載的文件依賴其它文件,就經過__webpack_require__繼續加載其它文件;
5.直到入口文件執行完畢。
下邊是一個簡單的原理圖,畫的比較簡陋: