• Webpack探索【15】--- 基础构建原理详解(模块如何被组建&如何加载)&源码解读


    本文主要说明Webpack模块构建和加载的原理,对构建后的源码进行分析。

     一 说明

    本文以一个简单的示例,通过对构建好的bundle.js源码进行分析,说明Webpack的基础构建原理。

    本文使用的Webpack版本是4.32.2版本。

    注意:之前也分析过Webpack3.10.0版本构建出来的bundle.js,通过和这次的Webpack 4.32.2版本对比,核心的构建原理基本一致,只是将模块索引id改为文件路径和名字、模块代码改为了eval(moduleString)执行的方式等一些优化改造。

    二 示例

    1)Webpack.config.js文件内容:

     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源码如下(下边代码是将注释去掉、压缩的代码还原后的代码):

      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.直到入口文件执行完毕。

    下边是一个简单的原理图,画的比较简陋:

  • 相关阅读:
    JAVA设计模式之单例模式
    JAVA设计模式之单例模式
    数据库连接池
    数据库连接池
    DbUtils操作数据库
    DbUtils操作数据库
    Hadoop 问题之 Linux服务器jps报process information unavailable
    echarts ——纵坐标赋值
    echarts ——div没有设置样式图表就展示不出来!
    Elasticsearch+spring boot(一)
  • 原文地址:https://www.cnblogs.com/zhaoweikai/p/10945771.html
Copyright © 2020-2023  润新知