Webpack模块化原理图解

为什么须要模块化

场景1

A同学开发了模块a,B同学开发了模块b,在页面下通过如下形式进行援用

<script src="a.js"></script><script src="b.js"></script>

这时模块a,模板b中的代码都裸露在全局环境中,如果模块a中定义了一个办法del。同学b并不知道,在模块b中也定义了一个办法del。这时便造成了命名抵触的的问题。

场景2

C同学开发了一个公共的工具库utils.js,D同学开发了一个公共的组件tab.jstab.js依赖utils.js。同学E须要应用D同学开发的tab.js,就须要通过如下形式援用

<script src="util.js"></script><script src="tab.js"></script>

同学E本人也开发了一个dailog.js同时它也依赖util.js。当初页面同时援用了dialog.jstab.js,代码如下

<script src="util.js"></script><script src="dialog.js"></script><scrpt src="tab.js"></script>

同学E不仅须要同时援用这三个js文件,还必须保障文件之间的援用程序是正确的。同时,从下面的代码咱们无奈间接看出模块与模块之间的依赖关系,如果不深刻tab.js,咱们无奈晓得tab.js到底是只依赖util.js还是dialog.js或者两者都依赖。随着我的项目逐步增大,不同模块之间的依赖关系则会变的越来越难以保护也会导致许多模块中大量的变量都裸露在全局环境中。

模块化的几种实现计划

模块化的标准有很多种, 如下
| 标准 | 实现计划 |
| --- | --- |
| CommonJS | node.js |
| AMD | Require.js |
| CMD | Sea.js
| UMD | |
| ES6 Module | |

webpack反对CommonJS,AMD,ESModule等多种模块化形式的语法

webpack的模块化原理图解

在webpack中,所有皆模块。上面咱们通过webpack来打包以下代码

目录构造如下:

代码如下:

// webpack.config.jsconst path = require('path');module.exports = {  entry: 'a.js',  output: {    path: path.resolve(__dirname, "dist"),          filename: "[name].js"    },    resolve: {          modules: [path.resolve(__dirname)]    },    optimization: {          minimize: false    }}
// a.jsvar b = require('b');module.exports = b.text + ' world';
// b.jsexports.text = 'hello';

在simple目录下执行webpack命令,会在simple目录下生成dist/output.js文件。

// outout.js// 代码及正文如下(() => {  // 所有导入的模块都存储在__webpack_modules__对象中,并且每个模块都要一个标识该模块的id  var __webpack_modules__ = ({     847: ((module, __unsed_webpack_exports, __webpack_require__) => {        // 模块a...     }),     996: ((__unused_webpack_module, exports) => {       // 模块b...     })  })    var __webpack_module_cache__ = {};  function __webpack_require__(moduleId) {      // 查看缓存中不存在847导出对象,避免模块847屡次执行     if (__webpack_module_cache__[moduleId]) {        // 从缓存中返回847导出对象        return __webpack_module_cache__[moduleId].exports;     }          // 1.创立{exports: {}}对象     // 2.像缓存中增加该对象,并让该对象与模块id 847相关联     var module = __webpack_module_cache__[moduleId] = {       exports: {}     }          // 3.通过__webpack_modules__查问模块847     // 4.执行模块847并传入刚刚创立的模块847的导出对象module,以及module.exports等     __webpack_modules__[moduleId](module, module.exports, __webpack_require__);          // 5.返回模块847的导出对象     return module.exports;  }    // 导入模块847  __webpack_require__(847);})()

如上代码咱们能够晓得,所有的的模块都存储在__webpack_modules__对象中,模块的导出对象则存储在__webpack_module_cache__对象中,咱们定义的模块都能够通过传入moduleId来调用__webpack_require__接口来拜访该模块以及该模块的导出对象。

当咱们通过__webpack_require__接口拜访模块847的导出对象时。会先判断__webpack_module_cache__对象中有没有该模块的导出对象,如果有的话,则间接返回。如果没有的话则会

  • (1)创立模块847的导出对象
var module = {  exports: {}}
  • (2)将该导出对象增加到__webpack_module_cache__对象中
__webpack_module_cahce__[moduleId] = module
  • (3)(4)通过__webpack_modules__查问模块847并执行
// 传入模块847的导出对象,以及require接口__webpack_modules__[moduleId](module, module.exports, __webpack_require__)
  • (5)返回模块847的导出对象
return module.exports

图解如下:

  • 橘黄色背景的元素代表数据
  • 绿色背景的元素代表函数

总结

模块与模块之间通过require 接口进行通信。

  • 模块第1次被援用时,调用require接口会像cache中增加导出对象。并返回增加的导出对象。
  • 模块被第2+次援用时,调用require接口查问cache对象时,返回cache中对应的导出对象