Webpack探索【15】--- 基礎構建原理詳解(模塊如何被組建&如何加載)&源碼解讀

本文主要說明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 });

4.1 自執行函數的參數解讀

該參數是一個對象,對象屬性的key就是入口模塊和它引用的全部模塊文件的路徑和名字組合。整個代碼有多少文件被引入,就會有多少個屬性對應。屬性key對應的值是一個函數。該函數的內容具體是什麼,後邊會單獨分析。

4.2 自執行函數體解讀

自執行函數主要作了下邊幾件事:

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標識。在這裏正是啓動了入口模塊的加載。

4.3 __webpack_require__函數源碼解讀

該函數的源碼以下:

 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)返回模塊的導出值。

4.4 __webpack_require__函數的屬性源碼解讀

該函數上定義了不少屬性,各個屬性的做用以下(英文是源碼的原始註解,這裏沒有刪除):

 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__函數,能夠獲取到全部信息。

 4.5 入口文件./src/index.js源碼解讀

上邊分析自執行函數中,最主要的一行代碼就是經過__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__方法。

4.6 引用模塊./src/module.js源碼解讀

在上邊入口模塊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.直到入口文件執行完畢。

下邊是一個簡單的原理圖,畫的比較簡陋:

相關文章
相關標籤/搜索